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
+212
View File
@@ -0,0 +1,212 @@
# Runme como sidecar ejecutable
Este documento explica cómo y por qué `clusterlog` usa Runme, qué garantiza y
qué no. La decisión formal está en
[`docs/adr/0004-runme-sidecar.md`](adr/0004-runme-sidecar.md); esto es la
guía operativa del día a día.
## Qué es "sidecar" aquí
No es un contenedor sidecar de Kubernetes ni un servicio adicional. Es una
capa opcional y adyacente: Runme abre `RUNBOOK.md` y `runbooks/**/*.md` como
notebooks y ejecuta las celdas, pero cada celda sólo invoca `make`,
`./bin/clusterlog` o un script existente en `scripts/`. Si Runme no está
instalado, todo sigue funcionando exactamente igual copiando el comando de la
celda a una terminal — no hay una segunda implementación de la lógica en
ningún lado.
## Instalación
Versión fijada: **`v3.17.4`** (nunca `latest`).
```bash
make runme-install
```
Esto corre [`scripts/runme-install.sh`](../scripts/runme-install.sh), que:
- descarga el binario de `github.com/runmedev/runme` para su combinación de
SO/arquitectura (Linux x86_64/arm64, macOS x86_64/arm64);
- verifica su SHA-256 contra los valores publicados en el `checksums.txt` del
release (verificados de forma independiente con `sha256sum` durante esta
implementación, no copiados a ciegas del sitio de descargas);
- instala en `$HOME/.local/bin` (o `$RUNME_INSTALL_DIR`), nunca sobre
`bin/clusterlog` — el script aborta si detecta un binario `clusterlog` ahí;
- no usa `curl | sh`.
**Windows**: el script no lo soporta. Descargue manualmente
`runme_windows_{x86_64,arm64}.zip` desde la página de releases y verifique su
checksum contra el mismo `checksums.txt` antes de usarlo.
`make runme-install` no es un paso obligatorio de ningún flujo existente:
`make build`, `make test`, `clusterlog sync`/`validate` y `zola build` no lo
requieren ni lo disparan.
## Cómo correrlo
```bash
make runme-list # runme ls --filename RUNBOOK.md
make runme-check # clusterlog validate --skip-zola + compatibilidad blanda con Runme
make runme-ci # runme run --filename RUNBOOK.md --tag ci-safe --all --skip-prompts
```
`runme-check` nunca escribe en el repositorio. `runme-ci` sólo corre celdas
etiquetadas `ci-safe`.
## El flag `--tag` es real (corrección respecto al diseño inicial)
Durante la investigación previa a implementar, la documentación pública de
`docs.runme.dev` que se pudo consultar no mostraba un flag de filtrado por
etiqueta en `runme run`, sólo `--filter` (regex por nombre). El plan inicial
asumió por eso que la "seguridad de CI" tendría que vivir como una lista de
nombres explícita en el `Makefile`.
Al instalar el binario real de `v3.17.4` y correr `runme run --help`, apareció
`-t, --tag stringArray` ("Run from a specific tag"). Se verificó
empíricamente:
```console
$ runme run --tag totally-bogus-tag-xyz --filename RUNBOOK.md --dry-run
could not execute command: No tasks to execute with the tag provided
$ runme run --tag ci-safe --all --skip-prompts --filename RUNBOOK.md
► Running task clusterlog-runme-preflight...
...
► ✓ Task clusterlog-runme-preflight exited with code 0
► Running task clusterlog-build...
...
```
Es decir: `--tag` sí filtra de verdad, y `--tag ci-safe --all` corre
exactamente y sólo las celdas marcadas `ci-safe`, en el orden en que aparecen,
deteniéndose en el primer fallo. `make runme-ci` usa esa forma. La lección
que sí se mantiene del plan inicial: **nunca** ejecute `runme run --all
--skip-prompts` sin `--tag ci-safe` — sin el filtro, `--all` corre también
las celdas que mutan estado.
`runme ls --json` **no** expone las etiquetas de una celda en su salida (se
verificó con `--json` contra `RUNBOOK.md`: el JSON trae `name`, `file`,
`first_command`, `description`, `named`, `run_all`, pero no `tags`). Por eso
la fuente de verdad para "qué es `ci-safe`" es el atributo `tag` en el propio
Markdown, exigido y verificado por `clusterlog validate`
(`internal/clusterlog/runbooks.go`), no algo que se pueda inspeccionar desde
`runme ls`.
## `excludeFromRunAll` es la defensa real, no la etiqueta
`runme ls --json` también reveló que **toda** celda es elegible para
`--all` (`"run_all": true`) salvo que declare explícitamente
`"excludeFromRunAll": true`. Una celda `risk-mutating`/`risk-destructive` sin
ese atributo seguiría corriendo si alguien ejecuta `runme run --all` sin
filtro. Por eso `clusterlog validate` exige `excludeFromRunAll:true` +
`interactive:true` + la etiqueta `manual-only` en toda celda
`risk-mutating`/`risk-destructive` — la etiqueta sola no protege nada; el
atributo que Runme sí respeta es `excludeFromRunAll`.
## Capas de identidad (no confundirlas)
### Capa 1 — Identidad de contenido (Runme)
El atributo `name` de una celda y cualquier Lifecycle Identity que Runme
asigne identifican **el documento y la celda**, para poder seguir sus
cambios. No identifican quién la ejecutó ni prueban que se ejecutó. No edite
a mano un identificador Lifecycle Identity generado por Runme.
### Capa 2 — Evidencia local de sesión (Session Outputs)
Runme puede, si se habilita, guardar una copia del documento con la salida de
cada celda (hora, duración, código de salida, hostname, salida). Esta
integración **no habilita esa función**. Si en el futuro alguien la activa
localmente:
- debe quedar fuera de Git, del sitio Zola y de cualquier Gist;
- es evidencia auxiliar de que algo se ejecutó en una máquina, nunca prueba
de autoría ni sustituto de una memoria en `clusterlog`.
**Limitación documentada**: la documentación pública de Runme
(`docs.runme.dev/usage/auto-save/`) no fija un nombre de archivo exacto para
estos Session Outputs en `v3.17.4` — sólo dice "genera un archivo, agréguelo
a `.gitignore`". Por eso este repositorio **no** agrega un patrón amplio de
`.gitignore` que podría ocultar Markdown legítimo sin darse cuenta.
`clusterlog validate` sí revisa (best-effort, no exhaustivo):
- que no exista un directorio `.runme/` versionado en el árbol de trabajo;
- que ningún archivo dentro de `runbooks/` tenga "output" en el nombre sin
ser `_template.md` (señal, no prueba, de que podría ser una captura de
sesión y no contenido fuente).
La defensa real sigue siendo procedimental: revise el diff antes de hacer
`git add`, igual que con cualquier otro archivo generado.
### Capa 3 — Auditoría canónica de Clusterlog (sin cambios)
La atribución fuerte sigue siendo exactamente la de siempre:
```text
huella de llave pública SSH
+
identidad registrada en data/admins.json
+
commit Git firmado por SSH
+
verificación en CI (clusterlog verify commit)
```
Runme **no** está enlazado criptográficamente a esto de ninguna forma. Un
runbook crítico que termine en una escritura real sigue el patrón de
`AGENTS.md`: `clusterlog identity` → operación con la herramienta canónica →
validación observada → `--dry-run` → `--commit` en una celda manual,
interactiva y excluida de "Run All" → `clusterlog verify commit`.
Esta primera iteración **no** agrega un modelo nuevo de eventos de ejecución
(por ejemplo, un futuro `clusterlog runbook record`). Si se necesita, se
describe primero en un ADR con su propio modelo de amenazas y contrato JSON.
## Abrir y cerrar el modo notebook en VS Code
1. Instale la extensión recomendada en
[`.vscode/extensions.json`](../.vscode/extensions.json)
(`stateful.runme`).
2. Abra `RUNBOOK.md` o cualquier archivo en `runbooks/`; VS Code lo mostrará
como notebook.
3. Para volver a verlo como texto: clic derecho en la pestaña → "Reopen
Editor With..." → "Text Editor".
Ningún archivo `.md` fuera de `RUNBOOK.md`/`runbooks/` se fuerza a abrir como
notebook.
## Limitaciones conocidas de este entorno
- **Zola local es 0.22.1**, no la `0.23.3` que documenta `README.md` y que
instala la CI. `make validate-local` / `validate --skip-zola` no dependen
de la versión exacta; `make site`/`zola build` sí podrían comportarse
distinto localmente que en CI.
- El escaneo de `clusterlog validate` sobre runbooks es un analizador de
líneas con estado, no un parser completo de CommonMark: reconoce fences de
una sola línea de atributos (`` ```sh {"name":...} ``). Fences de apertura
multilínea no están soportados.
- La detección de "target de make inexistente" busca el patrón textual
`make <nombre>` dentro del cuerpo de la celda; puede marcar un falso
positivo si ese texto aparece dentro de un comentario o un `echo` (no
ocurre en los runbooks incluidos, pero es una limitación real del
analizador, no una garantía de que nunca puede pasar).
- La detección de Session Outputs es best-effort (ver arriba); no hay una
garantía de que capture todo lo que Runme pudiera llegar a escribir en el
futuro.
- `runme ls` lista **cualquier** fence de código, incluso uno sin atributos
y con lenguaje `text` usado a propósito como texto de referencia (se
confirmó con el binario real: aparecen como filas "No" en la columna
`NAMED`). No son ejecutables por nombre y `--tag ci-safe --all` los
ignora correctamente, pero ensucian la salida de `runme ls`/`make
runme-list`. No se encontró una forma de excluirlos sin dejar de mostrar
también snippets ilustrativos legítimos.
## Fuera de alcance de esta iteración
Terminal web, ejecución desde el sitio Zola, acceso directo al clúster desde
el navegador, un servicio Runme permanente, contenedor sidecar de
Kubernetes, Runme Cloud, publicación de Gists, autenticación propia de
Runme, reemplazo de Git/SSH, ejecución productiva en CI, conversión masiva de
manuales existentes, y runbooks para infraestructura que no existe en este
repositorio (bases de datos, escalamiento, rotación de secretos, despliegue a
producción).