Skip to content

ADR-0012: Image channel retirement (G3)

Status: Accepted (rescatado como fichero 2026-09-06 desde OBJ-DOCSRUNTIME-001)

(contenido completo pendiente de rescate desde OBJ-DOCSRUNTIME-001)

ADR-0010 — Etapa G1: Canal runtime docs (decisión de arquitectura)

  • Decisión: docs.wetopi.net pasa a servirse de un pod runtime que rebuild-ea desde git@main al recibir webhook GitHub (HMAC). El contrato "imagen inmutable + promo" queda RETIRADO para docs-site (fue diseñado como fail-safe del primer landing; el operador decide el nuevo modelo: merge a main = publicado, gate de contenido = el PR review + mkdocs --strict del runtime). Alternativa considerada: mantener promo por imagen (rechazada por el operador: "rebuild de imagen es lento").
  • Controles de seguridad como PRECONDICIÓN (no fase posterior): el endpoint /hooks/rebuild NUNCA queda enrutable sin sus controles. La promoción es ATÓMICA: despliegue del deployment y exposición del route ocurren juntos con todos los controles de ADR-0011 ya activos (HMAC, filtro push/main, swap atómico, last-known-good, lock). Antes del flip del route se demuestra con NEGATIVA: un request sin firma válida (o firma incorrecta) → 401; evento no-push o ref≠main → ignorado. Prohibida cualquier variante donde G1 exponga el webhook y G2 añada después sus controles.
  • AC: ADR accepted (approver) + despliegue ATÓMICO documentado: deployment docs-runtime (build inicial desde main) + controles HMAC/filtro/swap/last-known-good/lock activos en el MISMO despliegue + pruebas negativas de firma/evento/ref superadas ANTES del route flip + HTTPRoute apuntando al service nuevo + webhook GitHub registrado y validado (rebuild disparado por push test) + identidad de versión visible: el footer de docs.wetopi.net muestra el sha7 de la build servida (formato build: <sha7> + link al commit, via override mkdocs-material con variable git_sha que el runtime inyecta al hacer fetch); AC de footer verificable contra git rev-parse --short HEAD de docs@main.
  • Gate: approver (ADR + manifests + evidencia de pruebas negativas) + operador (flip del route).

ADR-0011 — Etapa G2: Webhook + swap atómico + fail-safe (precondición de G1)

  • Decisión: endpoint /hooks/rebuild (HMAC-SHA256 con secreto compartido, solo eventos push a main), rebuild a emptyDir y swap simbólico atómico; build fallido = sigue sirviendo el último build bueno (nunca 500); build-lock para no pisar rebuilds concurrentes. Esta etapa NO va después de G1: sus controles son precondición de la exposición del endpoint definida en ADR-0010 (promoción única y atómica G1+G2).
  • AC: push de prueba a main → sitio actualizado en <60s; build roto intencional (test) → sitio sigue sirviendo el anterior + alerta en log; pruebas negativas de firma/evento/ref registradas como evidencia del gate G1 (ver ADR-0010).
  • Gate: approver (seguridad del endpoint) — sin operador; se valida en el mismo gate atómico G1+G2.

ADR-0012 — Etapa G3: Retirada del canal imagen (cleanup)

  • Decisión: deployment docs-site (imagen GHCR) retirado tras N=7 días de estabilidad del runtime; la imagen/docs-build CI se mantiene como artefacto de backup/rollback (no se borra: ADR-0003 recovery).
  • AC: docs-site deployment archived, HTTPRoute docs-site apunta al runtime, CI docs-build sigue verde (artefacto), runbook de rollback documentado (kubectl apply del deployment imagen = 5 min).
  • Gate: operador (la retirada es su decisión por timeline).

ADR-0013 — Etapa G4: Estado/status viva del proyecto (depende de G1)

