Serie AI-Native · Confiabilidad
Arreglé el Bug Tres Veces. El Bug Nunca Fue el Problema.
Resumen de 1 minuto — qué te llevas de esto
Reporté la misma página rota a mi agente de IA tres veces; la tercera vez en mayúsculas. Cada respuesta era cierta —arreglado, mergeado, tests en verde— y la página seguía muerta, porque tres eslabones distintos de la cadena de entrega estaban rotos, y cada capa por encima de ellos seguía reportando éxito. La lección: "listo" es una afirmación sobre el ÚLTIMO eslabón. Verifica a la altitud que experimenta el usuario, o vas a terminar coleccionando tus propios recibos.
Una autopsia de una tarde: un error de cableado, una plataforma de despliegue que te deja plantado con toda cortesía, y una función que nunca había corrido ni una sola vez. ~6 min.
El 2 de agosto le dije a mi agente de IA que un dashboard privado en este mismo portafolio mostraba "Not Found". Me dijo que había arreglado el bug. Revisé: Not Found. Lo reporté de nuevo. Arregló otro bug. Not Found. La tercera vez escribí WHY??? — tres signos de interrogación, la unidad internacional de la paciencia de un stakeholder.
Aquí viene la parte incómoda: cada uno de esos arreglos era correcto. Los tests estaban en verde. Los merges eran reales. Y la página siguió muerta a través de todos ellos, porque yo no estaba peleando contra un bug. Estaba peleando contra una cadena — y una cadena falla exactamente en un eslabón a la vez, revelando la siguiente rotura solo después de arreglar la última.
El modelo mental: una carrera de relevos donde cada corredor se autocalifica su propio tramo. Un arreglo tiene que viajar — código → merge → build → deploy → servir → autenticar → la pantalla del usuario. Cada corredor en ese relevo jurará que corrió un gran tramo, y por lo general tiene razón. Pero la carrera solo se gana cuando el testigo cruza la línea final, y nadie en el medio puede verlo. "Listo" es una afirmación sobre la entrega final. Todo lo anterior es la autoevaluación de un corredor.
Acto 1: dos mitades, ambas correctas, sin apretón de manos
La API del dashboard se autentica leyendo un encabezado HTTP. La página del dashboard llamaba a esa API — y no enviaba ningún encabezado. La API funcionaba bien: rechazaba llamadas anónimas, tal como estaba diseñada. La página funcionaba bien: obtenía sus datos, tal como estaba diseñada. Nadie era dueño de la frase intermedia: la página debe presentar la credencial que la API lee.
El libro de SRE de Google tiene una línea para esto que había leído años atrás y aparentemente archivé bajo "qué lindo": "Note that in a multilayered system, one person's symptom is another person's cause" [1] (Traducción: Nótese que en un sistema de múltiples capas, el síntoma de una persona es la causa de otra). Mi síntoma era un 404. La causa vivía en la costura entre dos componentes que, cada uno por su lado, habían pasado sus propias pruebas. Las costuras no tienen suites de pruebas a menos que escribas una — ese es todo el argumento a favor de las pruebas de contrato, donde Fowler señala "[a] failure in any of these contract tests implies you need to update your test doubles, and probably your code" [2] (Traducción: una falla en cualquiera de estas pruebas de contrato implica que necesitas actualizar tus dobles de prueba, y probablemente tu código). El contrato era lo único que nadie había probado.
Acto 2: la plataforma que te deja plantado educadamente
Así que arreglamos el conexionado, hicimos merge, y le dijimos al usuario (yo) que estaba listo. Not Found.
El arreglo estaba en la rama principal. Las pruebas estaban en verde. Lo que nadie sabía: el plan gratuito de la plataforma de hosting permite — tal como aparece, textualmente, en su propia página de límites — "100 times every 86400 seconds" [3] (Traducción: 100 veces cada 86400 segundos), y una tarde de entusiastas pull requests pequeños había gastado los cien. Cada merge después de eso disparaba nada. Ningún error en el repositorio. Ninguna X roja. La integración de deploy simplemente no respondía, como un buzón sin fondo: acepta tus cartas todo el día.
Mi agente seguía diciéndome "merged ✓", igual que un mesero dice "enseguida se lo traigo" sobre una cocina que lleva cerrada desde el almuerzo. La afirmación era cierta. La afirmación también era inútil, porque "merged" es tu propia letra en tu propio recibo — la pregunta que le importa al cliente es "¿desplegado y funcionando?", y esa pregunta nunca se hizo. El libro de SRE llama a esto la división black-box/white-box: el monitoreo black-box "is symptom-oriented and represents active—not predicted—problems: 'The system isn't working correctly, right now'" [1] (Traducción: está orientado a síntomas y representa problemas activos —no predichos—: 'el sistema no está funcionando correctamente, ahora mismo'). Cada verificación que hicimos fue white-box. El usuario era el único monitor black-box de guardia, y el usuario me estaba cobrando en signos de interrogación.
Acto 3: la función que jamás había corrido
Cuota liberada, deploy forzado, la página por fin carga — y está vacía. El dashboard renderiza tarjetas de reporte que un CLI empuja al sitio. Ese envío, descubrimos ahora, jamás había tenido éxito en toda su vida: el CLI enviaba la credencial bajo un nombre de encabezado; el sitio leía otro distinto. La función había sido probada — contra la mitad del camino que no necesita credenciales. La mitad autenticada tenía cero ejecuciones en producción. No "rara vez corría". Cero.
Una ruta de código que jamás ha corrido en producción no es una función: es un rumor con pruebas unitarias.
¿Por qué tres menciones? La respuesta del queso suizo
El modelo del queso suizo de James Reason describe los accidentes en sistemas de alta tecnología como trayectorias que atraviesan agujeros en múltiples capas defensivas — "some are engineered (alarms, physical barriers, automatic shutdowns, etc), others rely on people" [4] (Traducción: algunas son diseñadas —alarmas, barreras físicas, cierres automáticos, etc.—, otras dependen de las personas). Normalmente se invoca cuando los agujeros se alinean y el desastre se cuela. Mi tarde fue lo inverso, y es la versión que los ingenieros viven a diario: las capas estaban apiladas tan profundo que arreglar un agujero solo revelaba el agujero de la siguiente capa, una queja de usuario a la vez. La defensa en profundidad funciona en ambos sentidos — también ofrece falla en profundidad, con cada capa absorbiendo cortésmente tu arreglo y presentando un nuevo "Not Found".
La cadena solo termina cuando alguien verifica en el último eslabón. En el momento en que por fin usamos un navegador real, con la sesión real del dueño, contra la página real ya desplegada — toda la cadena se iluminó: el arreglo de conexionado no estaba desplegado, el despliegue no estaba ocurriendo, el conducto de datos nunca había llevado agua.
Patrones / Antipatrones
- Patrón — verifica a la altitud del usuario. La definición de "terminado" para "arreglar la página" es un navegador, con sesión iniciada como el usuario, viendo la página. No son las pruebas. No es el merge. Son los píxeles.
- Patrón — cada traspaso asíncrono necesita una confirmación positiva. Merge → deploy es asíncrono. Si no verificas que el despliegue realmente arrancó, el silencio y el éxito son del mismo color.
- Patrón — la pregunta de la resurrección. Para cualquier función que cruce un límite, pregunta: ¿cuándo fue la última vez que esto realmente corrió, de punta a punta, en producción? Si la respuesta honesta es "nunca", tienes un rumor, no una función.
- Antipatrón — coleccionar tus propios comprobantes. "Merged ✓", "tests green", "build passed" son todas afirmaciones que un sistema hace sobre sí mismo. La pantalla del usuario es la única auditoría de un tercero.
- Antipatrón — probar la mitad de un contrato. Si la ruta autenticada necesita un secreto que tu CI — el robot que corre tus pruebas — no tiene, tu CI está probando una función distinta que casualmente comparte el nombre.
El mecanismo, por su nombre: confirmación en la altitud equivocada. Cada capa confirma lo que ella misma hizo, y los cerebros humanos —y los de los agentes— escuchan un coro de confirmaciones como un solo gran "terminado".
El primer principio, en una frase: un cambio solo existe a la altitud en la que el usuario lo experimenta; todo lo anterior es testimonio de una parte interesada.
Los comprobantes
Porque un artículo sobre verificación debería poder verificarse: tres reportes de usuario; tres fallas distintas en la cadena (página→credencial de la API, merge→cuota de build, CLI→nombre del header de la API); un límite de la plataforma de despliegue de 100 cada 86,400 segundos, agotado por completo; diecinueve tarjetas de reporte empujadas a través del conducto que había transportado cero — veinte, contando la tarjeta de reporte de este mismo incidente. Y mi propio motor de rendición de cuentas calificó el turno con 0.57 sobre 1.0. La tarjeta guardada (rc0020 en el superrepositorio) registra mis puntajes docs/reportcards/collection.jsondeclaradosdeclarados — cuatro 1.0 — y el evaluador del motor limita cada puntaje declarado según la solidez de su evidencia al releer la tarjeta; ejecuta anyagent report roadmap sobre ese archivo y arroja 0.57. Mi puntaje declarado y mi calificación, en desacuerdo permanente y en público — un número que luego tuve que transmitir, textual, a la persona que había estado esperando. Recomiendo construir sistemas que te obliguen a decir en voz alta el número incómodo. Es la única razón por la que existe esta autopsia.
Referencias
- Beyer, B., Jones, C., Petoff, J., & Murphy, N. R. (2016). Site Reliability Engineering, Cap. 6: Monitoring Distributed Systems. O'Reilly / Google. sre.google/sre-book/monitoring-distributed-systems
- Fowler, M. (2011). Contract Test. martinfowler.com/bliki/ContractTest.html
- Vercel. Limits — "Deployments per day (Hobby): You are able to deploy 100 times every 86400 seconds (1 day)." (Traducción: "Despliegues por día (Hobby): puedes desplegar 100 veces cada 86400 segundos (1 día)".) vercel.com/docs/limits
- Reason, J. (2000). Human error: models and management. BMJ, 320(7237), 768–770. pmc.ncbi.nlm.nih.gov/articles/PMC1117770
Relacionados
Escrito el mismo día del incidente, a partir de los comprobantes: los PRs, los logs de deploy, la salida del push de despliegue y un mensaje de usuario con tres signos de interrogación. — Paul Jialiang Wu · agentic-portfolio-lovat.vercel.app