Una propuesta tecnica concreta de una orquestadora a otra tiene fuerza de encargo. Entrega el problema MEDIDO y, si propones salida, di que no has leido su contrato. Y una frontera se valida el dia que alguien competente se equivoca dentro de ella y aun asi no pasa nada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
143 KiB
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.mdde la matriz.⛔ Este fichero ES la fuente, y vive en el repo
metodo(la matriz). Se reparte conmetodo update, que lo copia byte a byte a.metodo/metodo.mdde 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
§0no vale. (Familia deA20: 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
statedel 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 unwrite_zonefallido 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
curlque 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 uncurlpropio, 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
/healthque repite lo que le inyectaron, unpsque 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
/logines indistinguible de «no hay guard» si tu herramienta te da el200del final del camino. (Cicatriz 2026-08-13, y la cometió la orquestadora: midió el panel del hub del trabajo conurllib.request.urlopen—que sigue los3xxpor defecto y no lo dice— y concluyó «la aplicación no comprueba nada, el panel está abierto dentro del cluster». Era falso: contestaba303 → /login. Encima tumbó un hallazgo correcto de la sesión de ejecución —que sí había medido conwget -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.orges 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.comreportado como bomba estaba en un WIP sin commitear, y «hay 4build.yml» eran 3 enorigin/main(el cuarto vivía en un worktree de otra rama, con 0 commits propios). Si afirmas sobre el repo, mide sobreorigin/<rama>, 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.shestaba en CRLF en el worktree y así ni arranca —set -uo pipefail\r—, o sea que concore.autocrlf=trueun clon en Windows deja todos los bancos muertos. El censo no lo vio porque medía —bien— sobre ungit archiveen 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 (untr -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 objetosApplicationy dejó vivos el pod del runner, su PVC, el nskanikocon 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 undeleteexplí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íaSynced/Healthysin haber mirado nunca losSchedule. 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
ETagde la pubkey del peer endata/mesh.json, y el fichero que certifica —data/pq/peers/<pid>.pk— en otro sitio. Se borró el fichero y no volvió nunca: 8 vueltas pidiendo conIf-None-Match, el hub contestando304, y el enlaceblockedpara 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.jsonquedó en{"hubs":{}}mientraswgtransconservaba 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
lodel 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 diceSynced/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 necesitabawg_handshake_agepara su contador vivo; igual con IP, OSPF, y elstatede los nombres de malla. - Edad, no timestamp, en routers sin RTC fiable.
0es 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é
protose 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 = Truefijo 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 aLNK.statuscon uncfgdeclarado y sin asignar, así que el peldaño de identidad llegaba vacío. Los tests pasaban porque le pasancfga mano.- Un mutante que derivaba el sufijo de una LAN del dominio del router —la invención que
F11·D8del 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 contraport: 4440, o sea que la guarda del puerto decía que sí a un valor que no era. Es elhead -1deA16en 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-inetcontienenginx, y las dos son clases de Ingress reales de instancias distintas ⇒ una guarda sin ancla$decíaok [hermes] … emiten nginxsobre un render que poníanginx-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 — elpytestde esa misma sesión aguantó justo por eso. cmd | grep -qbajoset -o pipefail:grep -qsale 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 ramaelse— 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 elok? Si elokes elelse, 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 construyendoI11.2:- ⭐ Un
cmpprueba 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 elsedhabí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 comoFAIL. (Misma sesión: un helperquiere_lineadefinido 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 elok?» 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):
RESETa 0,78 s; por la WAN:201en 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_frrque se llevazebra/ospfd(F9.2), elrestartde uhttpd que tarda un minuto en devolver las rutas (U3.3a), y la regla de oro debacklog-ejecucion.md(capaworkflows/): 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).
rechazadono 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.
⭐ 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-lanno salen denetwork.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 (<host>.<sede>.<dominio>) 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.shcopia y--checkverifica; el.ipky 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.
⇒ 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.mdprimero, luegodecisiones.mdybacklog.md, más elCLAUDE.mddel repo. Es lo que dice qué muerde hoy. - CONSULTA —
bitacora.mdpor 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, losmtime, 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, unmtimey 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 teclearansible-playbookogit 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-syncreconcilia 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
<script>404.
⛔ Un mecanismo de resiliencia que NUNCA se ha ejercitado puede ser autodestructivo justo en el
momento de usarse. (Cicatriz 2026-07-27: el multi-hub existe para sobrevivir a la caída de un
hub — y hasta v2.1.34, añadir el segundo tumbaba el túnel del primero ~40 s, porque el
endpoint del hub nuevo es un nombre y, si no resolvía, netifd rehacía la interfaz WG entera.
La propiedad que existía para dar resiliencia te costaba un hub al activarla. Llevaba ahí desde
que existe el multi-hub, y solo apareció cuando por primera vez hubo dos hubs vivos en el mismo
edge.)
⇒ Un camino de recuperación que no se ha recorrido no es un camino de recuperación, es una
hipótesis. Ejercítalo antes de necesitarlo: el día que haga falta, ya es tarde para descubrir
que el remedio quita lo que venía a proteger.
⛔ La REDUNDANCIA oculta el fallo que la va a necesitar. Un defecto tapado por el camino
alternativo no se ve mientras el camino alternativo esté — y aparece exactamente el día que ya no
está, o sea en el peor momento y sin historial. (Cicatriz A18, 2026-07-31: con la identidad del
edge divergiendo entre los dos hubs, el nombre seguía respondiendo 3/3 porque el tráfico rodeaba
por el otro router de campo —G8 funde los dominios de inundación—. Con ese aparato apagado, el hub habría
perdido al edge del todo.)
⇒ Para ver el fallo, quita la redundancia y vuelve a medir: apaga el camino alterno, tira el
segundo hub, desconecta el vecino. Un verde obtenido con todas las rutas vivas no dice si el
servicio funciona: dice que alguna de ellas funciona.
⛔ Y su hermano, que es el que nadie busca: la ABUNDANCIA de recursos esconde el derroche. Medir
siempre en el aparato más holgado no certifica nada del más apretado — y el defecto no está latente,
está ocurriendo, solo que sobra sitio para pagarlo. (Cicatriz P12, 2026-08-02: con F13·D10
—la del edge, «PQ siempre que se pueda»— un edge sin binario de Rosenpass seguía bajándose
la pubkey del hub —524.160 B— en el bucle de 20 s y por cada hub. En el router grande (6,8 GB de
overlay) no se nota jamás; en un GL-AR300M eran 1 MB sobre 212 KB libres. Se descubrió
porque el aparato pequeño lo hizo visible, no porque alguien lo buscara.)
⇒ Pregunta cuando una medida salga limpia: ¿en qué aparato la he tomado, y qué le sobraba? Disco,
RAM, CPU y ancho de banda son todos amortiguadores que convierten un fallo en una estadística que
nadie mira. La flota real se parece al más pobre, no al banco.
⛔ «0 reinicios» no es estabilidad: es un estado NO EJERCITADO. Un proceso vivo sostiene en su
memoria cosas que ya no existen fuera — y mientras no reinicie, nadie se entera. El reinicio no
rompe nada: es el momento en que se descubre lo que llevaba roto. (Cinco cicatrices en tres días,
2026-07-27/30: los seis postconf del smtp-relay vivían en un contenedor y se perdían al reiniciar ·
el argocd-image-updater falla unauthorized en cada ciclo con una credencial cacheada en
memoria, y lo único que frenaba que reescribiera un pin bueno era esa avería — reiniciarlo arma
el riesgo · kernel-engine corre un digest que ya no está en el registro: lleva 62 días arriba y
un reinicio lo deja en ImagePullBackOff · tang y tftp son el mismo caso sin verificar.)
⇒ Cuando midas algo que lleva mucho arriba, la pregunta no es «¿funciona?» — es «¿sobrevive a
un reinicio?», y se contesta careando lo que el proceso usa contra lo que hay hoy en su origen
(el registro, el fichero, el secreto). Un uptime largo es motivo de sospecha, no de confianza.
⇒ Corolario para retiradas: antes de quitar un mecanismo, comprueba si algo vivo depende de que no
se reinicie. Quitar la última forma de reconstruir algo que hoy solo existe en una caché convierte
una limpieza en una pérdida.
⇒ Y el mismo raíl al revés: un arreglo COMMITEADO no es un arreglo APLICADO. Todo lo que se lee
al arrancar —ConfigMap, variables de entorno, fichero de configuración— sigue siendo el viejo
hasta que el proceso reinicie, y el git log dice que está arreglado. (Cicatriz 2026-07-31:
el MCP de build llevaba 21 h con el config anterior al renombrado de los hubs. El fix se commiteó a
las 00:49, pero editar un ConfigMap no rueda el Deployment ⇒ seguía sondeando un nombre que hoy
es NXDOMAIN ⇒ el verify no respondía nunca ⇒ auto-rollback que deshizo un pin bueno. Tercera
vez esta semana que el mecanismo de seguridad es el asesino.)
⇒ Cierra el cambio midiendo lo que el proceso vivo cree, no lo que dice el repositorio.
⛔ REVOCAR una credencial se lleva a sus HIJAS — así que «revocar el root» solo es seguro cuando
todo lo que importa es HUÉRFANO, y eso se mide ANTES. Una limpieza de credenciales no es una
limpieza si no sabes qué cuelga de lo que vas a quitar. (Cicatriz 2026-08-12, y la cadena empieza en
una recomendación de la orquestadora: se propuso quitar el root permanente de Vault —correcto
como higiene— y se ejecutó rotándolo con generate-root y revocando el viejo. En Vault los tokens
son hijos de quien los crea, y revoke del padre borra el árbol: 14 minutos después el
ClusterSecretStore del cluster hermes pasaba a InvalidProviderConfig y 30 ExternalSecret
dejaban de refrescarse — entre ellos la base de datos, el SMTP y el Twilio de una aplicación en
producción.)
⇒ ⭐ Y lo que lo probó no fue el muerto, fue el SUPERVIVIENTE: los tres tokens que aguantaron
eran orphan: true y el que murió era el único que no. El diferencial es la prueba, y no exigió
el privilegio que ya no se tenía — que es justo cuando hace falta un método, porque investigar un
incidente de credenciales suele empezar sin credenciales.
⇒ Antes de revocar: enumera qué cuelga y comprueba su orphan. Y al acuñar cualquier
credencial de servicio, hazla huérfana desde el principio — una credencial que hereda el ciclo
de vida de quien la creó es un rehén. La solución de fondo no es la orfandad, es no usar tokens
estáticos (auth method), pero la orfandad es lo que se puede hacer hoy.
⛔ No concedas un privilegio para callar un error: pregunta si el proceso debería estar haciendo
eso. (F11.14: chronyd moría con CAP_SYS_TIME not present. Añadir la capacidad era el reflejo —y
le habría dado a un servicio en red la potestad de mover el reloj del NODO, y con ella TLS,
certificados y etcd. La respuesta era -x: un contenedor que SIRVE hora no es uno que PONE
hora.)
⭐ Y su forma positiva, que es de DISEÑO y no de permisos: la AUSENCIA de una capacidad es un
guardarraíl. El poder peligroso se retiene estructuralmente —no se expone porque no se
construye—: vocabulario cerrado, sin exec, sin SQL libre, sin comando arbitrario. Lo que no
existe no hay que acordarse de no usarlo, ni protegerlo con un permiso que alguien acabará
concediendo un martes para callar un error.
⛔ Una PROYECCIÓN construida para otra cosa no sirve de entrada a una decisión: los campos que no
lleva se leen como valores, no como ausencias. (Cicatriz E6, 2026-08-01: el reconciliador
decidía la política de resolución a partir de lease_rows() —la proyección para publicar, que no
lleva nat—. «No viene nat» se leyó como «ninguna LAN tiene salida», así que la negativa se
encendía en cada vuelta, con el NAT puesto. Y el test propio lo consagraba.)
⇒ Dos arreglos, y hacen falta los dos: que el dato salga de su fuente (la configuración de las
LAN, no la proyección), y que una fila que no declara el campo devuelva dato_incompleto y no
decida nada. Una ausencia nunca puede ser interpretable como un valor.
⛔ Un valor de configuración que sale VACÍO no falla: cae al default, y el default suele ser el
menos seguro. (Cicatriz F11.14, 2026-07-27, cazada en el render, antes de existir: el
bindaddress de chrony salía vacío porque serviceLoopback lo inyecta el ApplicationSet y no está
en ningún values. Un bindaddress a secas no da error — chronyd vuelve a su default y escucha en
todas las direcciones, IP pública incluida: un reflector de amplificación NTP abierto a
internet.) ⇒ En las plantillas, required en todo valor cuyo vacío sea peligroso, para que
reviente al renderizar en vez de desplegarse abierto.
⇒ ⛔ Y el corte que decide cuándo un default es admisible: PREFERENCIA vs IDENTIDAD. Un default de preferencia —qué valor usar entre varios válidos— es legítimo. Un default de identidad —qué ES esto— no existe: si el dato que identifica al artefacto no está declarado, el artefacto no se puede construir, y la respuesta es un fallo explícito con su motivo, nunca el valor más común. (Cicatriz del motor de aprovisionamiento, cosecha 2026-08-15: un hueco de identidad rellenado con «lo de siempre» sirvió un kickstart destructivo con un HTTP 200 impecable.)
⛔ Y el corolario que este raíl NO llevaba, medido seis veces el 2026-08-19: un fallo benigno
explícito sólo protege si alguien LEE la línea. Un banco que se salta algo anunciándolo, en
una salida que nadie mira, produce el mismo resultado que el silencio — y encima con la conciencia
tranquila de haberlo declarado. (Cicatriz V11b: el careo routes-*.txt ↔ superficie.md del
chart del hub buscaba repos/infrarouter/…, carpeta renombrada la
2026-08-15. Se saltó en todas las corridas de cuatro días, y no en silencio: llevaba un
warnings.warn además del pytest.skip, puesto a propósito citando este §8, con el comentario
«esto no se salta en silencio — se salta diciéndolo». Y lo decía. Y nadie lo leyó. Mientras
tanto las cuatro capturas se quedaron atrás de una capa entera —la CRL— sin que el gate pudiera
enrojecer.)
⛔ UN GATE PERMANENTEMENTE ROJO ES TAN CIEGO COMO UNO QUE NO PUEDE ENROJECER — y es peor, porque
parece que funciona. Si un check lleva días en rojo, ya no puede señalar el defecto siguiente:
el 17.º aviso entra en una pantalla que tenía 16, y nadie lo ve. El rojo constante no es una deuda
pendiente: es el gate apagado, con la ceremonia intacta.
(Cicatriz 2026-08-30: un frente llevaba 16 avisos C25 —mensajes de commit— en rojo varios
días, y ninguna de las cuatro sesiones que pasaron por él lo dijo, teniendo el propio Método
escrito delante: «si sale rojo, no lo apagues: arréglalo o dilo». Lo cazó la quinta, el día de
llegar, precisamente porque no estaba acostumbrada a verlo.)
⇒ ⭐ Y al mirarlo, la vara estaba mal, no el trabajo: de los 16, seis eran avisos: y
bitacora: — las capas del propio corpus, que en este método son tipos de cambio de primera
clase y el vocabulario no las contemplaba. Arreglar la regla quitó seis; declarar excepciones
las habría enterrado a las dieciséis.
⇒ El orden, entonces: ante un gate en rojo crónico, primero pregunta si la vara mide lo que
crees —un rojo masivo y homogéneo acusa al criterio antes que al trabajo—; sólo lo que
sobreviva a eso se arregla o se declara como excepción nominal, fechada y motivada.
⛔ Y el corolario que lo hace operativo: acostumbrarse a un rojo es el mecanismo, así que quien
llega nuevo a un frente es el único que puede verlo — si arrancas en un sitio y su gate sale
rojo, dilo ese día; después ya te habrás acostumbrado tú.
⇒ ⭐ Lo único que convierte un aviso en un gate es un NÚMERO QUE TIENE QUE CUADRAR, y que ese número mueva el código de salida, no la pantalla. Las tres formas que ya existen en el árbol, y se copian en vez de inventarse:
- suelo de comprobaciones EJECUTADAS (
guards.sh:SUELO_CHECKS) — uncommand not foundno para el script y la comprobación simplemente no ocurre; - suelo de mutaciones EJECUTADAS (los bancos del edge y del hub) — una mutación que deja de casar no se queja;
- un
skip/una dormancia CONTADOS (elconftest.pydel chart del hub) — saltarse un careo es rojo, y las ausencias legítimas se declaran por nombre y se carean por igualdad, para que la lista no se pudra afirmando una ausencia que ya no existe.
⛔ Y su hermano gemelo, que es el que no se ve mirando el código: un gate que NADIE LANZA da
igual de bien escrito que esté. (Mismo censo: probe-guard-hub.js llevaba los mismos cuatro
días roto —falla ruidoso, MODULE_NOT_FOUND— y nadie vio el rojo porque no lo corre ningún gate;
y pq-sync.sh lleva escrito de su puño que su línea base estuvo ocho días en rojo «porque
este banco no lo corre ningún gate». Un banco mudo y un banco muerto se leen igual desde fuera.)
⇒ Al escribir una comprobación, la pregunta no acaba en ¿puede ponerse roja? — sigue en ¿quién
la lanza, y qué pasa si ese lanzador deja de existir? Un suelo dentro de un banco que nadie
lanza sigue sin ser una puerta.
⛔ Y el rastrillo que hay que pasar SIEMPRE que un testigo salga verde: ¿ese testigo puede decir
que no? Tres veces el mismo día 2026-08-19, las tres midiendo el arreglo del censo V12 y las
tres a punto de firmar un verde falso:
git status --porcelain 2>/dev/nulldesde WSL aborta por «dubious ownership» ⇒ no imprime nada porque FALLA. Ese vacío se leyó como «el árbol está limpio»; medido desde Git Bash, había tres ficheros mutados.bash -ndio verde sobre una llamada cuyos argumentos estaban corridos un puesto (unos comentarios metidos dentro de una continuación\). Sintaxis válida, semántica rota: lo cazó correr el banco, con$2: unbound variablea mitad de vuelta.- Un
tailsobre/tmp/a.outdevolvió la salida de otra sesión —fichero ajeno, de doce horas antes, con el redirect propio fallando por permisos— y estuvo a punto de reportarse como medida propia. ⇒ Tres instrumentos distintos, un solo modo de fallo: el silencio de la herramienta se leyó como la respuesta buena. Antes de creerte un verde, hazle decir que no una vez — rompe algo a propósito y comprueba que el testigo lo acusa. Y no midas en un sitio compartido (/tmp) lo que tiene que ser tuyo.
9. Sesiones: fresca para su stack; la continuidad la lleva el orquestador
Cada sesión de ejecución arranca limpia sobre el stack que toca (edge ≠ hub ≠ MCP). No se reutiliza una sesión de otro stack "por aprovechar contexto" — arrastra ruido. El hilo entre sesiones lo mantiene la orquestadora (backlog + memoria).
TODO PROMPT DICE SI ES EN FRÍO O EN CALIENTE. Es la primera línea, no una nota al pie: quien lo recibe tiene que saber si abre sesión nueva o lo pega en la que ya corre. (Cicatriz 2026-07-26: la orquestadora de redes escribió toda una racha de prompts sin decirlo — se asumían fríos porque empezaban con el ritual de lecturas, y nadie lo había hecho explícito.)
- CALIENTE — mismo stack, mismo repo, continuación directa, y el contexto de la sesión anterior es un activo: acaba de construir justo eso que vas a tocar. Típico de los remates cortos (cerrar un endpoint que ella misma dejó anotado). Re-arrancar en frío para cambiar un decorador significa releer cinco documentos para nada.
- FRÍA — cambio de stack; o la tarea es verificar / cuestionar lo que esa sesión acaba de hacer; o ha pasado tiempo y el estado ha cambiado; o su contexto ya es ruido (sesión larguísima, tema agotado).
⛔ La sesión que acaba de construir algo es la PEOR para verificar que está bien. Ya cree sus propias premisas, y el raíl 6 dice que de lo heredado hay que re-verificar más, no menos. Si la tarea es comprobar, medir o poner en duda → fría, siempre. Construir encima → caliente.
⛔ LO PRIMERO DE UNA ORQUESTADORA ES UN git pull — Y SI TRAE ALGO NUEVO, MEDIRLO ANTES DE
SEGUIR (Xavier, 2026-08-29). No es higiene de git: es que el estado que creías tuyo puede
haberlo movido otro mientras no estabas, y arrancar sobre una foto vieja es empezar con una premisa
falsa que nadie va a contradecir. ⇒ Las dos mitades son obligatorias: traerlo y mirarlo. Un
pull que trae doce commits y no se lee sólo ha cambiado el sitio donde está la sorpresa.
⇒ Y aplica también a los repos que tú no trabajas pero coordinas: puede que su frente lo lleve
otra persona, y que lo que traiga el pull no sea de ninguna sesión sino suyo.
(Cicatriz 2026-08-29: se re-estampó el Método en el repo de un frente que trabaja otra persona,
sin mirar antes qué había hecho. No rompió nada —la copia quedó íntegra y su trabajo intacto— pero
se le cambió el ritual de arranque sin decírselo, y su frente no tiene sesión en el canal a la
que avisar. ⇒ El corpus manda, pero alguien tiene que avisar de que cambió: un cambio de método
que llega por git pull y nadie anuncia es exactamente lo que este mismo raíl dice que no
funciona.)
⚙️ Desde el 2026-08-28 «CALIENTE» tiene MECANISMO: ya no se pega — se manda. Este raíl daba por supuesto que el prompt lo transporta una persona (de ahí «quien lo recibe tiene que saber si abre sesión nueva o lo pega en la que ya corre»). Ese día dos sesiones del mismo árbol se hablaron directamente por primera vez —cinco mensajes, todos actuados, en unas tres horas—, así que una sesión viva se direcciona por su nombre. ⛔ Lo que cambia es el TRANSPORTE, no el CRITERIO. Que alcanzar una sesión salga barato no convierte en caliente nada de lo que la lista de arriba llama frío, y el prompt sigue diciendo cuál es. La confusión es fácil y cara, por eso se escribe aparte.
⛔ Y la tensión NUEVA que el canal crea contra este mismo raíl: la sesión más barata de alcanzar
es exactamente la que §9 prohíbe usar. Mandarle un mensaje a la que acaba de construir la pieza
cuesta un gesto; abrir una fría cuesta releer. El camino cómodo lleva justo a «pregúntale al que lo
hizo si está bien», que es la frase que este raíl existe para prohibir.
⇒ La forma que sí funcionó, medida el mismo día: la orquestadora verificó por su cuenta y
desde fuera —abriendo los objetos de git, midiendo los certificados con el crudo delante,
censando el corpus— y usó el canal sólo para mandar el resultado ya medido. El canal transportó
datos y correcciones; la verificación, nunca. ⇒ Regla de bolsillo: por el canal viaja lo que
ya está medido; lo que hay que medir no se pregunta, se mide.
⚠️ Y lo que el canal SÍ compró ese día, que es por lo que no se descarta: una premisa falsa parada
antes de llegar más lejos en un entregable oficial, una corrección hacia arriba (de una sesión
a su orquestadora) en el momento, y una discrepancia declarada al instante.
(Lo procedimental —cómo se abre un mensaje, qué viaja por el canal y qué no, quién avisa a quién al
acabar— es procedimiento, no principio: vive en la capa workflows/, como el resto de la
estructura de sesión.)
⛔ EL CANAL AVISA; EL CORPUS MANDA. Preguntar y avisar, sí. Un encargo nuevo o una decisión NO viajan por aquí: van por el usuario y se escriben. Una corrección o adenda a un encargo ya aprobado sí, diciendo de quién viene. Y lo que cambie el trabajo lo escribe en el corpus quien lo recibe. ⇒ Un acuerdo que sólo existe en el canal no existe: un mensaje muere con su sesión; una línea del corpus sobrevive. ⚠️ Con una excepción medida, y hay que nombrarla o el raíl se aplica mal: en un relevo, el canal no está repitiendo lo que el corpus dice — está entregando lo único que el corpus no sabe recoger (callejones ya recorridos, una medida tomada y por qué no vale, la calibración del puesto). ⇒ Ahí el deber es el inverso y es del que recibe: fijar en el corpus lo que le llegó por el canal, o el relevo sólo ha movido la deuda de sitio.
⛔ El ESTADO DE OTRO FRENTE se re-verifica antes de retransmitirlo — o se marca «según me dijeron
a las HH:MM». Nunca se afirma en plano. (Cicatriz 2026-08-28, y son cuatro el mismo día, en
tres frentes: «E2.1 está bloqueado esperando un hierro prestado» —falso—; «esas dos filas son de sesiones
muertas» —una era de una sesión viva y escribiendo en ella—; «tiene 5 commits sin empujar» —ya
estaban empujados—; y una alarma de hierro que movió el orden de la cola del usuario. Los
cuatro iban en el bloque afirmativo, ninguno marcado como inferencia.)
⇒ ⭐ El mecanismo, que es lo que hay que saberse: en los cuatro, la foto era correcta cuando se
tomó y llegó rancia sin envejecer visiblemente. Un estado ajeno no se pudre de forma legible:
sigue pareciendo un hecho. Y no se puede verificar desde dentro del frente propio — por eso lo
declara cada cual y no lo reconstruye el que recibe.
⇒ Y la razón de que el canal lo empeore, que también es de diseño: transporta rápido y sin la
fricción que obliga a citar la fuente. Escribir en el corpus te obliga a poner ruta, fecha y
testigo; mandar un mensaje, no.
⛔ «YA MEDIDO, NO LO COMPRUEBES» NO EXIME — ES JUSTAMENTE LA FRASE QUE HAY QUE COMPROBAR. Esa marca no es evidencia: es posición. Y en un canal donde nadie ve la pantalla del otro, la posición es lo único que puede sustituir a un testigo sin que se note. (Cicatriz 2026-08-29/30, y va en los dos sentidos, que es lo que la hace raíl: una sesión mandó a otra dos datos marcados como medidos y los dos eran falsos —un recuento de líneas de una sección del que se afirmó el contenido, y un encaje de workflow que la medida desmentía—; la receptora los comprobó a pesar de la marca y los corrigió. Al día siguiente, en sentido contrario, la receptora entregó un conteo propio que sí era falso —una ruta relativa sin resolver— y lo cazó la otra por no reproducirlo. Un corte entero se habría apoyado en él.) ⇒ Regla: el que manda marca lo medido con su testigo (comando, cifra, ruta) y no con su autoridad; el que recibe reproduce lo que va a sostener una decisión, aunque venga marcado. Y si reproducir no es barato, se retransmite como inferencia, no en plano. ⇒ ⭐ Y el porqué, que es el diseño del canal y no una cortesía: la única forma de que un canal entre sesiones no propague errores es que nadie tenga la última palabra por su posición. Ni la orquestadora sobre el satélite, ni la superadmin sobre nadie. El fallo de una se caza porque la otra midió; el de la otra, porque la primera no reprodujo. Las dos mitades, o no funciona. ⛔ Corolario, y contradice el instinto de eficiencia: dos sesiones que nunca se corrigen no son un canal sano — son un canal donde la de arriba habla y la de abajo ejecuta, y ahí el error de la de arriba no tiene quien lo pare.
⛔ El NOMBRE de una sesión es una DIRECCIÓN; el ROL es la REFERENCIA. No se mezclan. Entre
sesiones se direcciona con el nombre que imprime tu propio listado —es lo único que entrega un
mensaje—; hacia el usuario se habla de ROL y proyecto, porque los nombres en su pantalla no
existen. (Cicatriz 2026-08-28: la superadmin informó todo el día citando nombres de sesión, todos
correctos, hasta que él dijo «yo no veo eso». Es §1 —la herramienta de diagnóstico no ve lo
mismo que el consumidor— aplicada a hablar con quien te dirige: darle el identificador que TÚ
usas y él no puede resolver es información inútil con aspecto de precisión, y es peor que
omitirla.)
⇒ Y el rol tiene lo que al nombre le falta: no rota. Un nombre caduca en cada reinicio y miente
de tres formas —la sesión murió, se renombró, o nunca fue una dirección (una sesión no sabe con
qué nombre la ven las demás)—. ⇒ El único testigo válido de que una sesión murió es «escribí y no
contestó», jamás «no la veo en el listado».
⛔ Nada de LAVADO DE PERMISOS: si a una sesión le deniegan algo, no se lo pide a otra — lo devuelve a quien la dirige. No es doctrina de esta casa: es un modo de fallo con nombre propio en el contrato de la propia herramienta (cross-session permission laundering). Un permiso que se consigue rodeando a quien lo negó no es un permiso, y el canal hace ese rodeo trivial.
⛔ «Enviado con éxito» dice que el mensaje ENTRÓ EN LA COLA, no que alguien lo haya LEÍDO — y su mitad simétrica, que es la que muerde: el emisor tampoco sabe si su mensaje se CRUZÓ con el del otro, así que la ausencia de acuse puede ser un cruce y no un silencio. (Cicatriz 2026-08-28: la superadmin mandó un encargo, recibió a continuación un «sigo esperando el encargo», concluyó que no había llegado y lo reenvió. Sí había llegado: entró mientras la otra sesión ejecutaba el turno en el que escribía su aviso. Lo corrigió la propia destinataria, con su transcripción delante, y evitó que una hipótesis no demostrada —«una sesión parada no drena su cola»— entrara en esta constitución. Esa hipótesis sigue sin medir y por eso no se escribe aquí.) ⇒ Regla de bolsillo: antes de reenviar, pregunta si lo que tienes es un silencio o un cruce. Y si reenvías, dilo —«ya te lo mandé, va otra vez»— para que quien lo reciba dos veces sepa que es el mismo y no un encargo nuevo.
10. Lo que "funciona por casualidad" está roto
Construir bien la capa de encima destapa lo de debajo. Al hacerlo, arréglalo y anótalo, no lo dejes pasar.
- CSS que teñía por selectores de ancestro que no cruzan el Shadow DOM (llevaba tiempo sin
teñir nada).
dnsmasq interface=wg0sin dirección → el hub no servía DNS a nadie desde hacía meses. El split-DNS escribiendo en/etc/dnsmasq.dcuando OpenWrt lee/tmp/dnsmasq.d.
Cuando destapes algo muerto, mira las DOS mitades: que nadie lo consuma puede estar escondiendo
que tampoco se podía escribir. (Cicatriz F11.9, 2026-07-26: la Global Config del hub estaba
muerta río abajo —su payload alimentaba a un agente borrado tres semanas antes— y al ir a
arreglarla apareció que también lo estaba río arriba: el handler POST estaba registrado como
GET, así que el formulario devolvía 405 y «Guardar» no había guardado nunca. Cada mitad
tapaba a la otra: nadie echaba en falta un dato que nadie podía introducir.) Señal barata y
fiable: un contador de versión de config que sigue en 1 con todos los campos vacíos no es «no
lo han usado» — es «no se puede usar». Búscala.
- Si es de producción (p. ej. el
gre-syncde c2et), no lo arregles a lo loco: anótalo como bomba latente y encájalo en el plan de release (verincidente-produccion.md).
11. El orden de lo irreversible: añadir antes de quitar · C14 (4 fuentes), promovido 2026-08-08
Todo lo verificable ocurre antes del paso que no se deshace. Se añade antes de quitar, se crea antes de borrar; cada paso de una release se revierte por separado y ninguno depende de dos cosas nuevas a la vez.
- Un tercer nodo de etcd entra antes de que salga el que se retira — al revés, el quórum se queda en uno.
- "Nunca borres el
IPAddressPoolviejo hasta tener el nuevo sirviendo" (MetalLB). - El cutover de un servicio con nombre: primero responde el nuevo, luego se retira el viejo.
12. Exploración solo-lectura, sin autoengaño · C35, promovido 2026-08-08
Explorar es medir sin tocar, y sin creerte tu propia medida.
curlcon control: mira/api/health/http_code, no "parece que responde".- "Aquí no hay nada" es un RESULTADO, no un fracaso — se anota igual que un hallazgo.
- Los falsos positivos de
grepson el riesgo nº1: una lista larga de "roto" delata TU método (un patrón demasiado amplio), no el sistema. Verifica cada hit contra la fuente antes de declararlo. - Sobre texto generado, el testigo se define sobre las líneas EJECUTABLES, no sobre el fichero: si el generador emite comentarios que documentan las ramas NO tomadas, el grep ingenuo mide el comentario — verde indistinguible de rojo. (Cicatriz: banco de render del motor, 2026-08-12.)
- Medir, no opinar. Sondear una escritura ES escribir (no es exploración).
Entrega — la parte de "sacar el trabajo" (promovido 2026-08-08)
El método nació de verificación/UI; la cicatriz de entrega (git, secretos, naming, deploy) vivía fuera, repetida en varios repos. Aquí se consolida.
Naming · C17, C32
- Ids con prefijo por ámbito, no se renumeran (romperían citas cruzadas), y antes de añadir uno se corre el grep de colisión — no basta con mirar.
- El nombre nombra DESTINOS, no rutas: ni el host, ni la IP, ni activo/reserva entran en un FQDN. Se escribe como regla, no como observación.
- Renombrar sin CNAME de cortesía: que lo viejo rompa a la vista, no en silencio.
Secretos · C20, C21, C22
- Una frontera de credenciales por dominio; aislamiento por ServiceAccount (ninguno con acceso a secretos ajenos).
- Capacidad > credencial: se actúa con el secreto, no se revela (revelar lo deja en el transcript); el estado raro se dice con la URL, nunca con el secreto.
- Nunca en git: imperativos / GPG / PushSecret, forzado por
.gitignore; se documenta **cómo
⛔ UNA CREDENCIAL ESCRITA EN EL CORPUS DICE DE QUÉ ES — banco o real — o el lector tiene que
asumir lo peor (Xavier, 2026-08-29: «hay que diferenciar entre el banco de pruebas y lo real»).
El raíl de Entrega/Secretos dice nunca en git, y con eso basta para las reales. Lo que no
cubría es que una credencial de banco escrita sin decir que lo es se lee como una fuga, y el
coste no es teórico: quien la encuentra para el trabajo y la sube como hallazgo de seguridad.
(Cicatriz 2026-08-29: una sesión de ordenación reportó como ⛔ una contraseña en claro en tres
documentos, con razón por lo que veía — y era del banco de pruebas. Nada que arreglar salvo lo
que el corpus no decía.)
⇒ Regla: si una credencial va a estar escrita, va con su ámbito pegado —«banco de pruebas,
no producción»— en la misma línea. Y si no lleva ámbito, el lector debe tratarla como real: esa
asimetría es deliberada, porque el fallo caro es el contrario.
⇒ ⭐ Y la generalización, que vale más que el caso: el corpus no distingue hoy, a la vista, un
sujeto de BANCO de uno REAL — ni en credenciales, ni en endpoints, ni en nombres de host. Un
ejemplo copiable que apunta a producción es el mecanismo de un accidente, no una errata, y no
hace falta que lo vendorices a ningún sitio: ya es peligroso donde está, porque una sesión lo
pega tal cual.
recrearlos**, no el valor.
Git · C24, C25, C38
-
Git canónico único y MEDIDO: se empuja a un remoto y solo ahí; un fallo se mide (dry-run/reintento) antes de concluir "no hay credenciales"; el remoto muerto se elimina localmente.
-
Commits Conventional (
feat/fix/docs/ci+ scope); eldoc:registra el porqué. -
Working-tree compartido:
git addcon ruta explícita (nunca-A),git pullantes de deploy y antes de commitear docs, una tarea = una sesión/worktree. -
⛔ QUIÉN EMPUJA: el
pushes un acto de ÁMBITO, no un acto sobre tus commits (Xavier, 2026-08-29). Unpushpublica todo lo que hay entre el remoto y tu HEAD, lo haya puesto quien lo haya puesto — así que el permiso no se decide por quién escribió el commit, sino por de quién es el repo:-
La ORQUESTADORA empuja en SU ámbito. Es la única que puede: es quien ve si la tarea reencuadra el backlog o cambia algo más, y empuja después de la medición, no antes.
-
La EJECUCIÓN no empuja: se lo dice a su orquestadora. Commitea y lo declara; publicar es de arriba.
-
La SUPERADMIN tiene su propio ámbito —el Método— y ahí se comporta como una orquestadora más. Fuera de él: si ve algo sin empujar, avisa a la orquestadora de ese frente; si no está viva, pregunta a Xavier, y es él quien autoriza a empujarlo todo. ⇒ Testigo obligatorio antes de cualquier
pushen repo compartido:git log @{u}..HEAD— y que todo lo que vas a subir sea de tu ámbito. Si arrastras algo ajeno, dilo en el mensaje en vez de callarlo: eso es lo que convierte un arrastre en un robo silencioso. ⚠️ Y EL LÍMITE DE ESE TESTIGO, que hay que saberse porque parece cubrir más de lo que cubre:git addcon ruta explícita NO protege cuando el cambio ajeno está en el MISMO fichero. Protege contra arrastrar otros ficheros; contra dos sesiones escribiendo en uno solo no distingue nada — la ruta es la misma. (Cicatriz 2026-08-29, y la encontró la sesión de ejecución que purgaba el semáforo mientras la superadmin le editaba la cabecera: paró antes de commitear porque vio en eldiff21 líneas que no eran suyas.) ⇒ ⭐ Lo que sí lo caza es la puerta de los tres números —ficheros staged·filas tocadas·¿todas mías?— pero enrojeciendo por un motivo que no es el tuyo: ahí habría dado22/43en ⛔⛔ Y LA PUERTA HAY QUE ESCRIBIRLA DE MODO QUE ATE, NO QUE INFORME.echo "filas tocadas: $n ¿mías?: $m" # INFORMA — depende de que alguien mire [ "$n" = "$m" ] || { echo "PARA"; exit 1; } # ATA — falla sola
(Cicatriz 2026-08-30: la orquestadora de un frente corre exactamente esta puerta, con el raíl escrito el día anterior y citándola a ella. Le sale
filas tocadas: 3 · ¿mía?: 1esperando1 y 1… y sigue adelante. El commit se lleva dentro la fila de otra sesión, bajo un mensaje que hablaba de otra cosa. Nada se perdió; la atribución, sí.) ⇒ ⭐ El enunciado general, que vale más que el caso — y es la TERCERA cara de la misma familia: el número puede estar bien y el veredicto no vincular. Las tres caras medidas son (a) elrcno sobrevive al transporte —el que lees es el detail—, (b) el rojo crónico, correcto e ilegible por costumbre, y (c) ésta: enrojece bien y no ata nada. ⇒ Un gate sólo ata si rompe el flujo por sí mismo —código de salida, comando que no continúa—, nunca delegando el veredicto en quien lo lee. ⛔⛔ CUARTA CARA, y es la que más engaña: UNA PUERTA CUYO RESULTADO SE LEE DESPUÉS DE ACTUAR NO ES UNA PUERTA — ES UN REGISTRO DE LO QUE YA PASÓ. Aquí el gate calcula bien, enrojece bien y el operador lo lee… y no sirve, porque no hubo ningún instante en el que pudiera parar. (Cicatriz 2026-08-30, y con la puerta puesta: una sesión encadena la comprobación y elgit commiten la misma orden. Sale4 filas tocadas · 2 de 4 mías—debía parar— pero para cuando lee la salida el commit ya está hecho, y publica el bloque a medio escribir de otra sesión bajo un mensaje que hablaba de otra cosa.) ⇒ La forma no es «correr la puerta»: es que la ACCIÓN sea un paso DISTINTO Y POSTERIOR a leerla. Un&&entre la comprobación y el acto convierte el gate en telemetría. Si van en la misma orden, la comprobación tiene que abortar ella misma (cara (c)) — leerla no basta. ⇒ Y el criterio para saber cuál estás escribiendo, que es de la sesión que lo pagó: si el cumplimiento depende de la atención de quien tiene prisa, no has escrito un raíl: has escrito un consejo. Un raíl se cumple aunque no te acuerdes de él. ⇒ ⭐ Y la regla NO es «átalo todo»: es ÁTALO SI ES DECIDIBLE SIN ADIVINAR — y si no lo es, DI POR QUÉ NO LO HAS ATADO. Un raíl que explica por qué no se mecaniza es lo único que impide que el siguiente lo mecanice mal «por completitud». (Caso 2026-08-30, dos prescripciones hermanas y sólo una atable: «una fila viva lleva bloqueEN VUELO» es puramente sintáctico —está el marcador o no está—, así que se ata y pasa de «acuérdate» a «no puedes empujar sin ponerlo». Su hermana, «ninguna sesión se cierra con fila viva», NO se ata a propósito: el fichero no puede saber si el dueño respira —medido:ListAgentspasó de 13 sesiones a 6 en dos minutos, y una de 12 h apareció como recién arrancada—, así que un gate que lo adivinara daría rojos crónicos, o sea la cara (b) de la familia de arriba. Queda como criterio humano, deliberadamente.) ⛔ Y por eso se escribe el NO: una prescripción sin gate y sin explicación se lee como un descuido, y el siguiente que pase «lo arreglará» — creando justo el rojo permanente que este párrafo prohíbe. vez de22/22. Un gate que se pone rojo por causa ajena entrena a desmentirlo, así que cuando salga descuadrado la pregunta no es «¿qué he hecho mal?» sino «¿de quién es lo demás?» — y la respuesta es un mensaje, no uncommit. ⭐ Y lo que de verdad caza el arrastre no es acordarse de mirar eldiff: es MEDIR DOS VECES y que los números no cuadren. (Corrección de la propia sesión que lo frenó, 2026-08-29, y lo dijo contra su propio mérito: «no fue virtud, fue orden — medí el fichero dos veces porque los números de la primera no cuadraban con los de la segunda, y por eso miré eldiff; no porque tuviera presente el raíl».) ⇒ El testigo no es la disciplina de mirar, es la discrepancia entre dos medidas del mismo sujeto, que te obliga a mirar sin depender de que te acuerdes. Y ya sabemos lo que rinde un raíl que depende de acordarse: es procedimiento, no propiedad. (Cicatriz 2026-08-29, y la cometió la superadmin el mismo día que escribió este raíl: al re-estampar el Método en nueve repos, uno de lospusharrastró un commit ajeno que esperaba una decisión. Tenía tres avisos delante —el de otra orquestadora esa mañana, su propia fila del semáforo, y su palabra dada— y corrió la puerta en su repo pero no en los ajenos: aplicó el control donde no hacía falta y lo omitió donde sí. Un raíl recién escrito no protege a quien acaba de escribirlo.) ⚠️ Y no se arregla conpush --force: sobre un repo que tocan varios frentes, deshacer algo ya publicado es peor que el daño. Se avisa a su dueño y lo decide él. ⚙️ AUTORIZACIÓN PERMANENTE de Xavier, 2026-08-29, con su motivo y sus límites (y se escribe así a propósito: en este árbol ya se borró un workflow por llevar «los commits van siempre, automáticos, sin confirmar» — una autorización sin fecha ni motivo es un agujero, no un permiso): si la orquestadora de ese ámbito NO está viva, la superadmin puede empujar — y una orquestadora también, en el mismo caso. Motivo: que él no sea el cuello de botella para publicar trabajo ya terminado de una sesión que ya no existe. ⛔ Y lo que NO cubre, que es la mitad que importa: EMPUJAR NO ES DECIDIR. El permiso alcanza al acto de publicar, nunca a la aprobación del contenido. Si un commit declara que espera una decisión, publicarlo no la toma: se empuja diciéndolo y la decisión sigue pendiente de su dueño. Y antes de usarlo se comprueba que la orquestadora no está viva — el único testigo válido es «escribí y no contestó», jamás «no la veo en el listado». ⛔⛔ Y HAY UN SEGUNDO AGUJERO, PEOR: EL ÍNDICE TAMBIÉN ES COMPARTIDO.git add <ruta>no desmonta lo que otra sesión ya dejó staged, así que tu commit se lleva ficheros que ni nombraste ni tocaste — con ruta explícita, sin-A, y haciendo todo bien. (Cicatriz 2026-08-30, y el sitio es lo que la hace útil: la superadmin commitea dos ficheros nombrados uno a uno; entran tres. El tercero era el trabajo staged de otra sesión, que sobrevivió íntegro pero quedó publicado bajo un mensaje que hablaba de otra cosa — y con él se perdió la explicación que esa sesión estaba escribiendo en su propio mensaje, que era lo valioso. ⭐ Y el asunto de ese commit era, literalmente, «en este árbol git no distingue sesiones», con un cuerpo que decía que un commit ajeno bajo un mensaje ajeno borra la única atribución que existe. Lo cometió mientras lo escribía. Su propia puerta imprimióficheros=3habiendo nombrado 2 — y no paró: cara (c) y cara (d) a la vez.) ⇒ El gesto que falta NO es «no usesgit add -A» —no se usó— sino: si te llevaste algo ajeno, DILO EN EL MENSAJE. Es lo único que devuelve la atribución cuandogitno distingue sesiones, y cuesta una línea. ⇒ Y el sub-raíl operativo:git status --porcelainANTES de tu primergit add. Si hay algo staged que no es tuyo, no es tuyo el índice: o esperas, o lo declaras. -
-
La representación en disco es parte del artefacto: cuando un hash o un runtime consume el checkout, se fija con el repo — eol por
.gitattributesy bit de ejecución — o el testigo mide el checkout, no el contenido (una DERIVA falsa en un clone sin tocar entrena a ignorar el check). (Cicatrices: copia vendorizada en CRLF, 2026-08-11;git archivesale en CRLF.)
Deploy · C12, C13
- Un solo flujo de build, disparado deliberadamente, no por
git push; el legacy es lápida (código muerto conservado hasta MEDIR qué lo ejecuta, no borrado a ciegas). - Todo cambio por git → deploy declarativo; imagen pineada por DIGEST inmutable commiteado; el historial de commits ES el registro de auditoría.
- El testigo de «puedo entregar» no es el push a git: es el artefacto en el registro del que tira el destino. Una cadena de entrega tiene tantas credenciales como saltos, y la que no se ejercita se pudre en silencio — un componente que lleva semanas sin reconstruirse no se sabe si puede entregar. (Cicatriz: robot de registro con 401 en tres repos, mudo durante semanas porque nadie construía, 2026-08-12.)
- El orden de lo irreversible aplica aquí → raíl 11.
La estructura de sesión no vive aquí
Los dos ejes fase × persona y el handoff automático entre fases son procedimiento, no
principio → viven en la capa workflows/, con la capa persona opcional (se activa solo con
interlocutor no-técnico; en infra en solitario, inactiva pero documentada).