Expediente 69/58: el espejo que revertía su propio trabajo
La herramienta decía 69. El disco tenía 58. Nadie comparaba las dos cosas — durante cuatro semanas
🩺 Mostrar historia clínica → 📋 Protocolo de sesión →
Historia clínica · Patología del Software
El paciente no tenía síntomas. Publicaba sin errores, la herramienta confirmaba el éxito, los logs salían en verde. Llevaba cuatro semanas publicando en el vacío.
Mostrar cuadro
- PAP
- Project's Architecture Principles — las reglas que sostienen la arquitectura: la fuente única que todo el código debe respetar.
- anti-PAP
- Código escrito en contra de esas reglas. Aquí: un mirror que decidía por dirección en vez de por propiedad.
- el check decide, nunca el reporter
- ADR-066, aceptado el 4-jul-2026: donde exista una comprobación mecánica, decide la comprobación; la palabra de quien informa puede negarse a ser contradicha, jamás imponerse. Este expediente es lo que ocurre en la única superficie donde esa regla no se aplicó.
- testigo
- Una constancia verificable de forma independiente de que algo es lo que dice ser. Un backup sin testigo no es un backup: es una copia en la que confías.
Protocolo para declarar, versionar y verificar esto → ontoref.dev
El doble balance — lo que costó, y lo que dejó
Lo que costó el crimen
- Lo que la herramienta informaba "index.json with 69 posts"
- Lo que había en el disco 58
- ADRs escritos y jamás publicados 11 (059→069)
- Ventana de invisibilidad 13-jun → 11-jul (fechas de los propios ADR)
- Contenido llegado a producción en ese tiempo 0
- Ficheros que el repo del site tenía trackeados 1 (README.md)
- git_sha estampado en TODOS los packs de deploy 5619a40 (siempre el mismo)
- Líneas de trabajo vivas sin un solo testigo 214
- Comandos de distancia hasta borrarlas 1 (<span class='mono'>just templates</span>)
- Tamaño del arreglo final 60 <i>líneas</i>
Lo que el caso dejó
- Decisión ADR-070 (aceptado)
- Gates adr-check · templates-check · posts-check
- Migración 0044 (rutas ancladas)
- La ranura que faltaba site/_htmx/templates/
- Restricciones curadas al anclar 29 (88✓ → 117✓)
- Ontología el converso en enforcement-vs-emergence
El punto de abandono — lo que no sale en la tabla
El punto de abandono de este caso no llegó con un error, sino con una frase: «he ido crate por crate por un sistema de codegen complejo y estoy muy pasado del punto razonable para seguir a ciegas». Es lo que dice una máquina cuando se le acaba el mapa y sigue caminando. Y la respuesta que lo desbloqueó no fue técnica: fue que alguien dibujara la jerarquía de niveles —rustelo → website-htmx-rustelo → outreach/site— que existía en una cabeza y en ningún fichero. Ese es el diagnóstico entero, dicho antes de tenerlo: lo que no está declarado, alguien lo sostiene con su memoria. Y la memoria se cansa.
Diagnóstico diferencial — lo que se descartó
| Los 6 huérfanos en site/r | “Sí, soy contenido muerto que sigue sirviéndose. Pero yo no impido publicar.” | síntoma, no causa |
| gen-adr-pages roto | “Yo funciono perfectamente. Genero los 69 ADR cuando alguien me llama.” | inocente — nadie lo llamaba |
| content_processor no procesa expedientes | “Sí los proceso. Lo que pasa es que me leíste con un head -20 y te cortó la salida.” | coartada firme (el error fue del investigador) |
| El índice no se regenera | “Me regenero cada vez. Con 69. Lo que pasa después no es cosa mía.” | inocente — y la clave del caso |
| El generador del grafo | “Yo exporto lo que hay en el árbol de contenido. Si me metéis un contrato entre los datos, reviento. Y hago bien.” | inocente — tenía razón |
Etiología — la causa — El mirror que decidía por dirección, no por propiedad
# La receta `content`, tal y como estaba: content_processor # escribe los índices en site/r rsync -a "site/public/r/" "site/r/" # ← el arma # Dos árboles. Cada uno DUEÑO de ficheros distintos: # site/r ← content_processor escribe aquí (lo lee el servidor) # site/public/r ← los generadores nu escriben aquí (y es lo que EMBARCA el deploy) # # El rsync corría en un solo sentido, y en el sentido equivocado: # · pisaba los índices recién generados con los fósiles del árbol de deploy # · y NUNCA llevaba el contenido nuevo al árbol que viaja a producción # # rsync -a sin --update no respeta "el destino es más nuevo": compara tamaño y # mtime, y si difieren, COPIA LA FUENTE. Un mirror cuya fuente se ha fosilizado # no es un no-op: es una máquina de resucitar estado viejo. # # La herramienta imprimía "Generated index.json with 69 posts". # El disco se quedaba con 58. # Nadie comparaba las dos cosas.
just — receta content
Tratamiento — El mirror se tipa por propiedad, y el check lee el disco
content_processor
# Índices de contenido → árbol de deploy. --delete: sin él, un slug renombrado
# sobrevive como URL viva EN PRODUCCIÓN.
rsync -a --delete \
--exclude about.json --exclude adr-map.json \
--exclude taglines.json --exclude content_graph.json \
"site/r/" "site/public/r/"
# Los cuatro artefactos nu → árbol servido. Van excluidos del tramo de ida, o las
# copias viejas de site/r machacarían los originales recién generados.
for f in about.json adr-map.json taglines.json content_graph.json; do
cp -f "site/public/r/$f" "site/r/$f"
done
# Y tres gates que ASERTAN CONTRA EL DISCO, nunca contra el stdout de la herramienta:
# adr-check el spine no tiene ningún ADR que el site no publique
# templates-check el árbol ensamblado se reproduce exacto desde su fuente declarada
# posts-check linkify + cobertura del grafo
#
# Los tres se pueden ejecutar MIENTRAS el daño sigue siendo hipotético.
# Un check que solo puedes correr después del paso destructivo no es un gate:
# es una autopsia.
just — receta content
Pronóstico
La serie hace una pregunta: ¿con ontoref no hubiera sido así? Casi siempre la respuesta es sí, y por eso hay serie. Este expediente es el que responde que NO — y por eso es el que más vale. Esto pasó DENTRO de ontoref, con la ontología cargada, los gates escritos y ADR-066 —«el check decide, nunca el reporter»— aceptado una semana antes y probado con una ejecución de falsación de ocho caminos. La regla que nombra exactamente esta patología ya existía mientras el pipeline que publica ontoref al mundo seguía dejando decidir al reporter. El protocolo no falló: es que la difusión era el punto ciego, la única superficie que el protocolo no gobernaba. Cada mecanismo que habría atrapado cada uno de estos fallos ya existía y estaba aceptado en este mismo repo. Ninguno estaba apuntado aquí. Y esa es la respuesta útil, la que un caso cómodo no da: no basta con tener el mecanismo — hay que apuntarlo a la superficie que duele. Un enforcement en el que no puedes confiar que falle es indistinguible de no tener enforcement, y es más peligroso, porque se cree.
| El mirror corría por dirección, no por propiedad de cada fichero | estado declarado → ADR-070: todo árbol declara su DUEÑO |
| La herramienta informaba éxito y nadie comprobaba el disco | review contra invariante → ADR-066: el check decide |
| Un generador existía y ninguna receta lo llamaba (11 ADR invisibles) | estado declarado → ADR-070: la REPRODUCCIÓN, en la cadena |
| 214 líneas vivían en el único árbol que el build borra | estado declarado → ADR-070: todo árbol declara su TESTIGO |
| Un contrato archivado entre los datos que tipa (just graph muerto) | PAP + anti-pattern → contract-in-the-instance-tree |
| Un check significaba una cosa según desde dónde lo lanzaras | review contra invariante → migración 0044: rutas ancladas |
La profilaxis — qué exige hoy la lección
- ✓Cada árbol de entrega declara dueño y testigo; un mirror corre por propiedad, no por dirección
adr:070/mirror-typed-by-ownership - ✓Un generador que ninguna receta llama no existe: entra en la cadena
adr:070/generator-in-the-chain - ✓El contrato vive fuera del árbol de instancias que tipa
adr:070/contract-in-the-instance-tree - ✓El árbol servido es el único que existe, y el check compara los dos
adr:072/the-served-tree-is-the-only-one-that-exists - ✓En la cadena, con su caso negativo por eje
gate:just expedientes-check
Glosario
Sin coincidencias.