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

Jesús Pérez
Once ADR escritos y jamás publicados. Cero contenido llegado a producción. 214 líneas de trabajo a un comando de desaparecer. Y ni un solo error en los logs: la herramienta informaba éxito y nadie leía el disco. La pregunta de la serie es «¿con ontoref no hubiera sido así?» — y este es el caso que responde que no, porque pasó dentro de ontoref con la regla ya aceptada.
Expediente 69/58: el espejo que revertía su propio trabajo

🩺 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.

Historia Nº 69/58Diagnóstico: ANTI-PAP · EL REPORTER DECIDÍAEstado: RESUELTO
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 ficheroestado declarado → ADR-070: todo árbol declara su DUEÑO
La herramienta informaba éxito y nadie comprobaba el discoreview 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 borraestado 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 lanzarasreview 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ónadr:070/mirror-typed-by-ownership
  • Un generador que ninguna receta llama no existe: entra en la cadenaadr:070/generator-in-the-chain
  • El contrato vive fuera del árbol de instancias que tipaadr:070/contract-in-the-instance-tree
  • El árbol servido es el único que existe, y el check compara los dosadr:072/the-served-tree-is-the-only-one-that-exists
  • En la cadena, con su caso negativo por ejegate:just expedientes-check

Glosario

¿Te ha resultado útil? Valóralo
¿Tienes algo que aportar? Cuéntame qué opinas, qué sugieres, o si seguimos explorando este tema.
· lecturas

Usamos cookies para que este sitio funcione, entender el uso del servicio y apoyar acciones de marketing. Política de cookies para más información.