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/rebuildNUNCA 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 contragit rev-parse --short HEADde 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
docsestá 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, permisocontents: read + writeSOLO (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í.