# El Método — constitución Principios que valen para **cualquier** sesión (exploración, ejecución, despliegue, incidente, orquestación) y para cualquier backlog. Los workflows concretos referencian este ⛔ **Y EL DAÑO NO ES EL QUE PARECE: UNA FILA ARRASTRADA NO QUEDA CORRUPTA — QUEDA PUBLICADA EN UNA VERSIÓN ANTERIOR.** No se rompe nada, no hay conflicto, no hay nada que se vea: simplemente el remoto **anuncia un estado de hace veinte minutos como si fuera el de ahora**. En un fichero cuyo valor entero es decir **qué está pasando AHORA**, un arrastre **no rompe: DESINFORMA** — y el siguiente que lo lea **lo creerá al día**. Es peor que un conflicto, que al menos se ve. ⇒ Y con un bloque de estado en vuelo dentro, sube de categoría: **ese campo existe para que un sucesor no tenga que reconstruir nada**, así que **publicarlo desactualizado es exactamente lo que ese campo promete que no pasa**. ⛔ **Frecuencia medida, y cambia cómo hay que leer todo esto: el 2026-08-30 ocurrió TRES veces en un día**, con seis sesiones vivas — dos mal *(una no leyó la puerta; otra la leyó **después** de actuar)* y **una bien**: miró el `diff` **antes** del `add`, comprobó que fueran esas filas y ningún fichero más, no tocó el texto ajeno **y lo declaró en el mensaje del commit**. ⇒ Por eso **el arrastre es la NORMA, no la excepción**, y por eso el remedio no es un mecanismo nuevo —*costaría más que el choque*— sino **dos gestos de una línea**: **declararlo en el mensaje** y **avisar a su dueña por el canal**, porque **el dueño es el único que sabe si lo publicado estaba al día**. doc en vez de repetirlo. No son teoría: cada uno lleva el caso real que lo enseñó. > **Versión 2026-08-29.** Documento **autocontenido**: es lo que se vendoriza, byte a byte, a cada > repo (no lleva enlaces a rutas de un árbol concreto, para que funcione clonado en solitario). > Cada regla lleva su **cicatriz** (meta-principio: un raíl se escribe con el incidente que lo > enseñó, no a priori). Procedencia, fuerza (nº de repos) y partición linter/juicio: fichero > `constitucion-de-facto.md` de la matriz. > > ⛔ **Este fichero ES la fuente**, y vive en el repo `metodo` (la matriz). Se reparte con > `metodo update`, que lo copia byte a byte a `.metodo/metodo.md` de cada repo. **La copia no se > edita nunca** (§7): se edita aquí y se re-estampa. *(Cutover 2026-08-19: hasta ese día la fuente > era la copia del árbol de trabajo y esta constitución llevaba los 10 raíles base **en una línea**, > con un puntero a una ruta que en un clon ajeno no existe. Coste medido: el raíl nuevo de §8 —«un > fallo benigno solo protege si alguien LEE la línea»— nació el 2026-08-19 y **no estaba en ninguna > de las 18 copias**. Ahora los raíles viajan COMPLETOS y las adiciones que estaban «pendientes de > inlinar» viven dentro de su raíl.)* > Origen: destilados de la racha Hermes de julio 2026 (Fases 11/13, convergencia UI) **unificados > con el método ya afinado en los workflows del frente de aplicación** (el más maduro en método). La lección de > fondo: **una UI/gate honesto actúa como auditor del resto del sistema** — casi todos los raíles de > abajo nacieron de un "verde que mentía" cazado al construir bien la capa de encima. > > **Meta-principio: cada raíl se escribe CON LA CICATRIZ.** El humano (o un gate) > caza lo que no cuadra → el raíl se escribe con el caso real que lo enseñó → no vuelve a pasar. Por > eso cada punto lleva su ejemplo: sin la cicatriz, un principio abstracto no se respeta. Este doc > crece añadiendo cicatrices, no teoría. ## 0. Si no escala, no vale para nada *(Xavier — la máxima que gobierna a las demás)* > *«Estamos en Kubernetes, GitOps con Helm, y lo estamos haciendo así **pensando en la masa**: masa > de edges, masa de hubs, redundancia barata. La clave es que sea **desechable y barato de > levantar**. Alquilar un VPS es barato; poner un Kubernetes en el edge con IP pública y un nombre > de dominio, también. Si dejo escritos los manifiestos, puedo levantar **todos los hubs que quiera > y todos los edges que quiera** de manera masiva. **No quiero cosas difíciles ni centralizables.**»* ⇒ **Y la consecuencia sobre la seguridad, que es la que más cambia el criterio**: *«si me capturan un Vault me doy por jodido, pero si me capturan un token lo revoco, **tiro el hub a la basura y levanto otro**»*. La respuesta a un compromiso **no es prevenirlo a cualquier precio: es que reponerlo sea barato**. Un componente que hay que defender porque cuesta reconstruirlo ya ha fallado el criterio. **Cómo se aplica, y en qué se nota:** - ⛔ **Lo que no se puede levantar en masa desde manifiestos, no vale** — aunque funcione. Un paso manual por instancia es un tope de escala disfrazado de detalle. - ⛔ **Lo centralizable se mira dos veces**: un secreto compartido por todas las instancias, un registro único, un nodo del que dependen los demás. Cada uno convierte «tiro uno y levanto otro» en «tengo un problema». - ⭐ **Y la pregunta antes de aceptar cualquier mecanismo**: *¿esto sigue funcionando con **cien**?* Si la respuesta exige un gesto humano por instancia, no escala — y por `§0` **no vale**. *(Familia de `A20`: la colisión de loopbacks era 1,9 % a 50 nodos y **26 % a 200**; lo que a escala de banco es una anécdota, a escala de flota es el trabajo de una tarde.)* ⚠️ **Y lo que NO cubre lo desechable, dicho una vez para que conste**: tirar un componente **no deshace lo que ese componente FIRMÓ o PUBLICÓ mientras estaba vivo**. Un artefacto firmado sobrevive a su emisor —se propagó, es monótono y no caduca—, así que ahí la reposición barata no es la respuesta. Es el único sitio donde hay que mirar el ciclo de vida de la **credencial**, no el de la **máquina**. ### ⭐ La calibración: contra qué se mide «suficientemente bueno» *(Xavier, 2026-08-18)* Antes de proponer una fortificación, **el punto de comparación no es un ideal: es lo que hoy se usa en su sector**. Y es esto, dicho por él: > *«Tengo aquí cifradores físicos del trabajo que **no son tan complicados como este sistema, y mucho > menos útiles y mucho menos seguros**. Llevan tokens físicos, y si alguien pierde uno se lía de tal > magnitud que hay que **llevarlos todos a que les cambien las claves**. Y en el sitio donde trabajo > **a nadie se le ha pasado por la cabeza que capturen en la vida el cifrador expuesto a internet** — > y a mí también me cuesta. **Y los tenemos.**»* ⇒ **Dos consecuencias, y las dos cortan discusiones enteras:** - **Sobre el coste de recuperarse**: *«si hay que hacer 1.000 o 10.000 ceremonias, sigue siendo infinitamente más barato que lo que tendríamos que hacer hoy si nos capturaran un cifrador físico edge»*. ⇒ Un procedimiento de reposición **caro pero existente** ya es una mejora enorme sobre el estado del arte, que es **no tener ninguno**. No se compara contra cero coste: se compara contra *«llevarlos todos al taller»*. - **Sobre qué amenazas modelar**: la captura del nodo expuesto a internet **no está en el modelo de nadie** en su sector, con equipos que protegen más y valen más. ⇒ Insistir en ella aquí no es rigor: es **gastar el foco de Xavier en el escenario que su propio sector descarta**, y él ya lo aplazó explícitamente tres veces el mismo día. ⛔ **Regla operativa**: antes de escribir *«pero si te comprometen X…»*, pregúntate **contra qué lo comparas**. Si la alternativa real que Xavier tiene hoy es peor, la propuesta no mejora nada — solo añade dificultad, y eso lo prohíbe `I11·D23` (del hub: «la vara de medir es el COSTE PARA EL OPERADOR»). ⇒ ⛔ **Y el error que esto evita, cometido CUATRO veces el 2026-08-18: una decisión lleva ÁMBITO DE AMENAZA, y leerla fuera de su ámbito la hace parecer mala.** *(Xavier, ordenando: «no estoy diciendo que no valore la captura de un hub — lo pondremos en el backlog por detrás, con su apartado. Ahora estamos en el apartado de **“y si capturan un edge”**, y para eso hay una CRL y unos procedimientos que mis equipos de hoy **ni tienen**».)* La orquestadora evaluó `D25` —del **edge**, apartado `E14`; ⚠️ **sigue sin entrada en su registro**, ver `A43` en el registro de avisos del edge, así que esta cita no se puede calificar todavía— contra la captura de un **hub**, y la decisión salía mal cuatro veces seguidas. **No estaba mal: se estaba midiendo con la vara de otra sección.** ⇒ **Regla**: antes de objetar, pregúntate **de qué apartado es la decisión**. Si tu objeción vive en otro, no es una objeción: es una **entrada para ese otro apartado** — y se escribe allí, no aquí. ⭐ Y el corolario que lo hace útil en vez de burocrático: **objetar sigue siendo bienvenido**. Lo que Xavier gestiona no es si la objeción vale, es **su alcance y su prioridad en el backlog**. ⭐ **Y el porqué, en sus palabras: «buscamos MEJORAR, no la perfección desde el principio».** Una mejora entregada bate a una solución completa que no existe todavía — y la que no existe **cuesta foco**, que es el recurso escaso. ⇒ Lo que convierte esto en método y no en excusa es lo de siempre: la mejora se **mide** y lo que queda fuera se **escribe** (un ⏸️ con su motivo, como el aplazamiento de la captura de hub), para que nadie lo confunda con un olvido. ⛔ **Y el refinement que lo hace ejecutable: una decisión que acarrea trabajo FUERA del alcance del producto o de la fase NO se queda como casilla.** Se convierte en **épica nueva** si es grande, o se va **a su fase** si tiene una — y se prioriza como todo lo demás: **por valor de producto**, no por lo bien argumentada que esté. *(Xavier, `D7`, 2026-08-22. Estrenada el mismo día: `I11.11` salió de su ficha a una épica propia **con su valor declarado — cero, porque no hay nadie esperándola**, que es justo el dato que una casilla escondida dentro de otra ficha no da.)* ⇒ Es el mismo movimiento que el del ámbito de amenaza, una capa más arriba: **lo que no es de aquí no se descarta ni se cuela — se muda, con su valor escrito.** ## 1. Verifica el resultado, no la intención Comprueba lo que el sistema **sirve/hace**, no lo que su fuente **dice** que hará. Una señal heredada, un booleano cocinado o "el comando salió 0" no son prueba de la operación de ahora. - El *verify version-aware* del deploy: `/health.version` == el digest pineado, no "el pod arrancó". - El `state` del DNS en el hub se calcula contra **el fichero de zona que dnsmasq parsea**, no contra la BD otra vez (es el único sitio donde se ve un `write_zone` fallido o un soft-state caducado que sigue resolviendo). - El snapshot de BD se acepta tras `pg_restore --list`, no tras "pg_dump salió 0". - ⛔ Antipatrón: "redes placebo" — dar por buena una operación por una señal que no la mide. ⭐ **Predice también lo que NO debe cambiar, y compruébalo.** Es lo que convierte un aviso en un hecho. *(F11.13: meter una LAN en la malla «toca el firewall, como en F9.2 que tiró el acceso de gestión». En vez de ir con cuidado, se **predijo** que el `md5` de `uci show firewall` no cambiaría —solo `ospfd.conf` gana una línea `network`— y se midió antes y después: idéntico. El miedo pasó a ser un invariante verificado.)* ⛔ **Una partición se verifica por SUS DOS CARAS: lo que debe estar cerrado Y lo que debe estar abierto.** Comprobar solo la mitad cerrada **no distingue «bien particionado» de «todo roto»** — y en autorización eso es peligrosísimo, porque el fallo se disfraza del resultado bueno. *(Cicatriz 2026-07-27: un segundo hub daba **401 en toda la administración** y parecía protegida. **No lo estaba: estaba rota.** Su Ingress reclamaba `/api`, que una imagen pre-F1 no sirve, así que el catch-all autenticado se tragaba también **`/links/pair`** — el mismo accidente que cerraba `/admin/pair` impedía que **ningún device pudiera emparejarse**. Se vio al medir la cara abierta: `/links/pair` pasó de `401` a **`422` con cuerpo de la aplicación**.)* ⇒ Es la misma familia que *«un panel que muestra "nada" y uno que no puede ver nada se leen igual desde fuera»* (F10.3), aplicada a la autorización. **Un `401` no es una prueba de nada por sí solo.** ⛔ **Y la partición NO se razona sobre el enrutador: se DERIVA de lo montado, y discrimina por MÉTODO además de por ruta.** El guard corre **antes** de enrutar, así que no ve rutas — ve **cadenas**. *(Cicatriz `R10`, 2026-07-28: la orquestadora afirmó «los espacios no chocan» mirando el enrutador —el panel usa `/`, `/pending`, `/devices/…`; la API `/links/*`— y era cierto **para el enrutador y falso para el guard**: `POST /devices/enroll` **casa con la plantilla** `GET /devices/{device_id}`, que es una página del panel. Una lista pública por **prefijo** habría abierto esa página a internet — el accidente del otro hub, en espejo: allí el catch-all cerró de más, aquí habría abierto de más.)* ⇒ La lista de lo público se genera **del montaje real**, nunca de un razonamiento sobre qué prefijos «son de la API»; y `GET /x` y `POST /x` son **dos entradas distintas**. ⇒ ⛔ **Y su aplicación al DESPLIEGUE, que es donde más engaña: «¿existe esta ruta?» NO se contesta con un código HTTP si delante hay un guard de sesión.** *(Cicatriz 2026-08-18: la orquestadora puso como testigo estrella de un despliegue que `GET /crl/afirmar` pasara de **404** a **302/401**, y escribió que sin esa comprobación *«un “desplegado” no distingue nada»*. Medido antes de usarlo: la ruta **no daba 404 ni antes** —daba `303 → /login`— y `/zzz-inventada` daba **exactamente lo mismo**. El testigo habría tomado el mismo valor con la pantalla puesta y sin ella.)* ⇒ **El testigo que sí discrimina es la TABLA DE RUTAS MONTADAS dentro del pod** —lo derivado del montaje, `R10` otra vez— y se lee **por el esquema OpenAPI, no recorriendo `app.routes` en plano**, que es la cicatriz `F11.11b` un piso más abajo: los routers incluidos van **anidados** y el recorrido plano ve seis rutas y ninguna `crl`. *(La orquestadora tropezó con las dos el mismo día: escribió el testigo ciego y luego midió el sustituto por el camino plano.)* ⇒ Y se comprueba por **sus dos caras**: que la ruta **existe** (tabla) y que **pide sesión** (`303 → /login`). La primera sin la segunda certifica una puerta abierta; la segunda sin la primera no certifica nada. ⚠️ **Cambiar la configuración no cambia lo que el cliente ya tiene.** *(F11.13: la concesión DHCP vieja siguió sirviendo `1.0.0.1` y `search lan` durante **12 h** después de que el router ya repartiera otra cosa.)* Renueva la concesión —o espera el lease— **antes** de medir, o estarás midiendo el pasado. **Prefiere la prueba POSITIVA a la ausencia.** «No hay hits» es débil —puede ser una ventana corta, un log rotado o un filtro mal puesto—; «el consumidor **se mudó**» es fuerte. *(F11.11b: en vez de concluir que nadie llamaba al endpoint viejo, se enseñó que **el mismo device**, minutos después de instalarse la versión nueva, pidió el endpoint nuevo con token y no volvió a tocar el viejo.)* ⛔ **Si hay dos caminos que hacen lo mismo, el que prueban tus tests se conserva y el que usa el usuario se pudre — y el andamio de los tests es justo lo que lo esconde.** *(Cicatriz 2026-07-27: al ir a borrar el duplicado `/api/admin/*` apareció que las dos copias habían **derivado**, y la correcta era **la que no llamaba nadie**. Desde el panel —la única puerta que usa un humano— aprobar un nodo **no lo publicaba** hasta el barrido siguiente, y revocar una sede dejaba resolviendo sus nombres **hasta 24 h**. Los tests iban por `/api/admin`, así que todo salía verde.)* ⇒ **Borrar un duplicado es un MERGE, no una limpieza**: antes de tirar una copia, averigua cuál de las dos es la correcta y porta lo que falte. Y **haz que los tests entren por la puerta del usuario**, o seguirán certificando la copia equivocada. - 🔎 **Y para separar tu propio ruido del tráfico real, genera un evento conocido y búscalo.** En esa misma sesión, la pista fácil era falsa: los hits «sospechosos» salían de la **misma IP pública y el mismo `curl`** que los del device (el router de campo y el PC salen por el mismo NAT), así que *«curl = humano»* no valía. Lo que los separó fue lanzar un `curl` propio, ver **qué versión de User-Agent** aparecía, y comprobar que el llamador periódico casaba **exactamente** con el intervalo de descubrimiento del edge. ⛔ **Un artefacto que GATEA un build tiene que vivir DENTRO del contexto de ese build.** Si el gate lee un fichero que la imagen no incluye, no estás comprobando nada: estás comprobando tu árbol de trabajo, que es justo lo que el gate existe para no creerse. *(Cicatriz `H35`, 2026-08-04, y salió bien porque el build se puso ROJO: el censo de la superficie pública se escribió en `doc/`, fuera del contexto `src/api` ⇒ dentro de la imagen **no existía**, `IndexError`, artefacto no pusheado. Se mudó a `src/api/tests/` y en `doc/` quedó un puntero.)* ⇒ La pregunta antes de escribir cualquier fichero del que dependa una comprobación: **¿entra en la imagen?** Y su gemela, que es la buena noticia: **un gate que revienta porque le falta su insumo está funcionando** — el fallo silencioso habría sido que lo encontrara vacío y siguiera. **Corolario: el verde lo certifica el GATE/la medida, no tu palabra.** "Los tests pasan" no es tu afirmación, es lo que dice el gate — y **antes de que exista artefacto** (gate rojo = no hay imagen que desplegar). Vale igual para cualquier "está hecho": lo certifica una comprobación, no la confianza en que lo hiciste bien. ⛔ **Un veredicto necesita un TESTIGO INDEPENDIENTE, o es decorativo.** Toda señal que gatea un `ready`/`verified`/`ok` debe poder **nombrar en el código**: (a) el observable independiente que lee —NO derivado de lo que verifica: ni el `control` del propio artefacto, ni la versión que TÚ inyectaste en el values, ni el status de una ejecución anterior, ni la **ausencia** de algo que comprobar, ni un exit-code sin mirar el efecto—; y (b) un **estado roto concreto, que PUEDE ocurrir en producción**, que la pone roja, demostrado mutando el cableado a ese estado (§3). Si no existe tal estado, la señal **no puede gatear**: se degrada a dato crudo. Corolarios: «no comprobado» es un veredicto **distinto** de «comprobado y verde» (y no admisible por defecto en una puerta); la **palabra** del veredicto codifica esa diferencia, no un campo lateral; una credencial acuñada prueba que **autentica** antes de `ready`. Pregunta única para cada señal: *«¿QUÉ tendría que estar roto para que esto se pusiera rojo, y ese estado PUEDE ocurrir de verdad?»* Si la respuesta es «no puede», es una tautología. *(Cicatriz 2026-07-28, barrido de la colección MCP: la MISMA clase apareció en los CUATRO — la rotación del MCP de secretos daba verde por una señal pegajosa + `kv_exists` [el path ya existía]; el `version_matches` del MCP del edge comparaba el `.ipk` con su propio `control`; el `mint_token(git)` no probaba que el token AUTENTICARA; el `verified-ok` del MCP de build daba verde con los pods VIEJOS sirviendo `200`; su gate A3 admitía `ungated` por ausencia de gates. Dos se cazaron por casualidad mirando otra cosa; el barrido encontró el resto. Dos de cuatro habría sido casualidad; cuatro de cuatro es un raíl.)* ⛔ **Leer el código de TU capa no dice qué hace la capa de encima con tu salida.** *(Cicatriz `I8`, 2026-07-29: la ficha estimaba —leyendo `wg_hub.py`— que un re-alta anónimo dejaba el peer WG en pie y que la pérdida llegaría «en el siguiente reinicio de `wg-hub`». **Son 36 segundos**: la API reescribe `wg0.conf` sin ese peer, y `gre-sync` —**un script del chart, en OTRO repo**— borra el `grehub` y con él la adyacencia OSPF. El peer sí sobrevivía; **moría todo lo construido encima**, y eso convertía una bomba latente en una denegación de servicio en vivo.)* ⇒ Una predicción sacada de tu propio código describe **tu función**, no el sistema. Y con las capas repartidas entre repos, el que consume tu salida **no aparece en tu `grep`**: se mide, o se mide. ⛔ **Dos copias que coinciden entre sí no son una verificación — el aparato es el único que desempata.** *(Cicatriz Fase 14, 2026-07-28: `transport-spec.md` daba la derivación v2 de la loopback por «congelado ⏳» y describía la v1 —«la teclea el operador; el hub la asigna por `peer_index`»— con las **dos mitades falsas**. Su espejo decía lo mismo, así que **leyendo docs era indetectable**: solo el hardware llevaba la contraria, con `10.100.203.26` en el router de campo y `10.100.255.154` en el hub. De la misma tirada cayó un problema que **la spec inventaba**: el puerto WG no es «443 RUTOS / 51820 OpenWrt» sino `51234` de escucha en ambas, así que el conflicto de despliegues mixtos que describía **no existe**.)* ⇒ Y la técnica que lo resolvió: **ejecuta el código INSTALADO, no lo reimplementes para comprobarlo.** Se corrió el módulo `wgt.overlay` del propio device; reimplementar el hash aquí habría verificado la reimplementación, no el sistema. ⛔ **Y el testigo tiene que ser independiente TAMBIÉN del acto de medir.** *(Cicatriz `P9c`, 2026-07-28: el panel comprobaba si `zebra` corría buscándolo en `ps` — y la contraseña del VTY de quagga **es `zebra`**, así que **cada sonda dejaba en `ps` un proceso propio que casaba**. El módulo se veía a sí mismo: cuanto más se miraba el panel, más imposible era que arrancara.)* Una sonda que casa con su propia invocación no mide el sistema, **te mide a ti mirándolo**. Señal de alarma: un observable que **mejora cuando lo consultas** — y, en general, cualquier comprobación por `ps`/`grep` sobre una cadena que también aparece en el comando que la busca. ⛔ **Un testigo que no puede ponerse ROJO no es un testigo — y el que se pone rojo SIEMPRE mata lo que venía a proteger.** Si la señal que esperas la produce una capa **DELANTE** de lo que verificas, no está mirando tu sistema: está mirando al portero. *(Cicatriz `engine`, 2026-07-30, cazada **antes de armarla**: el `verify` del MCP de build sale por internet y el Ingress de `engine` tiene whitelist de IP ⇒ devuelve **403 pase lo que pase**. Un `expect_status:200` habría fallado **siempre** → auto- rollback → re-pin del digest anterior, que daba **404** → `engine` muerto y sin vuelta atrás: el mecanismo de seguridad era el asesino. Y `expect_status:403` es **peor**: el ingress-controller devuelve 403 **aunque el pod esté en `ImagePullBackOff`** — verde eterno sobre un servicio caído.)* ⇒ Antes de elegir el valor esperado, pregunta **quién lo produce**. Si lo produce el proxy, el firewall o el balanceador, cámbialo por uno que solo pueda salir **de dentro**. Y si no hay ninguno alcanzable, la respuesta correcta es **la palabra degradada** (`deployed-unverified`) más una comprobación a mano — no un `verify` decorativo, que es un rollback automático esperando su turno. ⇒ **Y el espejo: un testigo que se pone rojo SIN causa enseña a no creérselo.** *(Cicatriz 2026-07-31: el MCP del edge dio `failed` sin que nada estuviera roto — su smoke midió durante el hueco de `uhttpd`.)* Falla hacia el lado seguro, sí, pero **un rojo que hay que desmentir a mano entrena a ignorar los rojos**, y el siguiente será de verdad. Un sondeo que puede caer en un hueco conocido **reintenta durante una ventana**; no informa del primer intento. ⛔ **Cuando una sonda te engaña, el arreglo es OTRO OBSERVABLE — no más reintentos.** Reintentar sobre un valor que el propio sujeto escribe solo es **esperar a que la tautología se complete**. *(Cicatriz `A16`, 2026-08-01: el MCP del edge leía la versión con `head -1` —**por posición**— y `/api/settings` tenía **dos claves `version`**; se quedaba con la del **install-report, un fichero que escribe el `postinst` del propio `.ipk`**. Resultado: `installed 2.1.58 · served 2.1.58 · HTTP 200 · 4/4 enabled`, **verde entero, con el panel ejecutando 2.1.57**. El arreglo no fue reintentar: fue esperar a **la marca de fin del instalador**, que es un observable distinto del que gatea.)* ⇒ Dos formas concretas de caer, las dos vistas: - **Leer por POSICIÓN y no por NOMBRE**: `head -1`, `[0]`, «el primer campo que casa». El día que aparece un segundo valor con el mismo nombre, gana el que no querías **y nadie se entera**. - **Leer un valor que el sujeto escribe de sí mismo**: un informe de instalación, un `/health` que repite lo que le inyectaron, un `ps` que casa con tu propia invocación. - ⭐ **Usar un cliente que SIGUE REDIRECCIONES, y leer el destino como si fuera el origen.** Un guard que redirige a `/login` es indistinguible de *«no hay guard»* si tu herramienta te da el `200` del final del camino. *(Cicatriz 2026-08-13, y la cometió la orquestadora: midió el panel del hub del trabajo con `urllib.request.urlopen` —que sigue los `3xx` por defecto y no lo dice— y concluyó **«la aplicación no comprueba nada, el panel está abierto dentro del cluster»**. Era falso: contestaba `303 → /login`. Encima **tumbó un hallazgo correcto** de la sesión de ejecución —que sí había medido con `wget -S`, que enseña la traza entera— y estuvo a punto de cambiar el motivo de una decisión por uno inventado.)* ⇒ Cuando midas una autorización, **exige ver la traza**: el código de CADA salto y sus cabeceras. Y desconfía por defecto de todo cliente que «te lo pone fácil» — `urlopen`, `requests.get`, un navegador: la comodidad es exactamente lo que te esconde el peldaño que estabas midiendo. - ⭐ **Preguntar por un NOMBRE que resuelve distinto según quién pregunta.** *(Cicatriz `H12`, 2026-08-02: `harbor.manabo.org` es **Valhalla** desde el constructor y **Athena** desde los nodos que tiran. Una sonda de «¿ya está la imagen?» lanzada desde el pod habría preguntado **al registro que acababa de recibir el push** ⇒ **200 siempre**. Por eso el destino de la sonda tiene que ser un **endpoint explícito**, no el nombre.)* Si un nombre es split-horizon, **no sirve como dirección de un testigo**: solo sirve para hablar, no para comprobar. ⛔ **En un reconciliador, «lo que yo no escribo» NO es «lo que sobra».** El conjunto que **enumeras** para decidir qué conservar tiene que ser **más ancho** que el que **escribes** — reutilizar el filtro estrecho convierte *«esto no lo gestiono yo»* en *«esto se borra»*. *(Cicatriz evitada el 2026-08-02 construyendo la poda de `allowed-ips`: el peer `yomi` lo pone `wg-setup` **desde un Secret** y el API **nunca** lo escribe en la conf. Una poda de «borro todo lo que no reconozco» se lo habría llevado — que es el error de `H13` con el `lo` del nodo, y por eso hizo falta una lista `PRESERVE_PEERS` explícita.)* ⇒ Antes de escribir una poda, pregunta: **¿quién MÁS escribe aquí?** Un recurso compartido casi siempre tiene más de un dueño, y el que no ves es el que vas a romper. ⇒ Y su gemelo: **si la lectura del estado vivo FALLA, no se poda nada.** Un `wg show` que no responde devuelve **ausencia**, no **conjunto vacío** — y tratarlos igual borra la instalación entera. ⇒ ⛔ **Y esto también le pasa a Kubernetes, así que no lo des por resuelto porque lo haga el sistema.** *(Cicatriz 2026-08-02: un `kubectl delete ns karmada-system` lanzado con el plano de control revuelto por los reinicios de esa misma sesión —`connection refused` contra el apiserver a la misma hora— se llevó el objeto `Namespace` y dejó **vivos los 18 objetos de dentro**, listables y sin `deletionTimestamp`, en un namespace que ya no existía. El `namespace-controller` **enumera por discovery** lo que debe borrar; una enumeración vacía se leyó como «no queda nada», retiró el finalizador y el `Namespace` desapareció antes que su contenido.)* ⇒ ⭐ **Y lo que lo hace peligroso no es el huérfano, es que sea INVISIBLE**: casi todo se audita **por namespace**, y ese namespace ya no está. Es la cicatriz de kaniko en variante nueva — *quitar el gestor no quita lo gestionado*. ⇒ **Regla operativa: ninguna poda que dependa de ENUMERAR se ejecuta con el plano de control inestable.** Si hay que borrar algo el día que se toca etcd o un apiserver, se borra **antes** de empezar o **después** de comprobar que los apiservers responden — y la comprobación del borrado no es que el contenedor padre ya no esté: es que **los objetos ya no están**, incluidos los *cluster-scoped*. ⛔ **De un contador se deduce TRÁFICO, no PROPÓSITO. «Nadie lo usa» no es una medida: es una inferencia — y es la que justifica retirar cosas.** *(Cicatriz 2026-08-02, cazada por Xavier antes de ejecutarse: un aviso describía un `wg0` con **RX 0 y TX 56 MiB** como «un túnel muerto que nadie usa», y la orquestadora ofreció «levantarlo o quitarlo» como dos opciones equivalentes. Xavier: **«ese túnel es con el que me conecto yo a casa»** — y estaba **fuera de casa** en ese momento. Quitarlo le habría cortado el acceso.)* ⇒ ⭐ **Y el caso peor es sistemático: un camino de último recurso parece ocioso PORQUE funciona.** Solo se usa el día que hace falta, así que «sin tráfico» es su estado normal — y es justo lo que lo hace parecer muerto. Acceso de emergencia, rutas de respaldo, credenciales de rescate: todas se ven igual que la basura. ⇒ Antes de proponer retirar algo, **pregunta para qué existe a quien lo puso**. Y si el aviso lo escribió una sesión que solo vio contadores, **la propuesta de retirada no está fundada**: lo que midió es que no pasa tráfico, no que no sirva. ⛔ **Un testigo que no DISCRIMINA es peor que no tener testigo**, porque da permiso. *(El mismo `A16`: se propuso «comprobar que el PID cambió» como prueba de reemplazo — y **el respawn de procd también estrena PID**, así que habría salido verde sobre el árbol viejo. El testigo bueno era **la versión que el panel SIRVE**, que es lo único que solo puede decir el árbol que corre.)* ⇒ Antes de aceptar un testigo, pregunta: **¿qué valor tomaría si el fallo estuviera presente?** Si es el mismo, no es un testigo: es un adorno. ⇒ ⭐ **Y el caso más común de todos: una alerta construida sobre «¿responde?» no puede distinguir «el sujeto está caído» de «mi sonda está rota».** *(Cicatriz 2026-08-02, cluster `home`: dos reglas —`etcdMembersDown` y `etcdInsufficientMembers`— contaban **targets de scrape** y los llamaban «miembros». Llevaban **56 días** en rojo porque los cuatro componentes del plano de control escuchaban sus métricas en `127.0.0.1`, con los cuatro **perfectamente sanos**.)* Lo que lo demostró **no fue razonarlo, fue un banco de usar y tirar**: tres etcd de juguete, **matando 2 de 3** — y el superviviente **siguió sirviendo `/metrics`**, o sea que `up` se habría quedado en **1**. Lo que sí se movió fue el estado interno (`etcd_server_has_leader` 1→0, `peer_sent_failures` 0→16→95). ⇒ **La regla útil lee el ESTADO que el sujeto publica de su propio consenso, no si contestó al teléfono.** Y la forma de saberlo es la misma de siempre: **provoca el fallo real y mira qué serie se mueve**. Si la tuya no se mueve, tu alerta no vigila lo que crees. ⛔ **Un campo de `status` que suena a «lo que hay AHORA» puede ser «lo que había la última vez que el controlador lo tocó» — y creerse ese nombre lleva a arreglar la declaración en vez del sistema.** *(Cicatriz 2026-08-02, y la cometió la orquestadora: dedujo que Vault llevaba «29 días corriendo sin el `hostAliases`» porque `currentRevision ≠ updateRevision`, y recomendó **revertirlo en git para que la alerta se apagara sola**. Medido en el aparato: hay **12** ControllerRevisions, el pod corre la **10**, `currentRevision` apunta a la **4** y el deseado es la **12**. Con `updateStrategy: OnDelete` ese campo no describe nada real, y el pod **sí** llevaba el `hostAliases` — en su `/etc/hosts`.)* ⇒ El testigo de qué corre es **la etiqueta del propio pod** (`controller-revision-hash`), o su `/etc/hosts`: se mide en el sujeto, no en la opinión del controlador. ⇒ ⭐ **Y el corolario general, que es el peligroso: cuando una alerta compara DESEADO con ACTUAL, la salida barata es tocar el deseado.** Eso la pone verde **haciendo que la declaración mienta sobre lo que corre** — se pierde la alerta *y* la verdad de la spec, a cambio de nada. Si el actual no se puede mover (aquí: reiniciar Vault lo deja **sellado**, shamir 3/5 sin auto-unseal, y se lleva 83 `ExternalSecret`), la respuesta correcta es **la excepción escrita con fecha de caducidad**, no editar la intención hasta que cuadre. ⇒ ⛔ **Y su espejo, que engaña en la dirección que NADIE vigila: un estado de ERROR es tan pegajoso como uno verde.** De un verde heredado ya desconfiamos; un **rojo** heredado se lee como *«sigue roto»* — y eso hace perseguir un problema **ya resuelto** y «arreglar» lo que estaba bien. *(Cicatriz 2026-08-12: repuesto el token de ESO de `hermes`, **20 de 30 `ExternalSecret` seguían en `SecretSyncedError`** y la lectura obvia era «no funcionó»; se estuvo a un paso de reiniciar ESO y de reescribir la política. Lo que lo desmintió fue **la HORA del último error en el log: siete minutos de silencio**. El campo contaba el ciclo anterior — su `refreshInterval` es de **una hora**. Forzando el refresco de los treinta: **30/30 verdes**.)* ⇒ El `status` de un objeto narra **su última reconciliación**, no el presente — en los dos colores. Para saber si algo sigue roto: mira **cuándo** fue el último fallo, o **provoca** la reconciliación. Nunca leas el campo y ya. ⛔ **Cuando el riesgo es una PÉRDIDA SILENCIOSA, el testigo tiene que ser DIFFEABLE.** Una comparación que solo puede hacer un humano mirando siempre concluye *«se ve igual»* — y esa es exactamente la conclusión que el riesgo necesita para pasar. *(Cicatriz al revés, 2026-08-01, hecho BIEN: vaciando de texto las pantallas de dos paneles, el peligro era llevarse **estado** junto con los comentarios. En vez de capturas —que **no se pueden diffear**— se volcó **el texto de todas las pantallas antes y después** en los dos aparatos, con un clasificador prosa/medida: **0 medidas cambiadas**, y los únicos cambios fueron los seis textos que se querían mover.)* ⇒ Antes de una poda, un refactor o una migración, pregunta: **¿qué artefacto puedo comparar a máquina?** Si la respuesta es «ninguno», invéntalo **antes** de tocar — un volcado, un censo, un hash. Lo que no se puede diffear no se puede echar de menos. ⇒ ⭐ **Y el corolario que justifica el gasto: «COSMÉTICO» NO ES UNA CATEGORÍA DEL SISTEMA, ES UNA CATEGORÍA TUYA.** Tú decides que un cambio no toca nada; el artefacto no comparte tus categorías, y un testigo diffeable es lo único que os pone de acuerdo. *(Cicatriz 2026-08-13, y salió BIEN: una sesión documentó el porqué de una perilla con comentarios `#`… **dentro de `spec:`**, o sea que salen en el render. El gate de bytes se puso **rojo en los tres hubs vivos sin que cambiara un solo valor**. Pasaron a `{{/* */}}` y volvieron los bytes exactos. Sus palabras: «el gate se ganó el sueldo en el cambio que yo habría llamado cosmético».)* ⇒ Por eso un gate de artefacto **no se salta «porque esto solo es un comentario»**: si de verdad no cambia nada, pasa solo y no cuesta; y si cuesta, es que cambiaba algo. ⇒ ⛔ **Pero un hash de algo GENERADO solo vale si el generador es determinista.** *(Cicatriz `F12.1`, 2026-08-02: se eligió el `md5` de `ospfd.conf` como testigo de *«esto no ha cambiado»*, y el fichero lo escribe un bucle sobre `pairs()` de Lua, que **no ordena**. El hash bailaba sin que cambiara una sola línea ⇒ el testigo habría gritado en cada medida, y a la tercera nadie se lo cree.)* El arreglo no es cambiar de testigo: es **normalizar antes de comparar** (ordenar) — y entonces el diff vuelve a significar algo, que es como se cerró la desistencia: `ospfd.conf` **idéntico al byte**, ordenado. ⇒ Pregunta previa a usar un hash: **¿dos ejecuciones sin cambios dan el mismo byte?** Si no lo has comprobado, no tienes un testigo, tienes un generador de ruido. ⇒ ⛔ **Y aunque normalices: guarda el ARTEFACTO, no su huella.** Un hash dice **que** algo cambió y **nunca qué** — así que el día que se mueve estás ciego justo cuando necesitas mirar. *(Cicatriz `E9`, 2026-08-03, y es la segunda vez en dos días: el `md5` de un `ospfd.conf` se movió, la sesión pudo demostrar por reconstrucción que no era suya… pero **no pudo diffearlo**, porque había guardado el hash y no el contenido normalizado.)* Un hash cuesta 32 bytes y un fichero de configuración cuesta dos kilos: **no hay ninguna razón para guardar el resumen en vez del original.** ⛔ **Una carrera no se ESPERA: se PROVOCA.** Si el fallo depende de coincidir con un bucle, cronometrar es un sorteo — y tres «no apareció» seguidos no dicen nada. *(Cicatriz `H24`, 2026-07-31: la ventana del zombi no era de 10 s fijos, iba de **~0 a ~10 s** según dónde cayera el revoke dentro del `sleep 10` de `pq-sync`; con la rotación de Rosenpass en 131 s, acertar era un **~8%**. Tres intentos fallaron por 50 s, 3 s y 1 s.)* ⇒ Lo que lo hizo repetible: **dejar de cronometrar y disparar el intercambio**, y **calcular la fase del bucle a partir del `T0` que el propio supervisor escribe en su log** — el sistema suele publicar su reloj; úsalo en vez de adivinarlo desde fuera. ⇒ Corolario duro: **hasta que no lo hayas reproducido, no puedes cerrar nada arreglándolo.** «No apareció después del arreglo» sobre una ventana en la que tampoco habría aparecido antes **no es evidencia** — es la misma medida sin discriminación. ⛔ **Una nota de cierre tiene que nombrar QUÉ COPIA cerró.** «Arreglado en `hermes`» no dice si habla del **código**, de la **imagen** o de la **instancia que corre** — y son tres cosas distintas que se desincronizan solas. *(Cicatriz `H16`, 2026-07-30, y es la **tercera** reincidencia de `H6` — casillas rancias— tras el 26 y el 27 de julio. El modo de fallo nuevo: la casilla no se podía contradecir **leyendo los docs**; hubo que entrar en el contenedor a comparar `sha256sum` contra `git show HEAD:`.)* ⇒ Y el testigo se elige por quién lo produce: **`/health` NO sirve** — devuelve el string que le inyectó el `values`, o sea un auto-informe. Lo que decide es el `imageID` del contenedor (qué se empaquetó) y, mejor aún, **una huella que solo pueda haber escrito el camino de código nuevo** (una fila `promoted` en `pair_claims`). Un fichero dentro de una imagen prueba **qué se empaquetó, no qué corrió**. ⛔ **Y el caso más traicionero: un artefacto producido por el CÓDIGO DEFECTUOSO no prueba nada sobre el estado que ese defecto ignoraba.** Si el fallo consiste en *«no mirar X»*, entonces lo que ese código escribe sale **igual valga X lo que valga** — así que leer su salida para deducir X es leer el defecto y llamarlo dato. *(Cicatriz 2026-08-03, y la cometió la orquestadora: dedujo que una LAN estaba en la malla porque el fichero de opciones DHCP existía. Cierto con el código de hoy —que solo lo escribe para las LAN de la malla— y **falso con el que corría cuando lo midió**, que lo escribía **incondicionalmente y sin consultar el eje**. La existencia del fichero probaba justo lo contrario: que el eje se ignoraba.)* ⇒ ⭐ **Y arreglar el defecto cambia el significado de la evidencia hacia atrás.** Antes de inferir estado de un artefacto, pregunta **qué versión lo escribió y si esa versión miraba lo que quieres deducir**. Si no lo miraba, el artefacto es un testigo de la avería, no del sistema. ⇒ Generalizado: **una medida sin su procedencia no es una medida.** Falta decir *qué copia* y *cuándo*, y las dos mataron un dato el 2026-07-30: - **El árbol de trabajo NO es el repo.** Dos veces el mismo día: un default `harbor.c2et.com` reportado como bomba estaba en un WIP **sin commitear**, y «hay **4** `build.yml`» eran **3** en `origin/main` (el cuarto vivía en un worktree de otra rama, con 0 commits propios). Si afirmas sobre el repo, **mide sobre `origin/`**, no sobre lo que tienes delante. ⇒ ⛔ **Y AL REVÉS, que es el que no se espera: medir el ARTEFACTO correcto puede esconder que la COPIA que usa la gente está rota.** Son dos preguntas distintas —*«¿qué despliega ArgoCD?»* y *«¿qué le pasa a quien clona esto?»*— y **ninguna receta contesta las dos**. *(Cicatriz 2026-08-06: `pq-secret.sh` estaba en **CRLF en el worktree** y así ni arranca —`set -uo pipefail\r`—, o sea que con `core.autocrlf=true` un clon en Windows deja **todos** los bancos muertos. El censo no lo vio porque medía —bien— sobre un `git archive` en LF: **la receta correcta tapó el defecto que la rodeaba**. Se cosió con un `.gitattributes`.)* ⇒ Cuando elijas dónde medir, di **qué pregunta estás contestando**; y si la herramienta tiene una salida de emergencia (un `tr -d '\r'`, un fallback), pregúntate **qué está haciendo tolerable que no deberías tolerar**. - **Si un inventario lista dos medidas del mismo día, la hora es parte del dato.** Un robot «vivo por la mañana» y «borrado por la tarde» no es una contradicción que resolver: es un **antes y un después**, y tratarlo como conflicto te hace corregir dos veces y acertar ninguna. ⛔ **Si delegas una decisión en el operador, TIENES QUE DARLE EL GESTO. Si no, no has delegado: has cerrado la puerta.** *(Cicatriz `H23`, 2026-07-31: `I1` decidió —bien— que re-admitir a un nodo revocado es **«una autorización NUEVA del operador»**… y construyó solo la **negativa**. Medido en el pod que corre: `POST /links/pair` da **403** permanente, `approve_pair` exige `pending`, y **ninguna pantalla pinta una fila revocada** ⇒ **no hay botón que pulsar**. Hubo que tocar `status` en la base de datos. Y lo que lo vuelve grave: el procedimiento documentado de `H21` dice **«revoca los edges antes de renombrar un hub»** — seguirlo en el cliente dejaría sus sedes fuera **sin marcha atrás**.)* ⇒ Prueba barata, y hazla al diseñar, no al depurar: **para cada estado en el que tu sistema puede entrar, ¿cuál es la salida y quién la pulsa?** Un control que solo se puede **aplicar** y nunca deshacer no es un control, es una trampa. Y si el estado terminal no aparece en ninguna pantalla, para el operador **no existe** — da igual lo que diga la fila en la base de datos. ⛔ **Un identificador que además es CLAVE DE BÚSQUEDA no se puede renombrar: sepáralo antes.** Si un mismo valor da el nombre público **y** direcciona secretos, rutas o etiquetas, cambiar la cara visible **re-direcciona lo invisible**, y en silencio. *(Cicatriz 2026-07-31, cazada antes de aplicar: `hubName` gobernaba también los **paths de Vault** — con `hermes.hub` el `trimSuffix "-hub"` no recortaba nada y el hub habría ido a buscar su clave WireGuard y su **clave Rosenpass** —la que la `D100` del hub prohíbe regenerar; era `F13·D10` hasta que el 2026-08-22 se renumeró al contador del hub, precisamente porque el id chocaba con el del edge— a rutas inexistentes. La sesión partió **identidad** de **nombre público** antes de tocar nada.)* ⇒ Antes de renombrar, **escribe qué se deriva de ese valor**. Lo que dice quién eres y lo que dice dónde están tus cosas tienen que ser campos distintos, aunque hoy coincidan. Y donde el derivado tenga formato obligado (una etiqueta de kernel, un path), **que la plantilla falle al renderizar** — probado que falla, no supuesto. ⛔ **Un comentario que JUSTIFICA saltarse una puerta sobrevive al motivo, y convierte el bypass en rutina.** Es el peor sitio donde se puede pudrir una nota: el resto de la documentación rancia hace perder el tiempo; ésta **desarma un gate y encima explica por qué está bien**. *(Cicatriz 2026-07-30: el catálogo del MCP de build llevaba un CAVEAT diciendo que la suite del hub estaba obsoleta —12/28 en rojo— y que por eso «el deploy se valida con `skip_tests`, bypass **auditado**». Había dejado de ser cierto el **2026-07-24**: las 12 aserciones se reescribieron y la suite va a **90 passed**. Seis días en los que la razón ya no existía y el permiso seguía escrito.)* ⇒ Toda excusa escrita para desactivar una comprobación **nace con fecha de caducidad**: dice qué la justifica y **qué la cancelaría**. Y quien arregla la causa tiene que borrar el permiso — si no, lo que queda no es una excepción, es la nueva costumbre. ⛔ **Y su hermano, que es peor porque parece inofensivo: un PENDIENTE rancio no es ruido, es una INSTRUCCIÓN EQUIVOCADA esperando a que alguien la ejecute.** Una casilla `[ ]` no solo describe un problema: casi siempre trae **cómo arreglarlo**, y ese «cómo» está anclado al diseño de cuando se escribió. *(Cicatriz 2026-08-04: bajo una cabecera marcada ✅ sobrevivía un pendiente sobre una carrera `revoke + enroll` **que `I1` había vuelto inconstruible** —los dos `db.delete` ya no existen—, y su arreglo propuesto era *«un SELECT+DELETE atómico»*. Quien lo hubiera cogido de buena fe habría **reintroducido exactamente lo que `I1` quitó**, que era el borrado de la fila y toda la vulnerabilidad con él.)* ⇒ **Al cerrar algo, barre los pendientes que describían su arreglo**: desde ese momento describen el arreglo **equivocado**. Cerrar una decisión sin barrer su cola deja instrucciones armadas. ⛔ **`Synced` es un veredicto sobre lo que la Application MIRA — no sobre el directorio, ni sobre lo que un día gestionó.** Dos caras, las dos medidas el 2026-07-30 desmontando kaniko: - **Quitar el gestor no quita lo gestionado: lo deja HUÉRFANO.** Las tres Applications no tenían `resources-finalizer`, así que el prune se llevó **los objetos `Application`** y dejó vivos el pod del runner, su PVC, el ns `kaniko` con credenciales de push y el Deployment del image-updater. ⇒ El resultado de fiarse de la predicción habría sido **peor que no tocar nada**: procesos con credencial de escritura corriendo **sin dueño ni en git ni en ArgoCD**. Borrar es un `delete` explícito **después** del commit; el commit solo retira la gestión. - **Un fichero que la Application no incluye no existe para ArgoCD.** El appset de velero monta su directorio con `directory.include: "externalsecrets.yaml"` ⇒ la Application de backup de un cluster salía **`Synced/Healthy` sin haber mirado nunca los `Schedule`**. Quitar un namespace del YAML en git no quitó nada: el objeto vivo seguía respaldándolo. ⇒ Antes de creerte un `Synced`, pregunta **qué ficheros entran** en esa Application y **qué recursos siguen vivos sin ella**. Y la comprobación de un desmontaje no es «la Application ya no está»: es **que los objetos ya no están**. ⛔ **Y borrando DOCUMENTOS pasa lo mismo, con una moneda peor: antes de retirar un documento de estado, cuenta lo que está ABIERTO en él — no sus ids, no sus líneas.** Ids y líneas miden **tamaño**; las casillas `[ ]` miden **obligaciones**, y son lo único que no se puede reconstruir después. *(Cicatriz 2026-08-03, y la cazó la propia sesión al aplicar su propuesta, no la propuesta: al retirar `BACKLOG-HERMES.md` —826 líneas, con el invariante de ids comprobado y cuadrado— quedaban **cuatro ítems abiertos de la red del trabajo** que el borrado se habría llevado **en silencio**: excluir una IP de la pool de MetalLB, identificar físicamente al okupa de un puerto de switch, renombrar un address-object del firewall y un puerto de destino. Nada de eso tenía id; eran casillas.)* ⇒ El censo previo a un borrado tiene **dos columnas**: qué ids se van *(y a dónde)* **y qué queda sin hacer** *(y a qué backlog se muda)*. La segunda es la que salva trabajo; la primera solo salva referencias. ⇒ Y el corolario para quien busca esas referencias: **búscalas en TODO el árbol, no solo en los `.md`**. En esa misma retirada apareció un puntero al documento **en un comentario de código** (`lib/wgt/config.lua`), que es donde un enlace muerto sobrevive más tiempo porque nadie relee los comentarios. ⛔ **La réplica PROPAGA la decisión, no la juzga: una réplica fiel de una decisión mala la hace global.** Mejorar el mecanismo que distribuye, sobre un procedimiento que decide mal, **no arregla — amplifica**. *(Cicatriz `A20`, 2026-08-01: la `/32` que un hub se inventó al resolver una colisión no se quedó ahí — el edge la adoptó (`A17`), la re-declaró al otro hub (`A18`), que la aceptó, y el nombre del router acabó resolviendo a la dirección equivocada **por los dos caminos**. Cuanto mejor funcionaba la réplica, más lejos llegaba el error.)* ⇒ Antes de mejorar una distribución, pregunta **quién decide lo que se distribuye**. Si el generador puede equivocarse, la respuesta no es replicar mejor: es **quitarle la decisión** — el arreglo fue que el hub dejara de asignar, no que asignara con más cuidado. ⛔ **Cuando un estado vive en DOS sitios, el que sobrevive es el que miente.** No es un caso: es la forma en que fallan los sistemas con estado duplicado, y apareció **tres veces el 2026-07-28**. - **Un validador de caché guardado APARTE del dato sobrevive al dato.** El reconciliador PQ guardaba el `ETag` de la pubkey del peer en `data/mesh.json`, y el fichero que certifica — `data/pq/peers/.pk`— en otro sitio. Se borró el fichero y **no volvió nunca**: 8 vueltas pidiendo con `If-None-Match`, el hub contestando `304`, y el enlace `blocked` **para siempre**, con todos los indicadores en verde. ⇒ Un validador tiene que invalidarse cuando **desaparece lo que valida**: guárdalo junto al dato, o compruébalo contra su existencia antes de usarlo. - **El kernel emparejado y el panel a cero.** Tras desmontar un edge «a cero», `data/mesh.json` quedó en `{"hubs":{}}` mientras `wgtrans` conservaba **dos peers con handshake fresco y PSK puesta**. Desde fuera, vivo; desde su propio panel, virgen — y sin links en su estado, el reconciliador no levanta quagga, así que **no hay OSPF y nadie lo dice**. - **El `lo` del nodo acumula direcciones que ningún manifiesto declara** (§ hub): el chart las añade y no las quita, así que un nodo lleva encima todo valor con el que se desplegó **alguna vez**, y ArgoCD dice `Synced/Healthy`. ⇒ **Regla**: si un dato tiene dos copias, di **cuál manda** y haz que la otra **no pueda sobrevivirla**. Y para probarlo no basta con razonar que ya no puede pasar: hay que **volver a ponerse en el estado roto** y ver que se recupera — es lo que hizo el arreglo del `ETag`, y por eso se sabe que sirve. ⛔ **UN HECHO MEDIDO SOBRE UN COMPONENTE, MÁS UN SUPUESTO NO MEDIDO SOBRE OTRO, PRODUCE UNA CONCLUSIÓN FALSA CON ASPECTO DE MEDIDA.** Es el fallo de la **juntura**, y hoy no lo caza ningún otro raíl: `§5` dice *no inventes lo no medido* y arriba está *verifica el resultado, no la intención* — pero aquí **nadie inventa nada y todo lo afirmado está medido**. Lo que falla es **unirlo**. *(Cicatriz 2026-08-28, y es la única del día que llegó a mover el orden de trabajo del usuario: un aviso reclamaba un gesto urgente sobre la BIOS de un servidor —«la carga está armada»— y desplazó cuatro asuntos. Un frente había medido **la conducta del SERVICIO** —un host declarado se sirve sin menú, y el arranque por red instala solo a los 30 s— **y era cierto**. Nadie midió la **precondición**, que es de **otro sujeto**: el orden de arranque de la **MÁQUINA**. Lo refutó el usuario en una frase: para arrancar por red **hay que pulsar una tecla**; el disco ya era el primero. La conclusión compuesta había viajado con fecha, contra-medidas y hasta un `User-Agent` —**todos los signos externos de una medida**— y **tres registros la aceptaron sin pedirle la precondición**.)* ⇒ **Regla**: cuando una conclusión **cruza dos sujetos** —un servicio y una máquina, un repo y un cluster, un motor y el hierro que lo arranca—, **cada sujeto necesita SU PROPIA medida**. Heredar la solidez de uno para el otro es **cómo se fabrica una alarma falsa con pinta de dato**. ⇒ ⭐ **Y su reverso, que es el mismo error con el signo cambiado: CORREGIR DE MÁS.** Pasar de *«la bomba está armada»* a *«aquí no hay nada»* vuelve a tomar por propiedad **del mecanismo** lo que era propiedad de **una máquina concreta**: que el disco arranque primero es cierto **de esos servidores**, no del sistema — el orden de arranque **se pone a mano, por máquina**. ⇒ Al refutar, la salida no es *«refutado»* a secas: es **«precondición identificada, compruébese por máquina»**. *(Lo señalaron dos frentes por separado, y uno de ellos ya se había pasado de frenada al corregir.)* ⛔ **UN TESTIGO SIN FUENTE NOMBRADA NO ES VERIFICABLE AUNQUE SEA CIERTO — y lo caza quien intenta REPRODUCIRLO, no quien duda de él.** El fichero, el endpoint o el comando exacto va **DENTRO del testigo**, no en la cabeza de quien lo midió. *(Dos frentes independientes el 2026-08-28, con casos distintos. Uno: dos sesiones afirmaban cosas **opuestas** sobre «el log del motor» — y no era un objeto, eran **dos**, uno por stdout **sin `User-Agent`** y otro dentro del mismo pod en formato combinado; **ninguna se equivocaba sobre su fuente, faltaba nombrarla**, y lo destapó que una intentara reproducir el testigo de la otra y no pudiera.)* ⇒ ⭐ Y el patrón que lo generaliza, con **cuatro instancias en tres frentes el mismo día**, todas de la misma forma: **el instrumento no decía de qué estaba hablando.** El log no decía **quién**; un `uptime` no decía **cuándo** —se leyó una máquina catorce minutos antes de que terminara su reinstalación y su «estado estable» eran dos días de otra cosa—; un `push` no dice **de quién** es lo que publica; y un listado de sesiones no dice **qué rol** tiene cada una. **Ninguna fue un error de medida**: las cuatro son **un dato bueno contestando una pregunta que no era la suya.** ⛔ **Y un ENCUADRE FALSO no sobrevive a los datos que lo contradicen: LOS RECLUTA.** No se cae solo cuando aparece la evidencia en contra — la absorbe como confirmación, porque **los mismos bytes dan veredicto opuesto según una precondición que nadie ha medido**. *(Cicatriz 2026-08-28: los dos hechos que más deberían haber tumbado el aviso de la BIOS **se leyeron como que lo confirmaban**.)* ⇒ Por eso, cuando un encuadre cae, **corregir la frase que lo dijo NO es corregirlo: hay que CENSAR dónde ha viajado** — y **empezando por los TÍTULOS**, que es lo único que enseña un listado compacto y la superficie donde los encuadres caídos se acumulan. ## 2. El dato crudo, no el cocinado El backend **mide y traduce a atributos**; el consumidor (UI, componente, otro servicio) **interpreta y pinta**. No cocines el estado antes de tiempo ni lo re-derives en la capa de presentación (si no, dos consumidores divergen). - Apareció **cuatro veces** en la convergencia UI: la API servía `wg_status` (up/idle/down) y el badge necesitaba `wg_handshake_age` para su contador vivo; igual con IP, OSPF, y el `state` de los nombres de malla. - **Edad, no timestamp**, en routers sin RTC fiable. `0` es *ausencia de medida*, no "época". - El estado se publica una vez (backend, testeable) y el componente sólo lo colorea. ## 3. Mutation testing: ataca el CABLEADO, no las funciones puras Las funciones puras se diseñaron junto a sus tests → caer es inevitable, no prueba nada. Los bugs viven en las **costuras donde el dato se ensambla**. Testea *qué* se ejecuta/ensambla, no sólo *cómo* se parsea. - F10.3: el parser de rutas tenía 8 aserciones verdes y el panel salía **mudo** — el bug estaba en qué `proto` se consultaba. El test bueno usa un doble que registra el comando ejecutado. - Dos listas de la misma forma (`to_publish`/`published`): intercambiarlas no rompe ninguna función pura, sólo **invierte la verdad** → stubea y afirma sobre el resultado con un estado conocido. - `pq_psk = True` fijo pasaba la suite entera → habría afirmado protección PQ inexistente. ⛔ **Cuando un cambio de configuración pretende cambiar un COMPORTAMIENTO, el testigo es el ARTEFACTO GENERADO: guardado ANTES, comparado DESPUÉS, y el veredicto es su `diff` por caso.** **Un caso cuyo diff sale vacío no está construido, aunque no dé error: es un campo-señuelo.** *(Cicatriz del motor de aprovisionamiento, cosecha 2026-08-15: `day1.mode` — dos modos producían **el mismo artefacto**; y así llegó `day0.luks` a existir declarado y sin consumir, sin que nada se pusiera rojo.)* ⇒ Es el testigo independiente de §1 aplicado a la configuración: sin el artefacto de antes, «cambié la perilla y no dio error» no distingue **construido** de **decorativo**. ⛔ **Y el modo de fallo más traicionero: los FIXTURES de la suite son más completos que la realidad.** Un test que **construye él mismo el argumento** no puede cazar a un llamante que lo construye mal, y la suite sale verde sobre un cableado roto. Dos veces en la misma semana (2026-07-31): - `GET /links` —el endpoint del panel— llamaba a `LNK.status` con un `cfg` **declarado y sin asignar**, así que el peldaño de identidad llegaba **vacío**. Los tests pasaban **porque le pasan `cfg` a mano**. - Un mutante que derivaba el sufijo de una LAN del dominio del router —la invención que `F11·D8` del edge prohíbe— **no rompió ni una aserción**, porque sus tests **no pasaban registro de router**. ⇒ Pregunta por cada test que pasa: **¿de dónde sale ese argumento en producción?** Si lo fabrica el test, ahí no hay cobertura — hay una simulación de que la hay. El testigo tiene que **entrar por donde entra el usuario**. ⛔ **El NÚMERO de rojos es la señal, no el rojo.** Una mutación que da **menos** rojos de los que esperabas acusa a **tu test**, no a la mutación. *(Cicatriz F11.11b, 2026-07-27: el test de cableado recorría `app.routes` **en plano**, pero en esa versión de FastAPI los routers incluidos van **anidados** → el recorrido veía 8 rutas y **ninguna** `/api/…`, así que **pasaba con el endpoint abierto de par en par**. Se delató porque la mutación dio **1 rojo donde tenían que ser 2**. Ahora las rutas se descubren por el esquema OpenAPI.)* ⇒ **Escribe cuántos rojos esperas ANTES de mutar.** Sin esa predicción, un rojo de menos pasa por éxito y el placebo sobrevive. ⇒ **Y para que el conteo SEA posible, las aserciones tienen que ser a prueba de `nil`.** *(F11.12, 2026-07-27: tal cual estaban, la primera mutación **reventaba el bloque entero** y escondía cuántas aserciones habría tumbado — el número dejaba de ser medible justo cuando lo necesitabas.)* Una suite que explota en vez de suspender no te deja contar rojos. ⇒ **Y un 0 rojos puede significar «la mutación NO se aplicó».** Distínguelo, o una mutación que no casó se lee como *«el test no cubre esto»* y sale a la basura una comprobación buena. *(2026-07-27: una quinta mutación dio 0 donde se esperaban 2 — **el arnés avisó de que el `sed` no había casado**, así que el cero quedó explicado en vez de pasar por bueno.)* El arnés de mutación tiene que **afirmar que el fichero cambió** antes de correr la suite. ⇒ ⭐ **Y una forma concreta de que no case, que no es un `sed` mal escrito: mutar donde el valor se HEREDA en vez de donde se DECLARA.** *(2026-08-13: una mutación apuntaba a `values-hermes.yaml` para tocar la clase de Ingress — y esa instancia **no la declara, la toma del default** ⇒ no había nada que mutar. El arnés dijo «mutación no aplicada» en vez de un `0 rojos` que se habría leído como «esto no está cubierto», y la mutación se reapuntó al default, donde sí enrojece las tres.)* ⇒ Antes de mutar un valor, pregunta **dónde está escrito de verdad** para la instancia que vas a medir. ⇒ **Y aun cambiando el fichero, el cero puede ser del CONTADOR.** *(2026-07-28, hub `I6`: la mutación quitaba un `{% if %}` y dejaba su `{% endif %}` → la plantilla no compilaba, la suite moría en el fixture y salían **31 `errors` y 0 `failed`**; el arnés leía solo `N failed` ⇒ **0 rojos**, indistinguible de «este test no cubre nada», y por poco se tira una comprobación buena.)* Dos consecuencias: **cuenta los `errors`** —una suite que explota no es una suite que aprueba (es la misma familia que el `nil` de arriba, un piso más abajo)— y **muta a algo sintácticamente válido**: una mutación que impide arrancar no mide el sistema, mide el arnés. ⇒ Y **no mutes a un valor que tu test ya espera.** *(Misma sesión: la mutación puso `10.99.0.2` a pelo… que era exactamente el valor contra el que afirmaba el test → **verde por coincidencia**, con el número de rojos acertado y la identidad equivocada. Con `10.99.0.77` salieron 3.)* Muta a algo que **no pueda ocurrir**, no a lo que el sistema usa de verdad. ⛔ **Y el propio ARNÉS es código sin tests — y falla hacia el VERDE.** Todo lo de arriba supone que la comprobación dice lo que crees; cuando no, el error es asimétrico, porque en un arnés el camino que no se ejecuta suele ser el que declara `ok`. *(Cicatriz 2026-08-05, construyendo el gate de `R14`, y lo cazó la mutación —no el gate en verde— que es la única razón por la que se sabe:* - ***casar por SUBCADENA y no por LÍNEA***: `grep -q 'port: 444'` salía **verde contra `port: 4440`**, o sea que la guarda del puerto decía que sí a un valor que no era. Es el `head -1` de `A16` en otra sintaxis: *leer por posición o por trozo, nunca por identidad*. ⇒ ⭐ **Y su forma PEOR, medida el 2026-08-13: cuando las dos cadenas son valores LEGÍTIMOS del sistema, la subcadena engaña en las DOS direcciones.** `nginx-inet` **contiene** `nginx`, y las dos son clases de Ingress reales de instancias distintas ⇒ una guarda sin ancla `$` decía `ok [hermes] … emiten nginx` **sobre un render que ponía `nginx-inet`**. Aquí el falso positivo no es ruido: es *el valor de otro hub*. ⇒ Ancla siempre, y si puedes **compara la cadena exacta en vez de buscarla** — el `pytest` de esa misma sesión aguantó justo por eso. - ***`cmd | grep -q` bajo `set -o pipefail`***: `grep -q` sale al primer acierto y cierra la tubería ⇒ **SIGPIPE al productor** ⇒ la tubería devuelve distinto de cero ⇒ **rojos intermitentes**. Y lo grave no fue el caso nuevo: estaba **latente en cinco comprobaciones preexistentes cuyo «ok» es la rama `else`** — o sea que llevaban tiempo pudiendo aprobar por el motivo equivocado.*)* ⇒ Dos preguntas antes de fiarte de una comprobación, y las dos se contestan leyéndola: **¿casa por línea o por subcadena?** y **¿cuál de mis dos ramas es el `ok`?** Si el `ok` es el `else`, cualquier error en la condición **pasa por éxito**. ⭐ Y el corolario que lo hace barato: **muta también el arnés**, no solo el sujeto — una guarda que no puede ponerse roja es tan decorativa como un testigo que no discrimina (§1), y aquí encima *da permiso a todo el gate*. ⇒ ⛔ **Y dos formas más de que el arnés mienta, las dos medidas el 2026-08-17 construyendo `I11.2`:** - ⭐ **Un `cmp` prueba que el fichero CAMBIÓ; nunca que cambió a LO QUE QUERÍAS.** Es la guarda que §3 ya prescribe —*afirmar que el fichero cambió antes de correr la suite*— y **no basta**: dos mutaciones de esa sesión dieron **0 rojos pasando la guarda**, o sea indistinguibles de *«esto no está cubierto»* cuando lo que pasaba es que el `sed` había casado **en otro sitio**. ⇒ La guarda completa no es *«¿cambió?»* sino **«¿está el texto nuevo, y ha desaparecido el viejo?»** — y para una mutación que debe ser única, **cuántas veces**. - ⛔ **Una función del arnés usada ANTES de definirse no falla: sale `command not found`, y eso no para el script ni cuenta como `FAIL`.** *(Misma sesión: un helper `quiere_linea` definido 300 líneas después de su primer uso ⇒ la cara **positiva** de esas comprobaciones no se ejecutaba y **nadie se enteraba**.)* Es el *«¿cuál de mis dos ramas es el `ok`?»* un piso más abajo: aquí no se ejecutaba **ninguna** de las dos. ⇒ Un banco en shell necesita **suelo de comprobaciones EJECUTADAS**, no solo de fallos: si esperas 40 y corren 33, el arnés está roto aunque salga verde. ⭐ **Y lo que lo cazó fue la predicción por NOMBRE**: el conteo dio **+7 donde se predijeron +8**. Con la predicción hecha solo con el número, un 7 razonable habría pasado por bueno. ⛔ **Antes de leer un solo conteo, comprueba que la BASE está VERDE.** Un banco cuya línea base ya suspende **no mide nada**: los rojos rancios viajan dentro de cada mutación y —esto es lo que nadie espera— **una mutación puede TAPAR uno**, así que el número sube o baja por motivos que no tienen que ver con lo que mutaste. *(Cicatriz `H19`, 2026-08-05: el banco de `pq-sync.sh` llevaba **ocho días** con la base en `17 ok / 4 fallos`, porque `62707e1` retiró del chart la semilla PQ legacy —tocó **cinco ficheros y no tocó el banco**— y cuatro casos quedaron afirmando sobre algo que ya no existe. Con esa base, los «más rojos de los previstos» **no eran una cascada**: N2 hacía pasar un rancio y N4 otro. Y el aviso que lo describía prescribía **re-derivar los conteos**, o sea maquillar la avería — §6, *un pendiente rancio trae el arreglo equivocado*.)* ⇒ ⛔ **Y predice por NOMBRE, no por número: un conteo es un hash con pérdida de la verdad.** *(Misma cicatriz, y es la mitad que asusta: `N1` era la **única** mutación en verde del banco… y lo era **por coincidencia**. Sus dos rojos eran dos rancios, y **las dos aserciones que esa mutación existe para romper salían VERDES** — el aviso colado por stdout subía el conjunto de 1 a 2, justo lo que se esperaba. El número acertaba y la identidad estaba entera equivocada: no eran 4 de 5 mal, eran **5 de 5**.)* Escribe **qué aserción, por su nombre**, debe ponerse roja y por qué; el número sale de esa lista, nunca al revés. ⇒ ⛔ **Y su generalización, que es la que hace daño: si en un fichero conviven DOS FAMILIAS de comprobación, el conteo global no es comparable con nada.** No es que el número mienta un poco: es que **suma peras y manzanas**, así que una predicción hecha sobre una familia se caree contra el total y salga un abismo. *(Cicatriz 2026-08-06, y salió bien **porque la predicción iba por nombre**: al coser seis comprobaciones de `guards.sh` se esperaban **6 `ok`** y salieron **37**. Los 31 de sobra eran `quiere_fallo` —otra familia: lee `stderr` y afirma en positivo—. Con la predicción hecha solo con el número, la conclusión habría sido «me faltan 31» y se habrían «arreglado» **31 comprobaciones sanas**.)* ⇒ Antes de comparar dos conteos, pregunta: **¿cuentan lo mismo?** Y si el fichero mezcla familias, el testigo no es el total — es **el subconjunto nombrado**. ⇒ Y el corolario que cierra el círculo: **si nada CORRE el banco, esto no es mala suerte, es inevitable.** Los cuatro bancos de ese chart se lanzan a mano, y por eso se pudrió ocho días sin que nadie se enterara. **Un banco sin gate no es una comprobación: es documentación ejecutable, y caduca.** ⇒ ⛔ **Y una base roja no solo falsea los conteos: ESCONDE MUTACIONES MUERTAS.** El banco se corta antes de llegar a mutar, así que una mutación que dejó de casar **nunca llega a quejarse** — no sale como «0 rojos», no sale de ninguna forma. *(Cicatriz 2026-08-06, y apareció **al poner la base en verde**, no antes: la mutación 4 de `pq-secret.sh` llevaba desde `R14` sin casar, porque `R14` movió los volúmenes a `_helpers.tpl` y en `deployment.yaml` ya no queda ningún `items:`. Dos días de censo no la vieron porque los dos rojos de la base la tapaban.)* ⇒ Arreglar la base no es el final del trabajo: **es cuando empieza a haber datos**. ## 4. Ejercita el flujo real (abre el navegador, toca el device) Tests verdes ≠ funciona. Conduce el flujo de punta a punta en el medio real. - El «Editar» de la br-lan llevaba **roto desde F9.2** (promesa muerta en silencio); sin abrir el navegador, F11.0/F11.1 se habrían dado por buenas siendo **inaccesibles**. - El PSK PQ, el `.ipk`, el DNS: validados en el router de campo real, no en mock. ⛔ **Sondear una escritura ES escribir.** Un cuerpo malformado **no** es una sonda segura: una API cuyos campos son todos opcionales lo acepta encantada como *«pon todo por defecto»*. *(Cicatriz 2026-07-26: comprobar si `PUT /api/config/global` pedía credencial con `{"__probe__":1}` **vació la Global Config** del hub y disparó el `write_zones()` — el hub dejó de servir sus tres estáticos durante ~45 s.)* Sondea con un método que **no pueda mutar** (`OPTIONS`, un id inexistente), o contra una copia. Y si la lías, **restaura desde el `GET` de hace un segundo y cuéntalo**, que es lo que hizo esa sesión: 45 s, verificado, y la única huella un contador de versión. **Y por el MISMO CAMINO que el usuario.** Un verde obtenido por otra ruta no dice nada de lo que él ve. *(Cicatriz F11.7, 2026-07-26: «Guardar cambios» no guardaba… y sí guardaba. `apply_default` hacía `ifup lan` incondicional, y la br-lan **es la interfaz por la que se administra el panel** → el `ifup` se llevaba el socket de **su propia respuesta**. El backend aplicaba y persistía, y se quedaba sin poder contestar; en el navegador, `r.json()` reventaba y la promesa del handler moría sin dueño. Silencio absoluto.)* - 🔑 **Instrumento de diagnóstico: la MISMA petición por DOS caminos.** Fue lo que lo destapó — por la **LAN** (la interfaz editada): `RESET` a 0,78 s; por la **WAN**: `201` en 3,5 s. Misma petición, mismo device, veredictos opuestos. Cuando el canal de gestión **es** lo que estás tocando, hace falta un segundo canal para poder verlo. - ⚠️ **Corolario incómodo**: el probe de UI daba **verde sobre la versión rota**, porque iba por la WAN. Una herramienta de prueba que no usa el camino del usuario **certifica lo que no es**. - Familia: `restart_frr` que se lleva `zebra`/`ospfd` (F9.2), el `restart` de uhttpd que tarda un minuto en devolver las rutas (U3.3a), y la regla de oro de `backlog-ejecucion.md` (capa `workflows/`): **lo que no se puede perder es el acceso de administración**. Toda operación que reconfigura el canal por el que te la pidieron es de esta familia — y **no puede confirmarse por ese canal**. ## 5. No inventes estados ni fallos que no has medido Honestidad de datos. Si no puedes afirmar algo, dilo ("sin dato"), no lo pintes de verde ni de rojo. No afirmes en verde algo que **dejó de ser cierto**. - La escalera de badges degrada a **"bloqueado" (gris)**, no a rojo: no inventa un fallo, dice "este dato no puede ser válido si el de abajo cae". - "Publicado con otra IP" → **pendiente**, no verde (el hub sirve algo que ya no es verdad). - `rechazado` no aplica en el hub (su rechazo es síncrono 400/409, no deja estado) → **no se inventó** el caso por simetría. ## 6. No des por hecho; mira la salida/estado real — incluida la premisa del prompt El plan escrito puede partir de una foto obsoleta o de otro entorno. ⛔ **«PARA y reporta» ESTABA A MEDIAS: no decía A QUIÉN.** Y el hueco costó dos veces el mismo día, por los dos lados, en dos frentes distintos. **(a) Un hallazgo que invalida una decisión va a quien la LLEVA, no sólo a quien la ejecuta.** Contestar a quien pregunta **no es reportar**. Son dos destinatarios y dos contenidos: el ejecutor recibe **qué hacer ahora**; quien decidió recibe **que su decisión ya no se sostiene**. *(Cicatriz 2026-08-30: una sesión de ejecución pregunta si puede quitar un campo de una plantilla para implantar una decisión. La medida dice que **ese campo es obligatorio en el instalador ⇒ quitarlo rompe TODAS las instalaciones**, en la validación, antes de tocar el disco. Se le contesta **a ella y sólo a ella** — pero eso **invalida media decisión**, y la decisión la llevaba su orquestadora, que **siguió planificando horas sobre una premisa ya sabida falsa**. Salió porque el usuario preguntó «¿esto se lo reportaste a la orquestadora?».)* ⇒ Y por eso **un fallo de MÉTODO se le dice a la superadmin** *(Xavier, el mismo día: «cada cosa a su ámbito»)*: quien lleva el método es el destinatario, igual que quien lleva la decisión. **(b) Cuando otro frente te reporta algo y lo arreglas, EL ACUSE ES PARTE DEL ARREGLO.** Un arreglo silencioso **no es más barato: es más caro**, porque el otro frente **sigue gastando en vigilar algo ya hecho**. *(Cicatriz del mismo día: un frente reporta que una plantilla de ejemplo enseñaba una capa contra una decisión del usuario, y cede el sujeto. Se arregla y se empuja… y no se avisa. El que lo reportó se queda con su aviso abierto, persiguiendo lo que ya estaba resuelto.)* ⇒ La entrega termina en **el que reportó**, no en el `push`. **(c) UN AVISO QUE SE REPITE EN UN RELEVO SE RE-MIDE, NO SE REENVÍA.** El relevo es **justo el momento en que el estado cambió y quien informa no estaba delante**: entre que avisaste y llega el sucesor, alguien pudo arreglarlo. Reenviar el aviso viejo **manda a la nueva a perseguir algo ya hecho** —y peor, **con la autoridad de venir de quien lo descubrió**. *(Cicatriz 2026-08-30, **dos veces el mismo día y en direcciones opuestas**: un frente avisa al relevo de que cierta decisión seguía apoyada en una premisa falsa… **y ya estaba corregida y sustituida**, por él mismo, la víspera. Y la superadmin repite ese mismo aviso al día siguiente **sin volver a mirar**. Las dos veces lo cazó la sesión nueva, que era la única que **no daba el estado por sabido**.)* ⇒ ⭐ **Y las tres de este bloque son la misma familia con el fallo movido de sitio**: **(a)** falla **a quién** · **(b)** falla **que no se dijo** · **(c)** falla **cuándo**. En las tres **el dato es correcto** — lo que está mal es **a quién, o desde cuándo, se refiere**. ⛔⛔ **LO QUE SE MIDE SE ESCRIBE EN LA CAPA DEL QUE EJECUTA, NO EN LA DEL QUE DECIDE — y por eso es correcto, está escrito, y quien decide no puede verlo.** Evidencias, scripts de banco, mensajes de commit, comentarios dentro de un rol: **todos son sitios donde el trabajo cae de forma natural** y **ninguno se lee para decidir**. *(Medido 2026-08-31: **tres instancias en una sola jornada**, en el mismo frente y todas ciertas —un sujeto de prueba documentado en un directorio de evidencias y un script de banco · una deuda escrita en el mensaje de un commit · una versión de herramienta medida cinco días antes, en un aviso, un comentario del rol y la cabecera de una comprobación—. **Las tres las destapó otra sesión leyendo por casualidad. Ninguna la cazó un gate.** Y en las tres, quien decidía llegó a proponer trabajo ya hecho o a operar con un riesgo que no existía.)* ⇒ **No es descuido de nadie**: las cuatro capas están pensadas **para el que ejecuta**. Falta el gesto que las cruza. ⇒ **Al medir algo que cambia lo que otro decidiría, la pregunta no es «¿lo he escrito?» sino «¿lo va a VER quien decide?»** — y el sitio donde cae solo casi nunca es ése. ⛔ **Y un careo por FECHAS no lo caza**: eso detecta *«la capa va por detrás»*; esto es **«el dato nunca entró en ninguna capa»**. Son dos enfermedades y la fecha sólo ve una. ⛔ **UN INSTRUMENTO QUE NO PUEDE VER EL ESTADO MALO DEVUELVE LO MISMO EN LOS DOS ESTADOS — Y CONTESTA CON APLOMO.** No se equivoca: **no puede distinguir**, y su salida no lo dice. *(Cicatriz 2026-08-31: `clevis luks list` **no puede desmentir** la existencia de una ranura de contraseña — devuelve `1: tpm2` **con una ranura y con dos**. Se comprobaba justamente si esa frase seguía abriendo el disco. La respuesta la dio otro instrumento, `luksDump`, que **sí enumera todas las ranuras**.)* ⇒ Es la cara consultiva de *«un gate que no puede enrojecer»*: allí el veredicto no puede ser malo; aquí **la pregunta no puede tener la respuesta que buscas**. ⇒ **Antes de creer una respuesta tranquilizadora, pregúntate si ese comando PODRÍA haber dicho lo contrario** — y si no, cambia de instrumento antes de cambiar de conclusión. ⇒ ⭐ Y para **datar** un artefacto en una máquina: **el `mtime` es lo que alguien decidió que pusiera; el `ctime` es cuándo pasó** *(un `useradd -m` copia `/etc/skel` **conservando fechas**, así que un home puede llevar una fecha meses anterior a la máquina)*. ⛔ Y la zanja obvia —carear el `stat` del directorio contra el de su contenido— **no separa las dos hipótesis**: las mueve las dos. ⛔ **UN DOCUMENTO PUEDE ACERTAR EL EFECTO Y EQUIVOCARSE DE COMPONENTE — y entonces manda a quien lo lea a depurar el sitio equivocado.** El síntoma correcto, la causa correcta y el arreglo correcto no salvan a un texto que nombra mal **de quién es el fallo**. *(Cicatriz 2026-08-25→31: un aviso decía «el instalador no baja el seed» y nombraba a **subiquity**. Quien pide las cuatro rutas y **valida la cadena TLS** es **`cloud-init`**; subiquity **consume** lo que cloud-init ya le entregó. El aviso era **útil y accionable durante seis días** —el síntoma que describía es el que se ve— y aun así habría mandado a depurar el componente equivocado. Lo escribió un frente, **lo heredó otro al citarlo**, y lo corrigió el segundo al medirlo.)* ⇒ Es la familia de **el instrumento que no dice de qué habla**, con una arista nueva: aquí **el documento no dice de QUIÉN**. Y muerde el doble cuando el texto acaba alimentando **una guía externa**, donde la diferencia entre *«lo vimos fallar»* y *«falla este componente»* es la credibilidad entera. ⇒ ⭐ **Y la lección de instrumento**: un aviso que **se ha editado varias veces** no está por eso al día. Puede haberse actualizado **por otro sitio** y conservar el error viejo **en la parte que nadie volvió a leer**. ⛔ Por eso **un careo por FECHAS no basta**: caza el corpus que se queda atrás, **no el que se actualiza y conserva un error**. Son **dos enfermedades**, y la fecha sólo ve una. ⭐ **La familia, que es lo que hace que se retengan las dos**: son primas de las cicatrices del instrumento sin sujeto —el log sin *quién*, el `uptime` sin *cuándo*, el `push` sin *de quién*—, pero **con el sujeto movido de sitio**. Allí el dato no decía de qué hablaba; aquí **el dato es correcto y llega al destinatario equivocado**. En los dos casos el fallo no está en el dato: está en **a qué o a quién se refiere**. ⓘ Y la instancia que las valida: la regla (a) **se formuló en un mensaje a un par en vez de a quien lleva el método**. La regla de enrutado, mal enrutada, por quien acababa de escribirla. - F10.3 en FRR: el hub tiene **cero rutas OSPF en la FIB** (las conectadas ganan) → la fuente es la RIB de FRR, no `ip route`. Copiar el filtro del edge habría dado tabla vacía con todo OK. - Los puertos del `br-lan` no salen de `network.br_lan.ports` (ese router usa sección UCI **anónima**) → se miden del kernel (`brif`). - Higiene de git del repo de GitOps: la premisa ("la doc no está en el remoto") era **falsa**; un rebase literal habría degradado doc buena. Verifica antes de reescribir. **Una decisión ya cerrada en la ficha es una premisa: contradecirla es PARAR y reportar, nunca escribir una segunda «decisión cerrada» encima.** Cicatriz (Hermes F11.3, 2026-07-26): la ficha de la Fase 11 decía *"FQDN = hostname + `dns_suffix`"* y *"zona plana, **sin namespacing**"*; ocho días después la sesión de ejecución escribió en el MISMO fichero su propia «DECISIÓN CERRADA» con lo contrario (`..`) y sin señalar el conflicto. Lo peligroso es que **su motivo era correcto dentro del mecanismo que ella misma acababa de elegir** (reenvío de un solo dominio) — una decisión que se auto-justifica y nunca sube al usuario. Señal de alarma: *"esto que voy a cerrar, ¿lo decidió el usuario o lo estoy decidiendo yo porque me conviene al mecanismo?"*. Si es lo segundo, va al resumen como pregunta, no al código como hecho. ⛔ **La corrección va DONDE ESTABA EL ERROR, no debajo — y un documento «ya corregido» con el artefacto viejo intacto es PEOR que uno sin corregir**, porque la enmienda hace creer que está resuelto. *(Cicatriz 2026-08-03, y es `A1` en versión nueva: `dns-malla.md` corrigió el 2026-08-01 su modelo de resolución en un bloque «⛔ CORREGIDO»… y **dejó vivo el diagrama de arriba**, que dibujaba la cadena vieja. Nadie lee el párrafo de enmienda: se lee **el diagrama**. Dos días después ese diagrama era lo que Xavier tenía en la cabeza al describir el diseño, lo que la orquestadora escribió como **premisa autoritativa** de un encargo, y lo que obligó a la sesión receptora a **parar**. El modelo derogado sobrevivió a su propia derogación y volvió a entrar por la puerta grande.)* ⇒ Regla: **enmendar es SUSTITUIR.** Si lo que estaba mal era un diagrama, se corrige el diagrama; si era una tabla, la tabla. Un párrafo debajo que dice *«esto de arriba ya no vale»* deja el error en el sitio donde la gente mira, y encima le pone un sello de revisado. ⇒ Y el corolario para quien ordena docs: **busca los artefactos VISUALES rancios primero** — diagramas, tablas, ejemplos de configuración—. Son los que se copian, los que se citan y los que nadie diffea. **Y una decisión tiene que PARECER una decisión.** *(Segunda causa de la misma cicatriz, vista al ordenar el fichero entero.)* La decisión original vivía **en prosa**, dentro de un párrafo de objetivo, sin número, sin dueño y sin marca visual. La que la sobreescribió iba en negrita y mayúsculas: «**DECISIÓN CERRADA**». **Ganó la que parecía una decisión.** Por eso el formato no es cosmético ni solo un remedio contra el tamaño: un bloque **`D` numerado, con dueño explícito (usuario / técnica) y separado del texto normal** se distingue a un golpe de vista, y eso vale **también en un documento corto**. ⭐ **Un fallo de RED transitorio se lee como un fallo de PERMISOS si no reintentas.** *(Cicatriz 2026-07-27: dos sesiones concluyeron «esta máquina no tiene credenciales para `git.manabo.org`» y dejaron un chart de producción sin aterrizar. Era falso — cinco pushes ese mismo día desde ese mismo PC. El mismo día, un push a `git.c2et.com` falló con `Recv failure: Connection was reset` y **entró al segundo intento**.)* Antes de convertir un fallo en una conclusión: **reintenta**, y si vuelve a fallar, reporta el error **literal** en vez de tu interpretación de él — «connection reset» y «no autorizado» no son lo mismo y llevan a sitios opuestos. **Y si una premisa del encargo es falsa: PARA y repórtalo — no la "arregles" sobre la marcha.** El prompt puede dar por hecho algo que no se sostiene; corregirlo en silencio esconde el problema. Además: **lo que verificaste en una sesión anterior PUEDE haber cambiado** (otras sesiones, otros commits entre medias) → de lo heredado tienes MÁS que re-verificar, no menos, justo porque crees que ya lo sabes. Vuelve a mirar el estado real. ⭐ **Y la forma concreta de trabajarlas: las premisas del encargo se NUMERAN** (`P1`, `P2`…) con veredicto explícito —verdadera / falsa / matizada, con `fichero:línea`—, y el **ratio de premisas falsas** es el termómetro de *«estoy escribiendo de memoria»*. **«El doc lo dice» es una premisa a verificar**, no una fuente. ⇒ ⛔ **Y hacia atrás, que es la mitad que se olvida: al ESCRIBIR un hallazgo se nombra la RUTA EXACTA que se midió, nunca la categoría.** Una categoría con **una sola medición** detrás se lee después como universal, y de ahí se hereda como premisa falsa. *(Cicatriz cosecha 2026-08-15: «el camino Rocky» eran **dos** caminos y solo se había medido uno; la frase viajó por tres documentos antes de que alguien la careara.)* ⛔ **Y una forma de romper un documento que no deja rastro: la CIRUGÍA POR NÚMERO DE LÍNEA.** Un `sed '1342,1453d'` o un `sed "${L}r fichero"` acierta o destroza según un número que **cambia con cada edición anterior de la misma sesión** — y el daño es *texto que desaparece*, que es justo lo que no se nota releyendo por encima. *(Cicatriz 2026-08-18: la orquestadora editó así la ficha `I11` durante toda una sesión y la rompió **dos veces** — un borrado se llevó la línea ⛔ *«que nadie construya un des-aplicar»* (**el raíl que su propio encargo repetía**) y una inserción metió `I11.9` **dentro de la viñeta de `I11.8`**, cuya cola quedó leyéndose como suya. Lo cazó la sesión siguiente al ir a trabajar sobre la ficha; la orquestadora había «verificado» cada edición con un `grep` del marcador nuevo, que **siempre casa** aunque el resto haya quedado hecho trizas.)* ⇒ **Un `grep` del marcador que acabas de insertar no verifica una edición: verifica tu inserción.** Lo que hay que mirar es **el texto de alrededor**, y en concreto **la línea anterior y la posterior al corte**. ⇒ Y siempre que se pueda, edita por **anclas de contenido** (buscar y sustituir una cadena única) en vez de por número de línea; los números se recalculan solos y las anclas no. ## 7. Fuente única + copias vendidas + red anti-deriva Un artefacto compartido vive en **un** sitio; los consumidores llevan copia **commiteada** (build sin red/Node) y un `--check` detecta la deriva. Nunca se edita la copia. - El design system propio → `sync.sh` copia y `--check` verifica; el `.ipk` y la imagen del hub construyen sin dependencias externas. Tres consumidores, cero cambios en el componente = el contrato estaba bien puesto. ⛔ **Y cuando coses una cicatriz, BUSCA A LOS HERMANOS: un arreglo que no se propaga deja al gemelo armado, y encima con la prueba de que el fallo es real.** *(Cicatriz 2026-08-06, censo de los bancos del chart del hub: `aip-sync.sh` y `lo-reconcile.sh` son **dos drivers del mismo directorio que hacen lo mismo** —extraer un bloque del render, mandarlo a un host y ejecutarlo como root—. El 2026-08-02 `aip-sync.sh` se comió una extracción rota y se cosió bien: `awk`, anclas **afirmadas** y una aserción negativa. A su hermano no lo miró nadie. Cuatro días después, en `mode: loadBalancer` el ancla de cierre de `lo-reconcile.sh` **ya no existe** —`R14` quitó el reconciliador a propósito— pero la de apertura sí, así que el rango del `sed` corre hasta el final del render: **893 líneas donde son 36**, con los `ExternalSecret` dentro. Su única guarda es `[ -s ]`, que 893 líneas pasan de sobra, y lo siguiente de ese script es `scp` + **`sudo bash`**.)* ⇒ La pregunta, y es la misma de la poda (§1): **¿quién MÁS hace esto?** Un arreglo se termina cuando se ha buscado al resto de sitios con esa forma — no cuando el fichero que fallaba pasa. ⇒ Y el sub-raíl que lo hace concreto: **una guarda de «no vino vacío» no protege de «vino de más»**. Si de lo extraído depende un `bash`, la guarda tiene que **afirmar las dos anclas** y llevar una **aserción negativa** (*esto NO debe aparecer aquí dentro*). El tamaño no es un testigo. **Si tocas artefactos del OTRO repo, le debes a ese repo su entrada — en la MISMA sesión.** El espejo cross-repo no se pudre por deriva de redacción: se pudre **asimétricamente**, porque el trabajo hecho desde el repo A sobre cosas de B se documenta solo en A. Síntoma: casillas `[ ]` en B para cosas **desplegadas** en B. *(Cicatriz Hermes 2026-07-26: el sidecar Rosenpass corriendo en el chart del hub y el backlog del hub dándolo por pendiente, con un plan que nunca ocurrió; y un fix del hub —`F13.0-c`— que solo existía en el backlog del edge.)* La disciplina del espejo falló justo para el trabajo cross-repo, que es **para lo que existe el espejo**. ⛔ **PROPONER UN CAMBIO SOBRE EL MECANISMO DE OTRO FRENTE ES TOCARLO.** La regla de leer su contrato antes de meter mano **no aplica sólo a editar: aplica a SUGERIR** — porque **una propuesta técnica concreta de una orquestadora a otra tiene fuerza de encargo**, y se implementa. *(Cicatriz 2026-08-30: un frente reporta a otro un testigo falso —correcto, aceptado— y **añade una solución**: que cierto endpoint publique la fecha además del identificador. La dueña lo mide: el endpoint devuelve el identificador **pelado**, la comparación del agente es **de cadena completa**, y una línea de más hace que **no vuelva a coincidir nunca** ⇒ cada nodo **se re-descarga su árbol en cada disparo, para siempre**.)* ⛔⛔ **Y lo que lo hace grave es que NO HABRÍA FALLADO**: convergería igual, nadie lo notaría, y sólo gastaría de más **indefinidamente**. ⇒ Es **un cambio de contrato disfrazado de mejora del log**, y esa frase nombra la clase entera: los cambios que **no rompen nada** son los que **nadie revisa**. ⇒ **Precisión de alcance, y es donde el raíl viejo no llegaba**: *«el mecanismo ajeno se ENTREGA, no se arregla»* **ya se cumplía** —se entregó—. Lo que faltaba es que **la entrega puede llevar dentro un arreglo**. ⇒ La forma correcta: **entregar el problema MEDIDO**, y si propones una salida, **decir explícitamente que no has leído su contrato** — que es justo lo que convierte una orden en una idea. ⭐ **Y lo que hay que llevarse no es el error, es lo que lo contuvo** *(palabras de la dueña del mecanismo)*: **«un mecanismo que sólo funciona cuando todo el mundo acierta no es un mecanismo; éste funcionó contigo equivocándote, que es la única prueba que cuenta»**. Lo que paró aquello **no fue el criterio de nadie**: fue **la frontera**, que obligó a mandar la propuesta en vez de aplicarla. ⇒ Generalizado: **una frontera se valida el día que alguien competente se equivoca dentro de ella y aun así no pasa nada.** Mientras todos acierten, no sabes si tienes una frontera o una costumbre. ⛔ **Y el caso más caro de fuente doble no es un artefacto compartido: es el RELATO escrito DOS VECES, en el backlog y en la bitácora.** *(Cicatriz 2026-08-18: la orquestadora documentó cada hallazgo de dos días en los dos sitios; la ficha `I11` llegó a **464 líneas** y Xavier dijo que **había dejado de poder leer el backlog**. La bitácora ya lo tenía todo: lo del backlog era una copia pura.)* ⇒ La regla que lo evita **ya existía** —*«las decisiones se quedan, la EVIDENCIA se va»*, `sesion-ordenacion-docs.md` (capa `workflows/`)— pero vivía **en el workflow de la sesión que LIMPIA, no en el de las que ESCRIBEN**, así que se incumplía sistemáticamente y se pagaba después en una pasada. Por eso está aquí ahora. ⛔ **COMPROBAR QUE ALGO ESTÁ NO ES COMPROBAR QUE HACE AQUELLO PARA LO QUE EXISTE.** Y la trampa es que **el artefacto puede ser correcto**: verificarlo sale **VERDE** y aun así el aviso sigue abierto. *(Cicatriz 2026-08-31: un aviso pedía que el testigo de sincronía apareciera **donde mira quien depura**. La línea se construyó, es correcta, nombra los dos identificadores y no miente… pero la escribe un script que va a **un fichero de log distinto** del que recibe el resumen de la ejecución. ⇒ **El testigo existe y no viaja pegado a lo que se lee** — o sea, el gesto que el aviso existía para quitar **sigue ahí**. Y lo destapó **una sola pregunta**: «¿dónde queda lo que imprime?».)* ⇒ **La pregunta que lo caza no es «¿está?», es «¿QUIÉN LO VA A VER, Y CUÁNDO?»** — un piso por encima de comprobar la fuente: ahí la fuente declaraba y el artefacto no reconciliaba; **aquí el artefacto es correcto y está en el sitio equivocado**. ⛔⛔ **Y UN MENSAJE DE COMMIT NO ES UNA CAPA DEL CORPUS.** Lo dicho **sólo ahí** es, a efectos de quien decide, **invisible**: quien lee `AVISOS.md` —que es lo que se manda leer primero— lee **«RESUELTO»**. *(Mismo caso: la sesión que construyó **ya había medido** que el testigo caía en otro log **y lo dejó escrito, honesta y explícitamente… en el mensaje del commit**. Una deuda **medida, dicha y aun así invisible desde el sitio donde se decide**. La encontró su relevo **por casualidad**, mirando el `git log` para copiar la convención de mensajes.)* ⇒ **Un mensaje de commit es el testigo de un CAMBIO, no el estado de un FRENTE.** Si lo que sabes cambia el estado, va a su capa **además** —`AVISOS.md`, `decisiones.md`, `backlog.md` o la bitácora— y el commit **puede** repetirlo. ⛔ **Nunca sólo en el commit**: es la quinta capa que nadie lee y que ⇒ ⛔ **Y hay un segundo filo, peor que la invisibilidad: UN AVISO CON FECHA DENTRO DE UN ARTEFACTO SIN FECHA CADUCA SIN AVISAR.** Un mensaje de commit está **congelado**: se escribió en un instante y el mundo siguió. Un *«esto NO está vivo todavía»* escrito a las 14:42 **era cierto**… y a las 15:10, con la pieza construida y desplegada, **es una mentira que nadie ha tocado** — y el que lo lea mañana no tiene forma de saber cuál de las dos cosas está leyendo. *(Medido 2026-08-31, segundo frente en el mismo día: el aviso fue **cierto durante 28 minutos**.)* ⇒ Por eso un aviso **vive en una capa que se REVISA** —donde alguien lo cierra cuando deja de valer— y no en un artefacto que **nadie vuelve a abrir para corregirlo**. ⭐ La prueba de si un sitio sirve para un aviso es: **¿alguien va a volver aquí a cerrarlo?** Si la respuesta es no, ese sitio **sólo sirve para lo que no caduca**. además **no se puede recorrer** (§8). ⇒ **Al escribir, decide el destino UNA vez**, y los destinos son **CUATRO**: el corpus de un repo tiene **cuatro capas**, no tres. *¿Es una decisión cerrada, de las que otra sesión no puede contradecir sin PARAR?* → **`doc/decisiones.md`**, con su texto **íntegro**. *¿Es trabajo — un item que cabe en una sesión?* → **`doc/backlog.md`**, que es **el eje del troceo**. *¿Es lo que hay que saber el primer día?* → **`doc/AVISOS.md`**, que se lee antes que el backlog. *¿Es cómo nos enteramos —lo medido, las predicciones, los mutantes, las premisas falsas, el relato—?* → **`doc/bitacora.md`**, append-only, y en el backlog **un puntero fechado**. ⭐ Escribirlo en dos **no es prudencia: es crear la deriva** que §7 existe para impedir, porque el día que uno de los dos se corrija el otro seguirá ahí. ⛔ **Y la cuarta capa existe porque una decisión guardada en el backlog se numera POR FASE, y la numeración por fase colisiona.** *(Cicatriz 2026-08-22, `D6` de Xavier: **18 decisiones distintas llamadas `D1`** en un solo `backlog.md` — y cuatro fallos de una semana que salen de ahí: un **resumen** que gobernó en lugar de la decisión, un «precio aceptado» que Xavier nunca pensó, dos ids que colisionaban, y una sustitución con las **dos versiones vivas** a 90 líneas de distancia. En sus palabras, para qué sirve la capa: «así tienes todo lo peligroso en un único documento».)* ⇒ Por eso se ordena **por ámbito, no por fase**; cada entrada lleva **texto íntegro** (nunca un resumen — un resumen adquiere invariantes que la decisión no tiene), **dueño visible** (*Xavier* o *técnica*: una técnica escalada como suya ya coló), **estado**, y el **testigo en la misma línea** si afirma algo medible (§1). Una sustitución **edita la entrada vieja** —⚰️ y puntero—, jamás deja las dos vivas. ⇒ Y «todo lo peligroso en un único documento» solo protege si **alguien lo recorre** (§8): toda sesión de diseño mira ahí **qué contradice** antes de cerrar nada, y si contradice una `vigente`, ⛔ **COLOCAR UNA ENTREGA CONVIERTE EL BORRADOR EN UNA COPIA DERIVADA — y el borrador se BORRA en el mismo gesto, no «cuando ya no haga falta».** Mientras se redacta, el borrador **es** la fuente y eso está bien. El instante en que la entrega se coloca en el repo, hay **dos**: una con dueño, gate e historial, y otra sin nada — y la segunda es justo la que su autora tiene abierta y a mano, así que es la que se sigue editando. La deriva no necesita descuido: necesita **una edición más**. ⇒ **Regla**: la entrega se coloca y el borrador **muere en el mismo turno**. Si aún quedan cambios, no se escriben en el borrador — **se aplican al máster**. Y si el autor no puede escribir en el máster (no es su ámbito), **los redacta y los entrega**; lo que no hace es guardarlos en su copia «hasta que se puedan aplicar», porque eso **es** la fuente doble, sólo que con buena intención. *(Tres ocurrencias en cuatro días, la misma forma y sitios distintos: un `workflows/metodo.md` que llevaba **6 días** siendo máster de facto porque el repartidor no alcanzaba ese árbol y nadie era su dueño; una entrada de buzón escrita **explícitamente** como copia de seguridad *«que se retira cuando su dueña la coloque»* —y que sólo se retiró porque lo llevaba escrito dentro—; y un borrador de propuesta que **su propia autora vio convertirse en derivada** en el momento de aprobarse la entrega, con dos cambios pendientes en la mano. Las tres se vieron; la primera, seis días tarde.)* ⇒ ⭐ Y el testigo de que esto está bien resuelto **no es que el borrador esté al día: es que NO EXISTE**. Un borrador vivo junto a su máster es una fuente doble aunque hoy coincidan byte a byte — lo que se mide es **quién puede escribir en él**, no si ahora mismo difiere. **PARA y reporta** (§6) o la sustituye explícitamente. ⛔ **Un valor por DEFECTO declarado como dato en la capa base solo es fuente única si TODOS los consumidores pasan por el merge.** El que se lo salta no es un descuido: es una **segunda fuente** —con su propia verdad— y hay que buscarlo **explícitamente al introducir el default**, no el día que los dos valores diverjan. *(Cicatriz del motor de aprovisionamiento, cosecha 2026-08-15: un endpoint construía su vista **sin mergear la capa base** y devolvía otro valor.)* ⇒ Es la pregunta de la poda (§1) en la capa de datos: **¿quién MÁS lee esto, y por dónde?** Declarar el default no reparte el default. ⛔ **El docstring/manual ES la interfaz — así que también tiene fuente única.** Cambia el comportamiento → cambia el docstring **en el mismo commit**. Un doc que miente no es un doc desactualizado: **ES el bug**, porque es lo que el llamante lee **en vez** de leer el código, y no hay ningún gate detrás que lo desmienta. ⛔ **Y LAS CUATRO CAPAS NO SE LEEN IGUAL: hay capas de ARRANQUE y capas de CONSULTA.** El corpus no crece porque se escriba de más —crece **porque funciona**— pero **se lee entero cada vez, y eso sí es un defecto**. *(Medido 2026-08-29: arrancar una sesión de un frente maduro cuesta **1,40 MB ≈ 400 k tokens** antes de hacer nada —`CLAUDE.md` raíz + la constitución + su corpus—, o sea **media ventana de contexto gastada en leer**. Y ahí está la causa de que las sesiones se agoten y haya que relevarlas: **el coste de arranque se paga en la moneda con la que se trabaja**.)* ⇒ **El reparto, y sale de una medida, no de una intuición**: - **ARRANQUE** — `AVISOS.md` **primero**, luego `decisiones.md` y `backlog.md`, más el `CLAUDE.md` del repo. Es lo que dice **qué muerde hoy**. - **CONSULTA** — `bitacora.md` **por su índice**, y se abre la entrada que haga falta. ⛔ **No se lee entera al arrancar**: es *append-only* y es historia, su función es **consultarse**. - **NI UNA NI OTRA** — los **entregables** (`pos-anexo-x`, `pcte`, `enmiendas`…). Se sostienen solos a propósito, así que **por diseño no cuentan nada del estado interno**: para ponerse al día no sirven; para entregar, son el producto. ⛔ **Y esa última frase es una regla de ESCRITURA, no sólo una descripción de para qué sirve cada capa**: **el entregable lleva el VALOR; el historial es de la bitácora.** Fuera de un documento firmado van las fechas del proceso, los `mtime`, los *«antes decía»*, las citas de quién lo pidió y el relato de cómo se llegó — **todo eso tiene su capa, y no es ésta**. ⇒ **El encuadre importa y es el fallo repetido de `§7`**: escrita como *«los entregables no cuentan el estado interno»*, la regla se lee al **arrancar** y se incumple al **editar**, que es cuando haría falta. Igual que *«las decisiones se quedan, la evidencia se va»* vivía en el workflow de la sesión que LIMPIA y no en el de las que ESCRIBEN. *(Cicatriz 2026-08-30: una sesión metió tres fechas, un `mtime` y **una cita literal del usuario** dentro de un anexo firmado — **conociendo el principio**, que se lo habían dicho horas antes. Y el usuario, al verlo: **«me paso el día matando historiales»** ⇒ no es un desliz de una sesión, es la norma del sitio.)* ⇒ Por eso la línea va **en la cabecera del propio entregable**, donde se edita, y no sólo aquí: un raíl que sólo vive en la constitución **llega tarde al gesto que lo rompe**. ⭐ **El testigo de que el reparto es correcto, y es una medida real**: una sesión limpia reconstruyó un frente entero —fase, cifras, las trece familias, las decisiones vigentes, las trampas y los solapes— leyendo **el 28 % del corpus**, con la bitácora **sólo por el índice y sus dos últimas entradas**, y **sin abrir** el censo de 1.098 líneas ni los mapeos. En ese frente la bitácora es el **79 %** del peso. ⇒ **Sacarla del arranque quita cuatro quintos del coste sin perder nada.** ⚠️ Y lo que ese mismo careo enseñó del otro lado, que es lo que hay que arreglar escribiendo MÁS y no menos: **lo que peor está escrito no es lo técnico, es lo OPERATIVO.** El corpus documentaba magníficamente *qué se decidió y por qué falló*, y no decía **cómo se trabaja un martes por la mañana**. *«Un sucesor no se atasca en una decisión de diseño; se atasca en no saber si teclear `ansible-playbook` o `git push`.»* ⛔ **Un AVISO es una BANDERA: si no cabe en una pantalla, no es una bandera — es un documento disfrazado.** La primera línea lleva **qué muerde y a quién**; el detalle va debajo, y el relato a la bitácora. *(Cicatriz 2026-08-29: el `AVISOS.md` de un frente activo tenía **1.905 caracteres de media por línea** y una de **8.196** — y es **el fichero que se lee primero**.)* ⇒ Y no es sólo volumen: **el título en negrita de un aviso es la superficie que acumula encuadres caídos**, porque se escribe para que muerda, porque un listado compacto **es lo único que enseña**, y porque corregir el cuerpo **se siente** como haber corregido el aviso. Un aviso ya llegó a tener el título equivocado **dos veces el mismo día**, con el cuerpo corregido las dos. ⇒ **Ante un encuadre caído, el título se censa PRIMERO.** ⇒ ⭐ Y el barrido que nadie corre y que sale gratis: **censar qué avisos citan un ítem YA CERRADO.** *(En un solo frente había **cuatro** afirmando bloqueos sobre trabajo cerrado la semana anterior —y el propio corpus tenía escrito el patrón, aplicado a un fichero ajeno y nunca a sí mismo.)* ⛔ **EL VEREDICTO VA EN LA PRIMERA PALABRA DEL CAMPO, NO AL FINAL.** Un campo de estado que **empieza** diciendo *«abierta»* y **termina** en *«✅ cerrada»* es **formalmente correcto y operativamente mentiroso**: quien escanea una columna —que es como se lee una tabla— ve lo primero. Y las dos lecturas son defendibles, así que **dan resultados opuestos y ninguna es refutable**. *(Medido el 2026-08-29 sobre el semáforo del árbol: **14 filas no empezaban por marca de cierre y sólo 3 estaban realmente vivas** ⇒ **11 falsos positivos**; y **dos empezaban en «viva» y acababan en «CERRADA»**. Un censo por el principio las cuenta vivas para siempre; uno que busque «CERRADA», las cuenta cerradas.)* ⇒ Es la **tercera cara del mismo patrón** en un solo día, y por eso está aquí: **el título de un aviso** acumula los encuadres caídos · **el cierre de una fila** se pone donde cabe en vez de donde se mira · **el veredicto de un campo** se entierra tras 900 caracteres. **Lo que se lee es el principio; lo que se corrige, el cuerpo.** ⇒ Al enmendar un estado, **la primera palabra es lo primero que cambia**. ⛔ **Y AL VERIFICAR QUE ALGO SOBREVIVE EN OTRO SITIO: UN `id` ES ANCLA DÉBIL — LO QUE DISCRIMINA ES UNA CIFRA DISTINTIVA.** Los ids se citan en todas partes, así que buscarlos da **verde por construcción**. *(Cicatriz 2026-08-29, verificando 22 filas antes de purgarlas: el primer barrido por ids —`D12`, `A37`, `A41`…— **las dio las 22 por buenas**. Sólo el careo por **cifra** —`102 s`, `8 h 14 m`, `suite 1621`— separó las que de verdad tenían su relato fuera.)* ⇒ ⭐ Y lo que lo hace un raíl y no una anécdota es **la asimetría**: un patrón demasiado **específico** da **falsos negativos** —molestan, se ven, se corrigen—; un ancla demasiado **común** da **falsos positivos**, y ésos **aprueban un borrado y no se notan nunca**. *«Es peor que tus falsos negativos porque no se nota.»* ⇒ Cuando el veredicto **autoriza a destruir**, el ancla se elige **por lo exclusiva que es**, no por lo cómoda. ⚠️ Y con su corolario: **un `sha` es ancla exclusiva pero puede no vivir en ningún documento**. En ese mismo censo, **tres shas existían sólo como commits** — su relato estaba en el corpus por id y por cifra, pero **el salto del documento al commit no sobrevivía**. ⇒ ⭐ **Y la forma general del hallazgo, que es transferible a cualquier frente**: *un corpus documenta bien **lo que costó decidir** y mal **lo que nunca costó nada** — porque el ciclo de trabajo **no se decidió: se fue quedando**. **Nadie escribe lo que hace todos los días.*** Y es exactamente lo que un relevo no transmite y lo que un sucesor necesita **en los primeros diez minutos**. ⇒ El corpus de un frente lleva, escrito y al día, **cómo se trabaja hoy**: dónde se edita, cómo se aplica, cómo se sabe en treinta segundos en qué estado está el banco, y **qué está publicado y qué no**. ⛔ **Y LA FRONTERA DE «NO EJECUTES, DELEGA» SE SOSTIENE BIEN PARA MÁQUINAS Y MAL PARA TEXTO.** Ante el hierro la pregunta se hace sola —*¿esto puede romper algo?*—; ante el corpus **no aparece**, porque quien coordina **escribe en él todo el día legítimamente** —avisos, decisiones, bitácora— y una pasada de ordenación **se siente como más de lo mismo**. **No lo es**: reescribe **memoria compartida que leen todas las sesiones**, y por eso su propio procedimiento la declara **única**. *(Cicatriz 2026-08-29: una orquestadora leyó el workflow de ordenación **entero**, tomó las medidas y **empezó a hacerlo ella**. Lo correcto era pedir que se abriera esa sesión y pasarle el encargo. La paró el usuario: «estaba esperando a ver si me sacabas un mensaje para que te abriera una sesión».)* ⇒ ⭐ **El criterio que sí para, y no es ninguno de los dos que se usan por instinto**: no es *«¿sé hacerlo?»* ni *«¿es peligroso?»* — es **«¿esto tiene un TIPO DE SESIÓN PROPIO?»**. Si lo tiene, **se delega aunque sea texto, aunque sea barato y aunque sepas hacerlo**. ⇒ ⛔ **Y el mecanismo del fallo, que es lo transferible**: **un documento que describe un TIPO DE SESIÓN se lee como un MÉTODO que aplicar uno mismo.** Son las dos cosas a la vez —describen la sesión *y* contienen su método— y **nada en el texto obliga a decidir qué lectura estás haciendo**: un método **invita a ejecutarlo**, un tipo de sesión **invita a delegarlo**, y el mismo fichero produce las dos conductas. ⇒ Por eso un documento así **lo dice en su primera línea**: *«esto lo hace una sesión propia; si lo estás leyendo y no eres ella, tu trabajo es escribir su encargo»*. ⛔ **Y LA UNIDAD DE MEDIDA DEL CORPUS ES EL TOKEN, NO LA LÍNEA** *(Xavier, 2026-08-29)*. Contar líneas **hace invisible el problema real**: una línea de **8.196 caracteres** y una de **70** cuentan igual, y en los corpus de este árbol **eso no es una anomalía, es la norma** — un `AVISOS.md` con **1.905 caracteres de media por línea**, un semáforo con **928** y filas de **9.004**, frente a la constitución con **79**. ⇒ **Dos documentos con las mismas líneas pueden diferir en veinte veces lo que cuesta leerlos.** ⇒ **Proxy práctico**: `wc -c` (bytes) y **dividir entre ~3,5** para castellano con markdown y emojis. No hace falta un tokenizador: hace falta **dejar de contar líneas**. ⚠️ **Y esto invalida disparadores ya escritos**: cualquier umbral del corpus expresado en líneas —*«el backlog llegó a 2.729 líneas»*— **mide otra cosa** que la que se quería medir, y por eso los recortes hechos sobre ese criterio no cuadran con el alivio que producen. **Se re-expresan en tokens.** *(Lo cazó Xavier al no cuadrarle una sesión de ordenación de documentación: recortaba líneas y el coste no bajaba.)* ## 8. Fallo benigno explícito, nunca degradación silenciosa Diseña qué pasa cuando algo cae, y hazlo verificable. Y si algo se rompe en silencio, que grite. - Rosenpass cae → se **mantiene la última PSK** (WG sigue cifrando); NO se baja a sin-PSK. - `gre-sync` reconcilia la ruta al spoke **en cada vuelta** (no sólo al crear) — el opuesto era perder la ruta de vuelta tras cada reinicio, en silencio. - El guard de build falla si falta la copia del bundle, en vez de servir un `