⛔ DEFERRED — NO-GO en el GO actual (decisión del operador, 2026-08-31): el repo docs está en plan org Free de GitHub; branch protection/rulesets no disponibles en repos privados (403 verificado en vivo por el approver). El operador acepta operar sin branch protection y NO necesita el status automático (cada 15 min): los dashboards de estado (emojis) se actualizan con cada merge normal. Reactivar tras upgrade GitHub Team o mecanismo equivalente. No es parte del GO actual.

  • Decisión: la página /status/ (dashboard de proyecto con emojis que el operador pidió en el análisis de visibilidad) se publica por el canal runtime — script determinista en el pod (cron interno) lee kanban state.db + gh api + ledgers y genera/actualiza docs/project-status.md en el repo docs vía PR automático (ver decisión de escritura abajo) → el propio webhook lo publica. Cero imagen, cero promo. El script escribe mediante la API de GitHub (creación de PR) con el token del pod contra el artefacto generado (docs/project-status.md).
  • Decisión explícita sobre escritura: PR automático, NO commit directo a main. El token del pod NO puede bypasear branch protection: el script abre un PR (rama efímera con etiqueta autosync) y un auto-merge con aprobación de máquina SOLO si las comprobaciones de CI (mkdocs strict) están verdes y el diff toca exclusivamente el artefacto generado. Si la aprobación automática de PR no está disponible o queda deshabilitada, el PR queda abierto a revisión humana — el canal nunca escribe en main directamente. (Si el operador prefiere eliminar el auto-merge, basta desactivarlo; la decisión NO queda abierta.)
  • Invariantes verificables (ACs) del contrato G4:
  • Identidad/token de mínimo privilegio: el token del pod es un fine-grained PAT limitado al repo docs, permiso contents: read + write SOLO (sin admin, sin workflows, sin otros scopes). Verificación: auditar scopes del PAT en GitHub y demostrar una operación prohibida falla (p. ej. listar otro repo o modificar un workflow) → 403.
  • Branch protection SIN bypass: main está protegida y el token no está en la allowlist de bypass. La vía de escritura es únicamente PR automático (ver decisión explícita). Verificación: commit directo a main con el token del pod → rechazado por GitHub; ningún setting de allow-force-push/bypass concede el paso.
  • Fuentes read-only: kanban state.db, gh api y ledgers se leen read-only; el script no invoca ninguna operación de escritura contra Kanban/infraestructura (sin kubectl mutante, sin mutation de board). Verificación: revisión del código del script + prueba de que su ejecución no altera ningún estado externo salvo el PR descrito.
  • Redacción / no secretos: el contenido generado nunca contiene valores, tokens, nombres de secretos, URLs con credenciales ni cuerpos de evidencia; sólo IDs, estados, fechas y contadores. Verificación: secret scanner (el mismo gate del PR) sobre docs/project-status.md + revisión por lista negra de campos.
  • Escritura acotada al artefacto @generated: el PR automático sólo puede tocar docs/project-status.md (marcado <!-- @generated --> en cabecera). El auto-merge valida que el diff del PR toca SOLO esa ruta; cualquier otro path aborta el merge y falla cerrado. Verificación: PR de prueba con un cambio fuera del artefacto → el auto-merge lo rechaza.
  • Idempotencia y concurrencia: el script es idempotente (misma entrada → mismo status.md, diff vacío = no abre PR) y usa un lock/ejecución única (cron overlap → skip); dos ejecuciones concurrentes no crean PRs duplicados. Verificación: doble ejecución manual consecutiva → segunda no-op; simulación de solape → sólo una corre.
  • Fail-closed: cualquier fallo (API GH inaccesible, estado kanban ilegible, CI roja, validación de scope de diff fallida) deja el sitio /status/ sirviendo la última versión buena (el propio fail-safe del runtime) y NO publica contenido parcial ni desactualizado sin marca; el error queda en log, nunca silencioso. Verificación: inyectar fallo de API en test → sin PR, sin contenido parcial, log con alerta.
  • AC (resumen verificable): /status/ accesible con estado real de cards/objectives/PRs; refresco <15 min; marcado @generated; los 7 invariantes de arriba demostrados con sus verificaciones concretas — los 7 en verde son requisito del gate G4.
  • Gate: approver (contrato @generated, contrato del token y verificación de los 7 invariantes) — matiz: el ajuste de cadencia (<15 min) y del modo de auto-merge es del operador, pero la decisión de escritura (PR automático, no commit directo) está tomada aquí.