feat(runme): integrar Runme como sidecar de documentación ejecutable

Agrega RUNBOOK.md y runbooks/ (fuera de content/, para no interferir con
la publicación de Zola), con reglas de nombres/etiquetas/aislamiento de
celdas mutantes verificadas por clusterlog validate. Instalación pinneada
a v3.17.4 con checksum verificado (scripts/runme-install.sh), un job de
CI separado (.gitea/workflows/ci.yml) que sólo corre celdas ci-safe, y la
decisión documentada en docs/adr/0004-runme-sidecar.md y docs/RUNME.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 00:06:01 -06:00
co-authored by Claude Sonnet 5
parent fcee75fe83
commit c06037fa3b
22 changed files with 1682 additions and 1 deletions
+216
View File
@@ -0,0 +1,216 @@
# ADR-0004: Runme como sidecar de documentación ejecutable
- **Estado:** aceptada
- **Fecha:** 2026-08-26
## Contexto
Los procedimientos frecuentes de `clusterlog` (compilar, probar, sincronizar,
validar, la revisión semanal) viven como bloques de comando dentro de
Markdown que hay que copiar y pegar a mano. Se evaluó integrar Runme
(`v3.17.4`, fijado) para poder abrir esos mismos procedimientos como
notebooks ejecutables desde VS Code o su CLI, sin introducir un segundo lugar
donde vivan los mismos comandos ni debilitar el modelo de identidad/auditoría
existente (huella SSH + commit firmado + `clusterlog verify commit`).
## Decisión
Se integra Runme como una capa opcional y adyacente:
- `RUNBOOK.md` (raíz) y `runbooks/**/*.md` son los únicos documentos que
llevan atributos de celda Runme.
- Cada celda invoca exclusivamente `make`, `./bin/clusterlog` o un script ya
existente en `scripts/`. Ninguna celda reimplementa validación,
autorización, resolución de identidad, rollback o gestión de secretos.
- `clusterlog validate` (no un script aparte) hace cumplir las reglas de
nombres, etiquetas y aislamiento de celdas mutantes — ver
`internal/clusterlog/runbooks.go`.
- La instalación de Runme es opcional y reproducible
(`scripts/runme-install.sh`, checksum verificado); ningún flujo existente
(`make build`, `make test`, `clusterlog sync`/`validate`, `zola build`)
depende de que Runme esté instalado.
## Significado de "sidecar"
No es un sidecar de Kubernetes ni un servicio adicional. Es un envoltorio: el
humano o agente interactúa con Markdown+Runme, que a su vez llama `make`,
`clusterlog` o un script — nunca al revés, y nunca con lógica propia.
## Ubicación de los runbooks
`RUNBOOK.md` en la raíz (índice) y `runbooks/` como carpeta dedicada, **fuera
de `content/`**. Se confirmó en `zola.toml` que Zola no tiene un
`content_dir` alterno: sólo construye desde `content/`. Poner los runbooks
fuera de esa carpeta significa que Zola nunca los toca — cero riesgo de que
un atributo de celda `{"name":...}` rompa el front matter TOML o el
resaltado de sintaxis de una página publicada. `clusterlog validate` además
hace cumplir esto activamente: si una celda de Runme aparece dentro de
`content/`, es un error (`runme_cell_in_zola_content`), no sólo una
recomendación.
## Relación con `content/manuales`
`content/manuales/` no se toca. Sigue siendo prosa + comandos de ejemplo para
lectura humana en el sitio Zola, sin atributos de celda ni ninguna
dependencia de Runme. Los runbooks nuevos no son una migración de los
manuales existentes: son procedimientos de desarrollo/operación de
`clusterlog` mismo que hoy no existían como documento ejecutable.
## Fuente de verdad
Opción elegida de las tres consideradas (sección 4.2 del pedido original):
**un archivo raíz delgado que enlaza procedimientos canónicos**, aplicada
así:
- `docs/WEEKLY_REVIEW.md` conserva la agenda y el criterio de la reunión
(prosa) y enlaza a `runbooks/weekly-review.md` para los comandos
ejecutables, en vez de mantener los mismos bloques de shell en dos
archivos.
- `README.md` enlaza `RUNBOOK.md` y `docs/RUNME.md` en vez de duplicar
ejemplos de comandos.
No se copian bloques de comando manualmente entre dos documentos en ningún
punto de esta integración.
## Convención de metadatos
Atributos de celda como objeto JSON de una sola línea después del lenguaje
del fence: `` ```sh {"name":"...","tag":"...","interactive":"...",
"excludeFromRunAll":true} ``. `clusterlog validate` decodifica ese objeto con
`encoding/json` (no confía en una regex para interpretar el contenido; la
regex sólo reconoce la *forma* de la línea de fence).
## Taxonomía de etiquetas
Cuatro categorías, tres obligatorias por celda:
| Categoría | Valores | Obligatoria |
|---|---|---|
| Audiencia | `role-developer`, `role-operator`, `role-reviewer`, `role-owner` | sí |
| Entorno | `env-local`, `env-ci`, `env-staging`, `env-production` | sí |
| Riesgo | `risk-read-only`, `risk-mutating`, `risk-destructive` | sí |
| Ejecución automatizada | `ci-safe`, `manual-only` | no, mutuamente excluyente con riesgo mutante |
## Modelo de identidad
Tres capas, documentadas en detalle en `docs/RUNME.md`:
1. **Identidad de contenido** (Lifecycle Identity / `name` de Runme):
identifica documento y celda, no a quien ejecuta.
2. **Evidencia local de sesión** (Session Outputs, no habilitados aquí):
auxiliar, nunca auditoría canónica.
3. **Auditoría canónica de Clusterlog**: huella SSH + `data/admins.json` +
commit firmado + `clusterlog verify commit`, sin cambios.
No se afirma ni se implementa ningún enlace criptográfico entre Runme y un
commit. Un futuro `clusterlog runbook record` queda explícitamente fuera de
esta iteración (ver "Trabajo futuro").
## Modelo de evidencia
Session Outputs permanecen desactivados por defecto en esta integración. Si
se habilitan más adelante, deben quedar fuera de Git, del sitio Zola y de
Gists, y tratarse como evidencia auxiliar redactable, nunca como prueba de
autoría.
**Limitación reconocida**: la documentación pública de Runme para `v3.17.4`
no fija el nombre exacto de archivo de un Session Output
(`docs.runme.dev/usage/auto-save/` sólo recomienda "agréguelo a
`.gitignore`" sin dar el patrón). Se optó explícitamente por **no** agregar
un glob amplio e inseguro a `.gitignore` que pudiera ocultar Markdown
legítimo. En su lugar, `clusterlog validate` aplica dos señales concretas
(directorio `.runme/` versionado; archivos en `runbooks/` con "output" en el
nombre) como defensa best-effort, documentada como tal.
## CI/CD
`.gitea/workflows/ci.yml` (Gitea Actions, no GitHub Actions — este repo no
tiene `.github/`) gana un job nuevo y separado que no reemplaza
`test-and-build`: instala Runme fijado en `v3.17.4` vía
`scripts/runme-install.sh` (checksum verificado), corre `make runme-check`
(reglas de `clusterlog validate`, obligatorias, más un chequeo blando de
compatibilidad de Runme) y `make runme-ci`
(`runme run --filename RUNBOOK.md --tag ci-safe --all --skip-prompts`).
Permisos mínimos (`contents: read`), `DO_NOT_TRACK=true`,
`SCARF_NO_ANALYTICS=true`, sin llaves ni acceso a producción.
La CI original de Zola instala herramientas fijadas por `curl` + tar en vez
de una Action de marketplace (ver el paso "Instalar Zola fijado" ya
existente); Runme sigue el mismo patrón por consistencia, no por elección
nueva.
## Seguridad
Ver el nuevo apartado en `SECURITY.md`. En resumen: una celda Runme equivale
a ejecutar comandos locales con los permisos de quien la corre; una etiqueta
no es control de acceso, `excludeFromRunAll`/`interactive` son defensas
adicionales; Runme no lee llaves privadas; Runme Cloud, Gists y `runme open`
expuesto a `0.0.0.0` quedan fuera de alcance.
## Alternativas consideradas
1. **Celdas dentro de `content/manuales/`.** Descartada: arriesga el front
matter TOML y el resaltado de Zola, y mezcla contenido para lectores con
contenido para ejecución; no encaja con la opción 1 de fuente única
(Markdown único compatible con ambos) porque el pedido de Zola y el de
Runme divergen (TOML `+++` vs. atributos JSON de celda) sin un beneficio
real, dado que `runbooks/` fuera de `content/` ya resuelve el conflicto
sin ese riesgo.
2. **Validación en un script Bash aparte en vez de `clusterlog validate`.**
Descartada para las reglas estructurales: encajan limpio en el patrón
existente de `ValidationReport`/`report.add(...)` y ya tenían pruebas
unitarias equivalentes para páginas/eventos. Sí se mantiene un script
(`scripts/runme-check.sh`) para el chequeo blando que sólo tiene sentido
con el binario real de Runme instalado.
3. **Selección de celdas CI-safe con una lista de nombres en el Makefile en
vez del flag `--tag`.** Fue el plan inicial, basado en documentación
pública de Runme que no mostraba un flag de filtrado por etiqueta. Al
instalar el binario real de `v3.17.4` se confirmó que `runme run --tag`
sí existe y filtra correctamente (ver `docs/RUNME.md`). Se adoptó
`--tag ci-safe --all --skip-prompts` porque es la forma más simple que
sigue siendo una allowlist explícita por etiqueta de seguridad, no un
"correr todo" sin filtro.
## Consecuencias
Positivas:
- Los procedimientos de desarrollo se pueden ejecutar como notebook sin
perder la posibilidad de copiar/pegar el comando a mano.
- `clusterlog validate` ahora también protege contra celdas mutantes mal
etiquetadas, nombres duplicados y fugas de Runme hacia `content/`.
- La instalación reproducible con checksum evita depender de `latest` o de
`curl | sh`.
Negativas / riesgos aceptados:
- El analizador de runbooks es un escáner de líneas, no un parser CommonMark
completo; hay casos borde documentados en `docs/RUNME.md`.
- La detección de Session Outputs es best-effort, no una garantía.
- Dos documentos nuevos (`RUNBOOK.md`, `docs/RUNME.md`) que hay que mantener
sincronizados con la realidad del `Makefile`.
## Limitaciones
- Zola local (`0.22.1`) no coincide con la versión que exige `README.md` y
que instala la CI (`0.23.3`); no se intentó resolver esto en esta
iteración.
- No se ejecutó ninguna celda `risk-mutating` de verdad durante la
implementación (por diseño: requieren identidad real y quedan fuera del
alcance de una demostración automatizada).
- No existe hoy un enlace criptográfico entre una ejecución de Runme y un
commit; quien necesite esa garantía debe seguir el flujo completo de
`AGENTS.md` fuera de Runme.
## Trabajo futuro
- Evaluar un comando `clusterlog runbook record` que capture, con su propio
contrato JSON y modelo de amenazas, evidencia de que una celda `ci-safe`
corrió — sólo si el equipo decide que Session Outputs no bastan como
evidencia auxiliar.
- Migrar manuales existentes a runbooks ejecutables uno por uno, sólo cuando
exista infraestructura real que respalde cada procedimiento (no antes).
- Revisar si vale la pena que `runme ls --json` exponga etiquetas en una
versión futura de Runme, para poder validar la relación entre "celdas
etiquetadas ci-safe" y "celdas que `runme-ci` realmente ejecutó" de forma
automatizada en vez de manual.