refactor: convertir este repositorio en instancia de clusterlog
CI / validate-and-build (push) Failing after 7s
CI / runme (push) Failing after 5s

Retira el código del motor (cmd/, internal/, go.mod, templates/,
examples/, scripts de desarrollo Go) — vive ahora en
https://github.com/fenandosr/clusterlog (release v0.1.0). Este
repositorio conserva únicamente content/, data/, config/ y zola.toml:
los datos reales de esta instancia (MCPS).

Se agrega .clusterlog-version (fija v0.1.0) y scripts/fetch-engine.sh,
que descarga el binario y las plantillas del motor por versión fijada
verificando su checksum contra el release. Makefile y
.gitea/workflows/ci.yml se adaptan para descargar en vez de compilar.
RUNBOOK.md y runbooks/ se recortan a las celdas que sí operan sobre
datos reales de esta instancia (sync-check, validate-local, site-build,
review-mine, verify-head); el ciclo de desarrollo del motor mismo
(build/test/vet) vive en su propio repositorio.

Se preserva toda la historia existente: éste es un commit nuevo, no una
reescritura. Ver docs/adr/0005-motor-instancia.md en el repositorio del
motor para el contexto completo de la separación.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 00:40:26 -06:00
co-authored by Claude Sonnet 5
parent 5240cfd8c2
commit eac5e22ed1
81 changed files with 186 additions and 8411 deletions
-113
View File
@@ -1,113 +0,0 @@
# Decisiones de arquitectura (ADR)
## Alcance
Un ADR (*Architecture Decision Record*) documenta una decisión de diseño sobre **clusterlog**: la herramienta, su modelo de datos, su CLI, su proyección estática y su cadena de confianza. No es el lugar para decisiones operativas del clúster que administra el equipo (topología de nodos, políticas de red, capacidad, etc.); esas pertenecen a **Manuales** o **Memorias** dentro del sitio.
Un ADR se justifica cuando la decisión:
- es costosa o difícil de revertir;
- afecta el modelo de datos, la identidad/firma, o el contrato de la CLI;
- descarta una alternativa razonable por un motivo que no es obvio leyendo el código;
- va a ser preguntada de nuevo en el futuro ("¿por qué no usamos X?").
No se justifica para elección de librerías internas reversibles, estilo de código, o correcciones de bugs — eso va en el mensaje de commit o, si amerita contexto, en una memoria.
## Dónde viven y cómo se gestionan
- Los archivos están en [`docs/adr/`](adr/), uno por decisión.
- **No se ven en la UI del sitio ni se gestionan con la CLI de `clusterlog`.** `docs/adr/` no forma parte de `content/` (lo que Zola proyecta) y no existe ningún comando `clusterlog adr *`. Son Markdown plano, editado a mano, versionado y revisado como el resto del repositorio.
- Se crean, discuten y aprueban por revisión de código normal (pull request), igual que cualquier otro cambio al repositorio.
## Nombre de archivo
```text
docs/adr/NNNN-titulo-en-kebab-case.md
```
- `NNNN`: número secuencial de 4 dígitos, sin reutilizar ni renumerar al superar un ADR.
- El slug es una versión corta del título en minúsculas, con guiones.
## Campos y secciones
Cada ADR sigue esta estructura, en este orden:
```markdown
# ADR-NNNN: Título de la decisión
- **Estado:** propuesta | aceptada | rechazada | reemplazada por ADR-MMMM
- **Fecha:** AAAA-MM-DD
## Contexto
## Decisión
## Consecuencias positivas
## Consecuencias negativas
## Alternativas descartadas (opcional)
## Regla de interpretación (opcional)
```
| Campo/Sección | Obligatorio | Contenido |
|---|---|---|
| Título | sí | Frase corta que nombra la decisión, no el problema. |
| Estado | sí | Uno de: `propuesta`, `aceptada`, `rechazada`, `reemplazada por ADR-MMMM`. Un ADR aceptado no se borra ni se edita para invertir la decisión; se agrega uno nuevo que lo reemplaza y se actualiza el estado del anterior. |
| Fecha | sí | Fecha en que el estado actual entró en vigor. |
| Contexto | sí | La fuerza o restricción que exige decidir: qué problema, qué tensión, qué no puede seguir igual. Sin esto, la decisión no se puede evaluar más adelante. |
| Decisión | sí | Qué se decidió, en términos concretos y verificables (no aspiracionales). |
| Consecuencias positivas | sí | Efectos deseables que se aceptan a cambio. |
| Consecuencias negativas | sí | Costos, riesgos o deuda que la decisión introduce a sabiendas. Un ADR sin consecuencias negativas es sospechoso. |
| Alternativas descartadas | no | Opciones consideradas y por qué se descartaron. Útil cuando la pregunta "¿por qué no X?" es previsible. |
| Regla de interpretación | no | Aclaración normativa para evitar que la decisión se lea de forma laxa (ver ADR-0002 como ejemplo). |
## Ejemplo
```markdown
# ADR-0004: Límite de tamaño para eventos JSON individuales
- **Estado:** aceptada
- **Fecha:** 2026-08-18
## Contexto
Los eventos de `data/review-events/` y `data/task-events/` son archivos JSON
inmutables, uno por evento. Nada impide hoy que un evento incluya un adjunto
grande o un volcado de log completo, lo que degradaría `clusterlog sync` y
ensuciaría el historial Git con blobs pesados.
## Decisión
- Cada archivo de evento se limita a 64 KB.
- La CLI rechaza la escritura si el JSON serializado excede el límite,
con un mensaje que indica el tamaño real y el máximo permitido.
- El límite se valida antes de firmar o comitear, nunca después.
## Consecuencias positivas
- El historial Git permanece liviano y rápido de clonar.
- `clusterlog sync` tiene un tiempo de ejecución más predecible.
- Fuerza a resumir en vez de adjuntar evidencia extensa.
## Consecuencias negativas
- Evidencia grande (logs completos, capturas) debe vivir fuera del evento
y referenciarse por enlace o ticket.
- El límite es arbitrario y puede requerir ajuste con el uso real.
## Alternativas descartadas
- Sin límite: expone el repositorio a crecer sin control y a bloquear CI.
- Comprimir el evento: complica la lectura directa con herramientas
estándar de Git, que es una propiedad central de ADR-0001.
```
## Relación con otros tipos de contenido
- **ADR**: por qué el software está diseñado así. Vive en `docs/adr/`, no se sincroniza al sitio.
- **Manual** (`content/manuales/`): cómo ejecutar un procedimiento repetible sobre el clúster o la bitácora. Sí se publica en el sitio.
- **Memoria**: qué se hizo, cuándo, por quién, con qué validación y reversión, en una sesión concreta. Sí se publica en el sitio.
Si al escribir una memoria o un manual surge una decisión de diseño reutilizable que no está documentada, es señal de que falta un ADR — no de que deba explicarse de nuevo cada vez.
-212
View File
@@ -1,212 +0,0 @@
# 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).
-41
View File
@@ -1,41 +0,0 @@
# ADR-0001: Git como fuente y Zola como proyección
- **Estado:** aceptada
- **Fecha:** 2026-08-12
## Contexto
El sitio debe mostrar estado compartido —revisiones, tiempo de tareas y estados— aunque Zola genere archivos estáticos. Guardar ese estado en el navegador no sería compartido ni auditable. Introducir una base de datos en el MVP aumentaría operación, permisos, respaldo y superficie de ataque.
## Decisión
La fuente de verdad será:
- Markdown para entidades legibles;
- un archivo JSON inmutable por evento;
- Git para historia y concurrencia;
- `clusterlog sync` para producir `data/generated/site_state.json`;
- Zola para consumir la proyección en modo sólo lectura.
La proyección es desechable y nunca se edita a mano.
## Consecuencias positivas
- El repositorio funciona sin un servicio permanente.
- Toda la historia es inspeccionable con herramientas estándar.
- Los conflictos se reducen al separar eventos.
- La publicación puede ser atómica y cacheable.
- Es posible reconstruir el sitio desde cero.
## Consecuencias negativas
- No hay edición web en tiempo real.
- Cada escritura necesita sincronización Git.
- Con volúmenes muy altos de eventos habrá que medir rendimiento y quizá archivar.
- La coherencia depende de CI, validación y disciplina de ramas.
## Alternativas descartadas
- `localStorage`: no compartido, fácil de perder y no auditable.
- Archivo JSON central mutable: conflictos frecuentes entre administradores.
- Base de datos desde el MVP: complejidad desproporcionada para el flujo y volumen iniciales.
-35
View File
@@ -1,35 +0,0 @@
# ADR-0002: Identidad por llave SSH y prueba mediante commit firmado
- **Estado:** aceptada
- **Fecha:** 2026-08-12
## Contexto
Los administradores ya usan llaves SSH y los agentes operan en su sesión local. Se busca evitar un sistema adicional de usuarios y contraseñas, pero una llave pública almacenada no basta para probar que una operación la realizó quien posee la privada.
## Decisión
- El registro `data/admins.json` asocia una o más huellas públicas con cada administrador.
- La CLI resuelve la identidad a partir de una llave local.
- El contenido conserva `admin_id` y huella para trazabilidad.
- Las escrituras de producción usan `--commit` para crear un commit Git firmado por SSH.
- `config/allowed_signers` se genera desde administradores activos.
- CI y protección de rama verifican firmas y autorización.
## Consecuencias positivas
- Reutiliza la práctica operativa existente.
- Evita secretos de aplicación dentro del repositorio.
- La evidencia se integra con el historial Git.
- Los agentes pueden usar el mismo contrato sin conocer la llave privada.
## Consecuencias negativas
- La configuración inicial de Git/OpenSSH requiere cuidado.
- Rotación y revocación deben gobernarse explícitamente.
- Una operación sin `--commit` queda atribuida sólo de manera declarativa.
- El proveedor Git debe aplicar políticas de firma y rama.
## Regla de interpretación
La huella es identidad declarada; una firma válida y autorizada es evidencia criptográfica. Nunca deben tratarse como equivalentes.
-31
View File
@@ -1,31 +0,0 @@
# ADR-0003: Revisiones por hash y conjunto capturado
- **Estado:** aceptada
- **Fecha:** 2026-08-12
## Contexto
Un booleano `revisada` no indica quién revisó ni qué versión vio. Además, añadir un administrador meses después podría reabrir todo el histórico si la política se recalculara siempre contra el equipo actual.
## Decisión
Al crear una memoria:
- se captura el conjunto de revisores requerido;
- se calcula un hash SHA-256 sobre la versión de contenido;
- cada revisión registra administrador, huella, fecha y hash;
- sólo cuentan revisiones cuyo hash coincide con el actual;
- la cola se completa cuando todos los IDs capturados tienen un evento válido.
## Consecuencias positivas
- Editar una memoria invalida automáticamente el consenso anterior.
- La cola histórica no cambia al incorporar administradores futuros.
- Es posible explicar exactamente por qué una entrada está pendiente.
- Los eventos permanecen inmutables y auditables.
## Consecuencias negativas
- Corregir incluso un detalle menor puede reabrir la revisión.
- El equipo debe decidir cuándo una corrección editorial merece conservar o repetir consenso; el MVP prioriza seguridad y vuelve a abrir siempre.
- Bajas de personal requieren una política de excepción explícita, todavía no automatizada.
-216
View File
@@ -1,216 +0,0 @@
# 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.