{
    "version" : "https://jsonfeed.org/version/1.1",
    "title" : "Misael Zapata",
    "description": "Sitio personal de Misael Zapata",
    "home_page_url" : "https://misael.org/",
    "feed_url" : "https://misael.org/es/index.feed",
    "icon" : "https://misael.org/apple-touch-icon.png",
    "favicon" : "https://misael.org/favicon.ico",
    "author" : {
        "name" : "Misael Zapata",
        "url": "https://github.com/misaelzapata",
        "avatar": "https://misael.org/images/avatar.webp"
    },
    "items" : [
    {
        "title" : "La actualización que nadie pidió",
        "date_published" : "2026-06-28T00:00:00Z",
        "date_modified" : "2026-06-28T00:00:00Z",
        "id" : "https://misael.org/es/the-update-nobody-asked-for/",
        "url" : "https://misael.org/es/the-update-nobody-asked-for/",
        "summary": "Las apps ya no se actualizan para mejorar: se actualizan para subirse al evento. Un recorrido, con números reales y enlazados, desde la fiebre de updates del Mundial 2026 hasta cómo se diseñó y se masificó el FOMO que la hace posible — y el ciclo de hype que se acorta cada vez más, del offshoring a los \u0026lsquo;agentes\u0026rsquo; de IA.",
        "content_html" : "\u003cp\u003eAbrí cualquier app esta semana y algo cambió de un día para el otro. Pedí un Uber y el autito en el mapa tenía la bandera de mi selección encima. Abrí PedidosYa y me saltó un cupón que apareció \u003cem\u003eporque alguien metió un gol\u003c/em\u003e del otro lado del planeta. Entré a Duolingo a hacer mi lección y Duo estaba vestido con la camiseta de una selección. Mandé un mensaje con el emoji de la pelota y se transformó en el balón oficial del Mundial. Busqué un resultado en Google y la pantalla me tiró una animación de festejo.\u003c/p\u003e\n\u003cp\u003eNinguna de esas actualizaciones arregló nada. Ninguna hizo la app más rápida, más segura, ni más útil. Todas existen por la misma razón: hay una pelotita rodando en algún estadio y nadie quiere quedarse afuera de la conversación.\u003c/p\u003e\n\u003cp\u003eHubo un tiempo en que una actualización de software era una promesa. Esperabas meses a que arreglaran \u003cem\u003eese\u003c/em\u003e bug, a que la batería durara un poco más, a que la cosa dejara de crashear. La actualización impactaba a millones de personas y por eso se la tomaba en serio. Hoy desplegamos a esos mismos millones de dispositivos, coordinando cinco plataformas a la vez, para ponerle una banderita a un ícono. Y la semana que viene lo vamos a sacar y nadie se va a acordar de que existió.\u003c/p\u003e\n\u003cp\u003eEste artículo trata de eso: del número y no del contenido. De cómo una nimiedad termina afectando a alguien en la otra punta del mundo y después desaparece tan rápido como apareció. De cómo se diseñó —deliberadamente— el resorte psicológico que hace que esto funcione. Y de por qué este patrón es exactamente el mismo que el de OpenClaw, el de las cripto, el del IoT, el del big data, y el de cuando movimos el trabajo de América a Asia.\u003c/p\u003e\n\u003cdiv class=\"details admonition note open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z\"/\u003e\u003c/svg\u003e\u003c/span\u003eSobre las fuentes\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eTodo número en este artículo está enlazado a su fuente original, pública y gratis. Si una afirmación no tiene link, es una opinión mía y está escrita como tal. Las imágenes de apps son \u003cstrong\u003ereconstrucciones ilustrativas\u003c/strong\u003e (no capturas reales): el patrón importa más que el pixel, y cada caso está citado a su anuncio oficial.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"el-mundial-como-deploy-masivo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-mundial-como-deploy-masivo\" class=\"header-mark\" aria-label=\"Header mark for 'El Mundial como deploy masivo'\"\u003e\u003c/a\u003e1 El Mundial como deploy masivo\u003c/h2\u003e\u003cp\u003eRepasemos lo que pasó, con nombre y apellido. No fue una app: fueron todas.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/world-cup-apps.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/world-cup-apps.png\" data-sub-html=\"\u003ch2\u003eLa misma pelota disparando un despliegue en cada pantalla. Reconstrucción ilustrativa; cada caso está enlazado abajo a su fuente.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/world-cup-apps.png\" srcset=\"/the-update-nobody-asked-for/images/world-cup-apps_hu17df65ef2da8e04ca35f5840b1d35b43_93822_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/world-cup-apps_hu17df65ef2da8e04ca35f5840b1d35b43_93822_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/world-cup-apps_hu17df65ef2da8e04ca35f5840b1d35b43_93822_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"822\" width=\"1080\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eLa misma pelota disparando un despliegue en cada pantalla. Reconstrucción ilustrativa; cada caso está enlazado abajo a su fuente.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003e\u003cstrong\u003eUber\u003c/strong\u003e dejó que personalizaras el ícono del auto en el mapa con la bandera de tu selección, y lanzó \u003cem\u003e\u0026ldquo;Defeat Deals\u0026rdquo;\u003c/em\u003e: si tu equipo queda eliminado, te tira un 30% de descuento en un viaje futuro (\u003ca href=\"https://www.uber.com/us/en/newsroom/traveling-for-soccer-this-summer/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eUber Newsroom\u003c/a\u003e, \u003ca href=\"https://thepointsguy.com/news/uber-world-cup-features/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eThe Points Guy\u003c/a\u003e). Tu duelo deportivo, convertido en un cupón.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePedidosYa\u003c/strong\u003e lanzó \u003cem\u003e\u0026ldquo;Si hay gol, hay cupón\u0026rdquo;\u003c/em\u003e: cada vez que se mete un gol en el torneo, la app libera cupones en tiempo real. La campaña promete repartir en promedio más de \u003cstrong\u003e$2.700 millones de pesos\u003c/strong\u003e en cupones, más de \u003cstrong\u003e$77 millones por día de partido\u003c/strong\u003e, en 14 países de Latinoamérica, del 11 de junio al 19 de julio (\u003ca href=\"https://www.infobae.com/america/agencias/2026/06/12/pedidosya-celebra-la-fiesta-del-futbol-entregando-millones-de-cupones-con-una-campana-inedita-si-hay-gol-hay-cupon/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eInfobae\u003c/a\u003e, \u003ca href=\"https://mercado.com.ar/marketing/pedidosya-activa-cupones-de-descuento-por-cada-gol-con-una-campana-regional\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eRevista Mercado\u003c/a\u003e). Un gol en un estadio dispara una bajada de cupones que hace que millones de personas abran la app corriendo antes de que se agoten. Eso es un evento de infraestructura, disparado por una pelota.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDuolingo\u003c/strong\u003e armó el \u003cem\u003eDuo Cup\u003c/em\u003e: \u003cstrong\u003e48 trajes de selecciones\u003c/strong\u003e que desbloqueás completando lecciones, con un calendario \u003cstrong\u003ealeatorio\u003c/strong\u003e para que no sepas qué traje toca y entres todos los días a ver (\u003ca href=\"https://www.duolingo.com/duo_cup_suits_intro\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo\u003c/a\u003e, \u003ca href=\"https://duolingo.fandom.com/wiki/Duo_Cup\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo Wiki\u003c/a\u003e). El marketing es nacionalismo puro: \u003cem\u003e\u0026ldquo;la selección te necesita, desbloqueá tu traje\u0026rdquo;\u003c/em\u003e (\u003ca href=\"https://www.facebook.com/duolingo/posts/the-national-team-needs-you-unlock-your-duo-cup-avatar-suit-%EF%B8%8F/1440333504788285/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eDuolingo en Facebook\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003eY los gigantes no se quedaron atrás:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eWhatsApp\u003c/strong\u003e, junto a Adidas y FIFA, hizo que el emoji de la pelota se transforme en \u003cem\u003eTrionda\u003c/em\u003e, el balón oficial, durante todo el torneo — más stickers temáticos, efectos en las llamadas y un directorio de canales del Mundial (\u003ca href=\"https://www.socialmediatoday.com/news/meta-announces-world-cup-features-across-its-apps/822741/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSocial Media Today\u003c/a\u003e, \u003ca href=\"https://www.clarosports.com/futbol/mundial-2026/el-mundial-2026-llego-a-whatsapp-con-golazo-como-utilizar-el-nuevo-emoji-animado-del-balon-trionda/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eClaro Sports\u003c/a\u003e).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGoogle\u003c/strong\u003e lanzó una serie de \u003cstrong\u003e69 Doodles\u003c/strong\u003e (36 obras únicas en 189 mercados, del 11 de junio al 20 de julio) y \u003cem\u003eeaster eggs\u003c/em\u003e en la búsqueda que te festejan la victoria o te consuelan la derrota (\u003ca href=\"https://www.mediapost.com/publications/article/415745/google-touts-ai-search-for-world-cup-games.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eMediaPost\u003c/a\u003e).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInstagram\u003c/strong\u003e sumó alertas de resultados en vivo, stickers y efectos temáticos (\u003ca href=\"https://www.socialmediatoday.com/news/meta-announces-world-cup-features-across-its-apps/822741/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSocial Media Today\u003c/a\u003e).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTikTok\u003c/strong\u003e llegó a lanzar \u003cem\u003eTikTok Pro Events\u003c/em\u003e, \u003cstrong\u003euna app aparte\u003c/strong\u003e dedicada al Mundial, donde ganas \u0026ldquo;Stars\u0026rdquo; por participar (\u003ca href=\"https://techcrunch.com/2026/06/03/tiktok-launches-tiktok-pro-events-an-app-for-cultural-moments-like-the-fifa-world-cup/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eTechCrunch\u003c/a\u003e).\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eUna app entera, nueva, para un evento de cinco semanas. Ese es el nivel de inversión que dispara una pelotita.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/app-shots.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/app-shots.png\" data-sub-html=\"\u003ch2\u003eAsí se veían algunas: el emoji Trionda y el «GOAL» en WhatsApp, el Doodle de festejo de Google, el creativo de PedidosYa, TikTok Pro Events y el hub del Mundial en Instagram. De la banderita de Uber, en cambio, no se publicó ninguna captura. Materiales de prensa y oficiales, uso editorial.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/app-shots.png\" srcset=\"/the-update-nobody-asked-for/images/app-shots_hud867f7e765278cc054c80769c48d7d39_369123_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/app-shots_hud867f7e765278cc054c80769c48d7d39_369123_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/app-shots_hud867f7e765278cc054c80769c48d7d39_369123_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"584\" width=\"1024\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eAsí se veían algunas: el emoji Trionda y el «GOAL» en WhatsApp, el Doodle de festejo de Google, el creativo de PedidosYa, TikTok Pro Events y el hub del Mundial en Instagram. De la banderita de Uber, en cambio, no se publicó ninguna captura. Materiales de prensa y oficiales, uso editorial.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"el-caso-de-duolingo-te-venden-la-camiseta-de-tu-país\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-caso-de-duolingo-te-venden-la-camiseta-de-tu-pa%c3%ads\" class=\"header-mark\" aria-label=\"Header mark for 'El caso de Duolingo: te venden la camiseta de tu país'\"\u003e\u003c/a\u003e2 El caso de Duolingo: te venden la camiseta de tu país\u003c/h2\u003e\u003cp\u003eEl de Duolingo es el más explícito sobre lo que en realidad se vende. No se sumó al Mundial con una animación gratis: abrió una \u003cstrong\u003etienda\u003c/strong\u003e. La \u003cem\u003eCopa Duo\u003c/em\u003e tiene una \u0026ldquo;Tienda de la Copa Duo\u0026rdquo; que te invita a \u003cem\u003e\u0026ldquo;comprar los uniformes de tus equipos favoritos\u0026rdquo;\u003c/em\u003e a \u003cstrong\u003eUS$ 1,99 cada uno\u003c/strong\u003e, con un reloj de 21 días corriendo y una sección de \u0026ldquo;ofertas especiales: más uniformes, más ahorro\u0026rdquo; para que compres en combo. Casi todas las selecciones se pagan; apenas unas pocas vienen gratis.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/duolingo-store.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/duolingo-store.png\" data-sub-html=\"\u003ch2\u003eLa Tienda de la Copa Duo: la camiseta de tu selección cuesta US$ 1,99. Argentina, Francia, Croacia, España, México, Japón, Senegal… casi todas pagas, con contador de 21 días y descuentos por combo. Capturas propias, junio 2026.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/duolingo-store.png\" srcset=\"/the-update-nobody-asked-for/images/duolingo-store_hu819a2c0d4ecd5b0fd51e678a67ce75a6_257264_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/duolingo-store_hu819a2c0d4ecd5b0fd51e678a67ce75a6_257264_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/duolingo-store_hu819a2c0d4ecd5b0fd51e678a67ce75a6_257264_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"712\" width=\"906\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eLa Tienda de la Copa Duo: la camiseta de tu selección cuesta US$ 1,99. Argentina, Francia, Croacia, España, México, Japón, Senegal… casi todas pagas, con contador de 21 días y descuentos por combo. Capturas propias, junio 2026.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eEl encuadre es lo revelador: la app dice \u003cem\u003e\u0026quot;¡Muestra tu apoyo con el uniforme de Argentina!\u0026quot;\u003c/em\u003e y el único botón debajo es \u003cstrong\u003ecomprar por US$ 1,99\u003c/strong\u003e. Si de verdad quieres apoyar a tu selección, tienes que comprarla: en esa pantalla, querer y pagar son la misma acción. Una app de idiomas convirtió el sentido de pertenencia a un país en un ítem cosmético con precio, y le sumó un contador regresivo que acorta la decisión.\u003c/p\u003e\n\u003cp\u003eHabía una alternativa más interesante a la mano: que la app asignara a cada usuario un país y que en las tablas globales se viera, país por país, a la persona real detrás de cada bandera — una imagen concreta de lo global que es el evento. Se eligió, en cambio, la mecánica que mejor convierte: cobrar por el uniforme.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEl objetivo no es mejorar el software, sino aprovechar una ola de atención que ya existe — antes de que se disipe.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"no-es-el-fútbol-es-cada-evento-en-cada-país\" class=\"headerLink\"\u003e\n    \u003ca href=\"#no-es-el-f%c3%batbol-es-cada-evento-en-cada-pa%c3%ads\" class=\"header-mark\" aria-label=\"Header mark for 'No es el fútbol: es cada evento, en cada país'\"\u003e\u003c/a\u003e3 No es el fútbol: es cada evento, en cada país\u003c/h2\u003e\u003cp\u003eEs fácil mirar todo esto y pensar \u0026ldquo;bueno, es el Mundial, pasa cada cuatro años\u0026rdquo;. Pero el fútbol es solo el ejemplo más visible de esta semana. El mecanismo es global y no para nunca: siempre hay un evento del cual colgarse, en algún calendario, en alguna cultura.\u003c/p\u003e\n\u003cp\u003eEn \u003cstrong\u003eChina\u003c/strong\u003e, el \u003cem\u003eSingles Day\u003c/em\u003e (11.11) que inventó Alibaba convirtió una fecha sin sentido —el 11 del 11— en el evento de compras más grande del planeta: solo Alibaba facturó \u003cstrong\u003eUS$84.500 millones en 2021\u003c/strong\u003e, y junto con JD.com superaron los \u003cstrong\u003eUS$139.000 millones\u003c/strong\u003e en un día (\u003ca href=\"https://www.cnbc.com/2021/11/12/china-singles-day-2021-alibaba-jd-hit-record-139-billion-of-sales.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCNBC\u003c/a\u003e, \u003ca href=\"https://chainstoreage.com/alibaba-sets-new-singles-day-record-845-billion-sales\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eChain Store Age\u003c/a\u003e). Una fecha fabricada, una app, una urgencia de un día.\u003c/p\u003e\n\u003cp\u003eEn \u003cstrong\u003eIndia\u003c/strong\u003e, no es fútbol: es cricket. Cuando juega la IPL, la app de fantasy \u003cstrong\u003eDream11\u003c/strong\u003e —que dice tener más de \u003cstrong\u003e200 millones de usuarios\u003c/strong\u003e— llega a \u003cstrong\u003emás de 15 millones de usuarios concurrentes\u003c/strong\u003e en el primer día del torneo, con el cricket representando el \u003cstrong\u003e87% de todo lo que se juega\u003c/strong\u003e (\u003ca href=\"https://restofworld.org/2023/dream11-fantasy-cricket-app-india/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eRest of World\u003c/a\u003e, \u003ca href=\"https://en.wikipedia.org/wiki/Dream11\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e). Un país entero refrescando una app al ritmo de cada over.\u003c/p\u003e\n\u003cp\u003eEn \u003cstrong\u003eMedio Oriente\u003c/strong\u003e, el evento es el calendario lunar: para Ramadán, super-apps como \u003cstrong\u003eCareem\u003c/strong\u003e reorganizan la experiencia entera alrededor del iftar y el suhoor —pedidos programados, modos especiales, funciones de donación— porque el día mismo cambia de forma (\u003ca href=\"https://blog.careem.com/posts/ramadan-trends-2025\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCareem\u003c/a\u003e, \u003ca href=\"https://www.arabianbusiness.com/culture-society/careem-shares-ramadan-2025-trends-massive-remittances-to-india-and-pakistan-most-popular-iftar-orders-top-bookings\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eArabian Business\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003eBlack Friday, Año Nuevo Lunar, Halloween, el Super Bowl, Diwali: cambian el país y la excusa, pero la mecánica es idéntica. Siempre hay una ola, y siempre hay una app esperándote para que no te la pierdas. La pregunta interesante no es \u003cem\u003epor qué\u003c/em\u003e lo hacen —es obvio: convierte—. La pregunta es \u003cem\u003ecómo\u003c/em\u003e llegamos a un mundo donde esto es el comportamiento por defecto de todo el software que tocamos.\u003c/p\u003e\n\u003ch2 id=\"cómo-se-masificó-esto\" class=\"headerLink\"\u003e\n    \u003ca href=\"#c%c3%b3mo-se-masific%c3%b3-esto\" class=\"header-mark\" aria-label=\"Header mark for 'Cómo se masificó esto'\"\u003e\u003c/a\u003e4 Cómo se masificó esto\u003c/h2\u003e\u003cp\u003eNo fue un accidente. El FOMO de hoy es el resultado de una idea de diseño que se fue puliendo durante medio siglo hasta volverse el motor por defecto de la industria.\u003c/p\u003e\n\u003cp\u003eEmpieza, antes que internet, con una intuición económica. En \u003cstrong\u003e1971\u003c/strong\u003e, el premio Nobel \u003cstrong\u003eHerbert Simon\u003c/strong\u003e escribió la frase que define todo lo que vino después: \u003cem\u003e\u0026ldquo;una riqueza de información crea una pobreza de atención\u0026rdquo;\u003c/em\u003e (\u003ca href=\"https://en.wikipedia.org/wiki/Attention_economy\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eeconomía de la atención, Wikipedia\u003c/a\u003e). Si la información es infinita y la atención es finita, entonces la atención es el recurso escaso — y donde hay un recurso escaso, se arma un mercado para explotarlo.\u003c/p\u003e\n\u003cp\u003eEse mercado encontró su mecánica en la \u003cstrong\u003erecompensa variable\u003c/strong\u003e, el mismo principio que hace adictivas a las máquinas tragamonedas: no sabes si al tirar de la palanca vas a ganar algo o nada, y esa incertidumbre —no el premio— es lo que te mantiene tirando. En 2014, \u003cstrong\u003eNir Eyal\u003c/strong\u003e lo empaquetó como manual de producto en \u003cem\u003eHooked\u003c/em\u003e: gatillo, acción, recompensa variable, inversión, repetir (\u003ca href=\"https://www.thebehavioralscientist.com/articles/an-incomplete-loop-a-review-of-nir-eyals-hooked\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ereseña en The Behavioral Scientist\u003c/a\u003e). El ex-diseñador de Google \u003cstrong\u003eTristan Harris\u003c/strong\u003e lo dijo más crudo: tu teléfono es \u003cem\u003e\u0026ldquo;una máquina tragamonedas en el bolsillo\u0026rdquo;\u003c/em\u003e, y cada vez que tiras para refrescar el feed, estás jugando (\u003ca href=\"https://archive-yaleglobal.yale.edu/content/smartphone-addiction-slot-machine-your-pocket\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eYaleGlobal\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003eY no es una teoría conspirativa: lo confesó uno de los que lo construyó. En 2017, \u003cstrong\u003eSean Parker\u003c/strong\u003e, primer presidente de Facebook, contó que la pregunta de diseño era literalmente \u003cem\u003e\u0026quot;¿cómo consumimos la mayor cantidad posible de tu tiempo y tu atención consciente?\u0026quot;\u003c/em\u003e. La respuesta fue darte \u003cem\u003e\u0026ldquo;un golpecito de dopamina\u0026rdquo;\u003c/em\u003e cada tanto —un like, un comentario— para crear un \u003cem\u003e\u0026ldquo;bucle de validación social\u0026rdquo;\u003c/em\u003e que, dijo, \u003cem\u003e\u0026ldquo;explota una vulnerabilidad de la psicología humana\u0026rdquo;\u003c/em\u003e. Su cierre: \u003cem\u003e\u0026ldquo;Dios sabe lo que le está haciendo al cerebro de nuestros hijos\u0026rdquo;\u003c/em\u003e (\u003ca href=\"https://www.cnbc.com/2017/11/09/facebooks-sean-parker-on-social-media.html\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eCNBC\u003c/a\u003e, \u003ca href=\"https://www.axios.com/2017/12/15/sean-parker-facebook-was-designed-to-exploit-human-vulnerability-1513306782\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eAxios\u003c/a\u003e). \u003cem\u003e\u0026ldquo;Lo entendíamos conscientemente. Y lo hicimos igual.\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eDe ahí salieron las dos mecánicas que masificaron el FOMO hasta hacerlo invisible:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLas rachas de Snapchat.\u003c/strong\u003e Quizás el mecanismo más coercitivo del software de consumo: no solo te premia por usar la app todos los días, te \u003cem\u003ecastiga\u003c/em\u003e por parar. La racha es un número que construiste con un amigo y que se borra si fallas un día. La investigación lo liga directamente con dependencia del teléfono y con FOMO en adolescentes (\u003ca href=\"https://www.sciencedirect.com/science/article/pii/S2772503023000476\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eScienceDirect\u003c/a\u003e). El miedo no es a perderte algo lindo: es a perder algo que ya tienes.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEl Spotify Wrapped.\u003c/strong\u003e Aquí está el paciente cero del FOMO-por-compartir, y es genial de lo astuto que es. En 2016, Spotify tomó tus propios datos —los que normalmente te dan miedo— y te los devolvió envueltos como un regalo de fin de año, listo para postear. Convirtió la vigilancia en celebración. Pasó de \u003cstrong\u003e30 millones\u003c/strong\u003e de usuarios que lo abrían en 2017 a \u003cstrong\u003e156 millones\u003c/strong\u003e en 2022, y en 2023 generó \u003cstrong\u003emás de 2.000 millones de impresiones\u003c/strong\u003e en redes — publicidad gratis hecha por ti, porque no querías quedarte afuera del día en que todos postean su Wrapped (\u003ca href=\"https://en.wikipedia.org/wiki/Spotify_Wrapped\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e, \u003ca href=\"https://theconversation.com/spotify-wrapped-success-story-unpacked-what-are-the-takeaways-251337\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eThe Conversation\u003c/a\u003e). Una vez que una app demostró que podías hacer que millones se promocionaran solos un día fijo del año, todas quisieron su propio \u0026ldquo;Wrapped\u0026rdquo;. El Duo Cup es el Wrapped del Mundial.\u003c/p\u003e\n\u003cp\u003eY como toda mecánica llevada al extremo, tiene su caricatura: \u003cstrong\u003eBeReal\u003c/strong\u003e. Una app cuya \u003cem\u003eúnica\u003c/em\u003e función era el FOMO en estado puro — una notificación a una hora aleatoria, dos minutos para sacar la foto, el pánico de \u0026ldquo;es hora de BeReal\u0026rdquo;. Funcionó tan bien que el \u003cstrong\u003e68% de los usuarios abría la app dentro de los 3 minutos\u003c/strong\u003e de la notificación. Y después se evaporó, como se evapora todo en la economía de la atención.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/massification-decline.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/massification-decline.png\" data-sub-html=\"\u003ch2\u003eLa misma curva, espejada. Spotify Wrapped masificándose (usuarios que lo abren cada diciembre) y BeReal desinflándose (usuarios activos mensuales) tras su pico de 2022. Fuentes: Wikipedia (Wrapped), Business of Apps (BeReal).\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/massification-decline.png\" srcset=\"/the-update-nobody-asked-for/images/massification-decline_hu0334bca45f40272ae75340b120c34789_80779_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/massification-decline_hu0334bca45f40272ae75340b120c34789_80779_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/massification-decline_hu0334bca45f40272ae75340b120c34789_80779_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"540\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eLa misma curva, espejada. Spotify Wrapped masificándose (usuarios que lo abren cada diciembre) y BeReal desinflándose (usuarios activos mensuales) tras su pico de 2022. Fuentes: Wikipedia (Wrapped), Business of Apps (BeReal).\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eBeReal pasó de \u003cstrong\u003e73 millones\u003c/strong\u003e de usuarios activos mensuales en agosto de 2022 a \u003cstrong\u003e33 millones\u003c/strong\u003e en marzo de 2023, y sus descargas se desplomaron un \u003cstrong\u003e60%\u003c/strong\u003e de 2023 a 2024 (\u003ca href=\"https://www.businessofapps.com/data/bereal-statistics/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eBusiness of Apps\u003c/a\u003e, \u003ca href=\"https://en.wikipedia.org/wiki/BeReal\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e). El FOMO sube y baja con la misma curva: la atención que sube como cohete es la misma que después no sostiene nada. Eso vale para una app entera, y vale —a escala de cuatro semanas— para una banderita en un autito.\u003c/p\u003e\n\u003ch2 id=\"el-número-no-el-contenido\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-n%c3%bamero-no-el-contenido\" class=\"header-mark\" aria-label=\"Header mark for 'El número, no el contenido'\"\u003e\u003c/a\u003e5 El número, no el contenido\u003c/h2\u003e\u003cp\u003eLo que une todo esto es que dejamos de medir el aporte y empezamos a medir el momento. Importa estar, no qué dices. Y no son solo las apps: los medios serios también se suman al número y no al contenido. Una noticia que antes duraba semanas hoy dura días, porque lo que importa no es la profundidad sino llegar a tiempo a la ola.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/newspapers-real.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/newspapers-real.png\" data-sub-html=\"\u003ch2\u003eLas tapas reales del viernes 12 de junio de 2026, el día después de la inauguración: 20 portadas de 11 países (vía kiosko.net). De Clarín al New York Times, de L\u0026rsquo;Équipe al Daily Mail, todas le dieron espacio en tapa. Reproducidas para comentario editorial.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/newspapers-real.png\" srcset=\"/the-update-nobody-asked-for/images/newspapers-real_hucf4cdc94043ded4365de8e8818a8de67_2558100_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/newspapers-real_hucf4cdc94043ded4365de8e8818a8de67_2558100_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/newspapers-real_hucf4cdc94043ded4365de8e8818a8de67_2558100_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"1490\" width=\"1274\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eLas tapas reales del viernes 12 de junio de 2026, el día después de la inauguración: 20 portadas de 11 países (vía kiosko.net). De Clarín al New York Times, de L\u0026rsquo;Équipe al Daily Mail, todas le dieron espacio en tapa. Reproducidas para comentario editorial.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eEsto está medido. Un estudio en \u003cem\u003eNature Communications\u003c/em\u003e, \u003ca href=\"https://www.nature.com/articles/s41467-019-09311-w\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cem\u003e\u0026ldquo;Accelerating dynamics of collective attention\u0026rdquo;\u003c/em\u003e\u003c/a\u003e (\u003ca href=\"https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6465266/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ecopia gratis en PMC\u003c/a\u003e), encontró que un hashtag entre los 50 más populares de Twitter se mantenía arriba unas \u003cstrong\u003e17,5 horas en 2013\u003c/strong\u003e y solo \u003cstrong\u003e11,9 horas en 2016\u003c/strong\u003e. El mismo patrón aparece en taquillas de cine, en citas científicas, en Google Books cien años atrás. La conclusión de los autores: la atención colectiva tiene un tamaño fijo, pero cada vez metemos más cosas a competir por ella, así que cada tema se quema más rápido y el siguiente lo pisa enseguida.\u003c/p\u003e\n\u003cp\u003eEso es el FOMO a escala de civilización. El \u003cem\u003efear of missing out\u003c/em\u003e —medido desde 2013 con la \u003ca href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC10943642/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eescala de Przybylski\u003c/a\u003e, que lo correlaciona con ansiedad, peor sueño y uso problemático del teléfono— dejó de ser un problema personal para convertirse en el modelo de negocio. La banderita de Uber, el cupón de PedidosYa, el traje de Duolingo: las tres apuestan a que tienes miedo de quedarte afuera. Y casi siempre aciertan.\u003c/p\u003e\n\u003ch2 id=\"el-costo-invisible-de-la-fiesta\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-costo-invisible-de-la-fiesta\" class=\"header-mark\" aria-label=\"Header mark for 'El costo invisible de la fiesta'\"\u003e\u003c/a\u003e6 El costo invisible de la fiesta\u003c/h2\u003e\u003cp\u003eNada de esto es gratis, aunque a ti te llegue gratis.\u003c/p\u003e\n\u003cp\u003eCada una de estas \u0026ldquo;celebraciones\u0026rdquo; es un despliegue real: builds nuevas para iOS y Android, configs remotas, feature flags, banners, assets, telemetría para medir cuánta gente tocó la banderita. Multiplícalo por cada app que se sumó al Mundial y por cada plataforma que mantienen en paralelo. Es una cantidad enorme de ingeniería, de datos y de energía gastada en cosas que se van a borrar en cuatro semanas.\u003c/p\u003e\n\u003cp\u003eY los datos no son abstractos. La electricidad que consumen los data centers del mundo se va a \u003cstrong\u003emás que duplicar para 2030, hasta unos 945 TWh\u003c/strong\u003e —aproximadamente todo lo que consume Japón en un año— según la \u003ca href=\"https://www.iea.org/reports/energy-and-ai/executive-summary\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eAgencia Internacional de Energía\u003c/a\u003e. No toda esa energía es por banderitas de fútbol, obvio. Pero cada feature efímera, cada evento de telemetría, cada bajada de cupones en tiempo real para millones, suma a una infraestructura que ya pesa como un país entero. Generamos océanos de datos para celebrar algo que vamos a olvidar. Y eso es solo la electricidad: el mundo ya produce \u003ca href=\"https://unitar.org/about/news-stories/press/global-e-waste-monitor-2024-electronic-waste-rising-five-times-faster-documented-e-waste-recycling\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e62 millones de toneladas de basura electrónica al año\u003c/a\u003e, y se recicla cinco veces más lento de lo que crece.\u003c/p\u003e\n\u003cp\u003eY hay un costo que casi nadie en estas oficinas considera: no todo el mundo tiene datos para gastar en una banderita. La GSMA calcula que \u003cstrong\u003e3.100 millones de personas —el 38% del planeta— tienen señal de internet móvil encima pero no la usan\u003c/strong\u003e, y una de las barreras principales es el costo (\u003ca href=\"https://www.gsma.com/r/wp-content/uploads/2024/10/The-State-of-Mobile-Internet-Connectivity-Report-Key-Findings-2024.pdf\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGSMA, \u003cem\u003eState of Mobile Internet Connectivity 2024\u003c/em\u003e\u003c/a\u003e). La \u003ca href=\"https://a4ai.org/affordable-internet-is-1-for-2/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eAlliance for Affordable Internet\u003c/a\u003e considera \u0026ldquo;asequible\u0026rdquo; 1 GB a un 2% del ingreso mensual o menos; en 99 países de ingresos bajos y medios, \u003cstrong\u003esolo 31 llegan a esa meta\u003c/strong\u003e, y el resto paga en promedio el \u003cstrong\u003e5,76% de su sueldo por 1 GB\u003c/strong\u003e. En Argentina, donde los planes móviles cuestan \u003ca href=\"https://www.cronista.com/infotechnology/actualidad/argentina-tiene-uno-de-los-planes-de-celular-mas-caros-y-vale-cuatro-veces-el-precio-uruguayo/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ehasta cuatro veces lo que en Uruguay\u003c/a\u003e, racionar datos es la norma —tan así que en junio de 2026 una empresa lanzó un \u003ca href=\"https://www.canal26.com/general/2026/06/26/internet-sin-vueltas-en-la-calle-como-funciona-el-nuevo-wifi-prepago-de-telecentro-que-se-puede-usar-sin-ser-cliente/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003cem\u003e\u0026ldquo;WiFi prepago\u0026rdquo;\u003c/em\u003e en la vía pública, pensado justamente para cuando te quedás sin datos\u003c/a\u003e—. El que vive a base de QR de WiFi gratis, el que apaga los datos hasta llegar a una red conocida, no es un caso raro: es media humanidad. Para esa persona, una app que se infla de animaciones, telemetría y assets de un evento que no pidió no es una celebración: es un peaje. Megabytes de su plan, quemados para que una marca participe de una tendencia.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/growth.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/growth.png\" data-sub-html=\"\u003ch2\u003eTres curvas detrás de la fiebre: el evento (Singles Day, GMV en US$), el dato (GB por smartphone) y el despliegue (apps nuevas en iOS por día). Todo crece; nada se detiene. Fuentes: Wikipedia, Ericsson Mobility Report 2025, 42matters.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/growth.png\" srcset=\"/the-update-nobody-asked-for/images/growth_hue249510a9064dc57cb86e1df8cd3656b_79000_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/growth_hue249510a9064dc57cb86e1df8cd3656b_79000_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/growth_hue249510a9064dc57cb86e1df8cd3656b_79000_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"420\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eTres curvas detrás de la fiebre: el evento (Singles Day, GMV en US$), el dato (GB por smartphone) y el despliegue (apps nuevas en iOS por día). Todo crece; nada se detiene. Fuentes: Wikipedia, Ericsson Mobility Report 2025, 42matters.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"el-desfile-de-modas-que-nadie-recuerda\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-desfile-de-modas-que-nadie-recuerda\" class=\"header-mark\" aria-label=\"Header mark for 'El desfile de modas que nadie recuerda'\"\u003e\u003c/a\u003e7 El desfile de modas que nadie recuerda\u003c/h2\u003e\u003cp\u003eAquí está la tesis grande. El Mundial es un caso fácil porque tiene fecha de inicio y de fin. Pero la industria entera funciona así: una moda aparece, todos se cuelgan, dura unos meses, afecta a gente que ni la pidió, y desaparece. Y nadie vuelve a hablar de eso.\u003c/p\u003e\n\u003cp\u003eEl ejemplo más fresco es \u003cstrong\u003eOpenClaw\u003c/strong\u003e. ¿Te acuerdas? Empezó como un proyecto personal llamado Clawdbot, se renombró a Moltbot, después a OpenClaw, y en \u003cstrong\u003efebrero de 2026 pasó las 100.000 estrellas en GitHub\u003c/strong\u003e —y para marzo ya superaba a React, que rondaba las 243.000—, convirtiéndose en uno de los repos no-agregadores más estrellados de la historia en tan poco tiempo (\u003ca href=\"https://www.kdnuggets.com/openclaw-explained-the-free-ai-agent-tool-going-viral-already-in-2026\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eKDnuggets\u003c/a\u003e, \u003ca href=\"https://en.wikipedia.org/wiki/OpenClaw\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eWikipedia\u003c/a\u003e). \u003cem\u003eFortune\u003c/em\u003e lo cubrió como \u003cem\u003e\u0026ldquo;la última locura\u0026rdquo;\u003c/em\u003e, la fiebre del \u0026ldquo;cría una langosta\u0026rdquo; que transformaba el sector de IA en China (\u003ca href=\"https://fortune.com/2026/03/14/openclaw-china-ai-agent-boom-open-source-lobster-craze-minimax-qwen/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eFortune\u003c/a\u003e). Mientras tanto, un análisis de seguridad de Bitsight encontró \u003cstrong\u003emás de 30.000 instancias de OpenClaw expuestas a internet\u003c/strong\u003e, muchas mal configuradas por usuarios que hicieron click en las advertencias sin leer (\u003ca href=\"https://www.bitsight.com/blog/openclaw-ai-security-risks-exposed-instances\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eBitsight\u003c/a\u003e). Su creador se fue a OpenAI y el furor empezó a apagarse tan rápido como prendió.\u003c/p\u003e\n\u003cp\u003eUna locura de meses. Gente real expuesta. Y ya casi nadie habla de eso.\u003c/p\u003e\n\u003cp\u003eNo es nuevo. Es el \u003ca href=\"https://en.wikipedia.org/wiki/Gartner_hype_cycle\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eHype Cycle de Gartner\u003c/a\u003e, descrito en 1995: cada tecnología sube a un \u0026ldquo;pico de expectativas infladas\u0026rdquo; y después cae al \u0026ldquo;valle de la desilusión\u0026rdquo; cuando los experimentos no rinden. Lo recorrieron todas:\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/hype-cycle.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/hype-cycle.png\" data-sub-html=\"\u003ch2\u003eEl mismo gráfico, una y otra vez: cada tecnología sube al pico de expectativas infladas y cae al valle de la desilusión. De cada diez que caen, seis no vuelven a subir. Fuente: Gartner Hype Cycle.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/hype-cycle.png\" srcset=\"/the-update-nobody-asked-for/images/hype-cycle_hu88fd49b3fcb8cba7019674893a061c28_77706_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/hype-cycle_hu88fd49b3fcb8cba7019674893a061c28_77706_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/hype-cycle_hu88fd49b3fcb8cba7019674893a061c28_77706_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"540\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eEl mismo gráfico, una y otra vez: cada tecnología sube al pico de expectativas infladas y cae al valle de la desilusión. De cada diez que caen, seis no vuelven a subir. Fuente: Gartner Hype Cycle.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eEl blockchain cayó del pico al valle y se quedó ahí: la mayoría de sus aplicaciones siguen \u003ca href=\"https://www.ciodive.com/news/most-blockchain-applications-sunk-in-the-trough-of-disillusionment-gartn/564613/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ehundidas en el valle de la desilusión\u003c/a\u003e, según el propio Gartner. El IoT, el big data, el serverless: cada uno fue, en su momento, lo que iba a cambiar todo. De cada tecnología que cae al valle, \u003cstrong\u003eseis de cada diez no vuelven a subir nunca\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eY la madre de todas las modas no fue ni siquiera digital: fue mover el trabajo de América a Asia. Se vendió como progreso, como eficiencia, como inevitable. Entre 2001 y 2013, el déficit comercial con China le costó a Estados Unidos, según la estimación del \u003ca href=\"https://www.epi.org/publication/china-trade-outsourcing-and-jobs/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eEconomic Policy Institute\u003c/a\u003e, \u003cstrong\u003e3,2 millones de empleos\u003c/strong\u003e, de los cuales \u003cstrong\u003e2,4 millones eran manufactureros\u003c/strong\u003e. Esa no fue una banderita que se borra en cuatro semanas; esa cambió ciudades enteras para siempre. Pero el mecanismo mental fue idéntico: una idea que todos adoptan a la vez porque nadie quiere quedarse afuera, y el costo lo paga otro, en otra parte.\u003c/p\u003e\n\u003ch2 id=\"la-moda-de-hoy-ponle-agente-a-todo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-moda-de-hoy-ponle-agente-a-todo\" class=\"header-mark\" aria-label=\"Header mark for 'La moda de hoy: ponle \u0026amp;ldquo;agente\u0026amp;rdquo; a todo'\"\u003e\u003c/a\u003e8 La moda de hoy: ponle \u0026ldquo;agente\u0026rdquo; a todo\u003c/h2\u003e\u003cp\u003eSi OpenClaw fue el pico viral, los \u003cstrong\u003e\u0026ldquo;agentes\u0026rdquo; de IA\u003c/strong\u003e son la ola corporativa que vino abajo. En 2025 y 2026 no hubo empresa grande de software que no anunciara su plataforma de agentes. No hace falta que liste cien logos: el dato agregado dice más.\u003c/p\u003e\n\u003cp\u003eGartner estima que de los \u003cstrong\u003emiles de proveedores\u003c/strong\u003e que dicen vender \u0026ldquo;IA agéntica\u0026rdquo;, \u003cstrong\u003esolo unos 130 son reales\u003c/strong\u003e — el resto practica lo que llaman \u003cem\u003e\u0026ldquo;agent washing\u0026rdquo;\u003c/em\u003e: rebautizar como \u0026ldquo;agente\u0026rdquo; al chatbot, al RPA o al asistente que ya tenían. Y predice que \u003cstrong\u003emás del 40% de los proyectos de IA agéntica se van a cancelar para fines de 2027\u003c/strong\u003e, por costos que se disparan, valor de negocio difuso y controles de riesgo insuficientes (\u003ca href=\"https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGartner, junio 2025\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eMiles de empresas vendiendo agentes, 130 reales.\u003c/em\u003e Esa es tu captura de cien apps, condensada en un número. Pero algunos nombres grandes, para que se vea la avalancha:\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eEmpresa\u003c/th\u003e\n\u003cth\u003eQué lanzó\u003c/th\u003e\n\u003cth\u003eFuente\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eSalesforce\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAgentforce — agentes para toda la empresa, presentados como \u0026ldquo;la empresa agéntica\u0026rdquo;\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.salesforce.com/news/press-releases/2025/10/13/agentic-enterprise-announcement/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSalesforce\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eMicrosoft\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eCopilot + \u0026ldquo;Agent 365\u0026rdquo;, presentado en Ignite 2025\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.microsoft.com/en-us/microsoft-365/blog/2025/11/18/microsoft-ignite-2025-copilot-and-agents-built-to-power-the-frontier-firm/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eMicrosoft\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eOracle\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eNuevos agentes en Fusion Applications + AI Agent Studio\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://www.oracle.com/news/announcement/ai-world-oracle-advances-enterprise-ai-with-new-agents-across-fusion-applications-2025-10-15/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eOracle\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eGoogle\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eAgentspace / Vertex AI Agent Builder\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://cloud.google.com/blog/products/ai-machine-learning/google-agentspace-enables-the-agent-driven-enterprise\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eGoogle Cloud\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eServiceNow\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eInnovaciones de IA agéntica; compró Moveworks\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://newsroom.servicenow.com/press-releases/details/2025/ServiceNow-announces-new-agentic-AI-innovations-to-autonomously-solve-the-most-complex-enterprise-challenges-01-29-2025-traffic/default.aspx\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eServiceNow\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eSAP\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eJoule agents + inteligencia embebida\u003c/td\u003e\n\u003ctd\u003e\u003ca href=\"https://news.sap.com/2025/10/sap-connect-business-ai-new-joule-agents-embedded-intelligence/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eSAP\u003c/a\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eEl patrón es siempre el mismo, ¿no? No es \u0026ldquo;tengo un problema y lo resuelvo mejor\u0026rdquo;. Es \u0026ldquo;hay un globo inflándose y me quiero colgar antes de que reviente\u0026rdquo;. El avance se convierte en negocio, no en mejora. Y cuando el 40% de esos proyectos se cancele en 2027, vamos a estar todos mirando para otro lado, hablando de la moda que siga.\u003c/p\u003e\n\u003ch2 id=\"los-números\" class=\"headerLink\"\u003e\n    \u003ca href=\"#los-n%c3%bameros\" class=\"header-mark\" aria-label=\"Header mark for 'Los números'\"\u003e\u003c/a\u003e9 Los números\u003c/h2\u003e\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/the-update-nobody-asked-for/images/numbers-grouped.png\" title=\"\" data-thumbnail=\"/the-update-nobody-asked-for/images/numbers-grouped.png\" data-sub-html=\"\u003ch2\u003eLos números del artículo, agrupados en sus tres motores: los picos del evento, la máquina del FOMO y lo que cuesta. Cada fuente está enlazada a lo largo del texto.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/the-update-nobody-asked-for/images/numbers-grouped.png\" srcset=\"/the-update-nobody-asked-for/images/numbers-grouped_hu6b65c194d519f75da2bad643ee08da42_152867_800x0_resize_q75_h2_box_3.webp 800w, /the-update-nobody-asked-for/images/numbers-grouped_hu6b65c194d519f75da2bad643ee08da42_152867_1200x0_resize_q75_h2_box_3.webp 1200w, /the-update-nobody-asked-for/images/numbers-grouped_hu6b65c194d519f75da2bad643ee08da42_152867_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"760\" width=\"1200\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eLos números del artículo, agrupados en sus tres motores: los picos del evento, la máquina del FOMO y lo que cuesta. Cada fuente está enlazada a lo largo del texto.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch2 id=\"mañana-se-lo-van-a-olvidar\" class=\"headerLink\"\u003e\n    \u003ca href=\"#ma%c3%b1ana-se-lo-van-a-olvidar\" class=\"header-mark\" aria-label=\"Header mark for 'Mañana se lo van a olvidar'\"\u003e\u003c/a\u003e10 Mañana se lo van a olvidar\u003c/h2\u003e\u003cp\u003eCuando termine el Mundial, los autitos van a perder la banderita, los cupones se van a apagar, Duo se va a cambiar de ropa y a nadie le va a importar el traje que tanto le costó conseguir. La app de TikTok dedicada al torneo va a quedar muerta en la tienda. La infraestructura que desplegamos para todo eso se va a desarmar, y vamos a empezar a pensar en lo que sigue. Porque recordar cuesta caro.\u003c/p\u003e\n\u003cp\u003eEsa es la verdadera función de toda esta máquina —la que diseñaron Parker, Eyal y los demás, y la que perfeccionaron Snapchat y Spotify—: no que recuerdes, sino que nunca dejes de mirar. Si te quedaras pensando en la banderita de la semana pasada, no estarías disponible para la moda de la semana que viene. El olvido no es un efecto secundario; es el producto.\u003c/p\u003e\n\u003cp\u003eUna animación de gol o un uniforme de fútbol no tienen nada de malo en sí. Lo notable es que se hayan convertido en la \u003cem\u003ecima\u003c/em\u003e de la ambición del software de consumo. Antes esperábamos meses una actualización porque iba a mejorar algo que usábamos todos los días. Hoy actualizamos todos los días para que no te olvides de mirar — y, sobre todo, para que mires algo que antes nunca te interesó: un partido entre dos países que no son el tuyo, un resultado que no cambia nada en tu vida, un evento que pasó a miles de kilómetros y del que te enteras igual porque la app se encargó de que no pudieras ignorarlo. Te enseñaron a tener FOMO de cosas que ayer ni sabías que existían.\u003c/p\u003e\n\u003cp\u003eLa próxima vez que una app cambie de un día para el otro, pregúntate una sola cosa: ¿esto me sirve a mí, o sirve para que yo no me vaya? Casi siempre vas a saber la respuesta. Y casi siempre, mañana, ya no te vas a acordar — seguro hay algo más urgente que necesita tu atención y no te lo puedes perder.\u003c/p\u003e\n",
        "language": "es"
    },
    {
        "title" : "Cómo funcionan los streams piratas del Mundial",
        "date_published" : "2026-06-17T00:00:00Z",
        "date_modified" : "2026-06-17T00:00:00Z",
        "id" : "https://misael.org/es/clearkey-drm-world-cup-2026/",
        "url" : "https://misael.org/es/clearkey-drm-world-cup-2026/",
        "summary": "Me puse a ver cómo los sitios y las apps Android sirven en realidad los partidos del Mundial. Las claves DRM estaban en el HTML, la encriptación de la app era AES-ECB, y el \u0026rsquo;license server\u0026rsquo; era una URL con la clave en el query string.",
        "content_html" : "\u003cp\u003eMe quería sacar la duda de cómo funcionaba ese truco a la vista de todos. Los sitios piratas de streaming existen desde siempre, pero durante el Mundial no podías abrir un foro deportivo sin que alguien tirara un link a un partido en vivo — full HD, sin buffering, sin login. Yo siempre supuse que había algo exótico funcionando por detrás. Un amigo me pasó un link antes del Inglaterra-Croacia. \u0026ldquo;Mira esto\u0026rdquo;, me dijo. Así que abrí DevTools.\u003c/p\u003e\n\u003cp\u003eEsperaba una playlist HLS scrapeada o un feed re-streameado. En cambio encontré una etiqueta \u003ccode\u003e\u0026lt;video\u0026gt;\u003c/code\u003e, una instancia de Shaka Player apuntando a un CDN legítimo de Akamai, y dos strings hexadecimales en un bloque \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e que hacían todo el trabajo. Esos strings hexadecimales eran las claves de descifrado DRM. En el código fuente de la página. No detrás de una API. No encriptados. Solo envueltos en JavaScript que parecía amenazante y no hacía nada.\u003c/p\u003e\n\u003cp\u003eDespués alguien me mostró una app de Android haciendo lo mismo para cada partido del Mundial — pero \u0026ldquo;como Dios manda\u0026rdquo;, con configs encriptados, Firebase Remote Config, un servidor de licencias, todo el paquete. Una sección dedicada \u0026ldquo;MUNDIAL FIFA 2026\u0026rdquo; con 28 canales que incluían multicam, player cam, y manager cam para cada partido. Me tomó cerca de una hora darme cuenta de que era la misma vulnerabilidad con un mejor traje.\u003c/p\u003e\n\u003cp\u003eLo que empezó como treinta minutos de curiosidad se convirtió en un fin de semana. Al final había mapeado \u003cstrong\u003e670 transmisiones en vivo en 169 CDNs en 33 países\u003c/strong\u003e del lado web, y \u003cstrong\u003e525 canales más de una sola app Android\u003c/strong\u003e — todas protegidas por un esquema DRM que el propio W3C llama una herramienta \u0026ldquo;de pruebas\u0026rdquo;. Durante un Mundial donde Inglaterra le mete cuatro a Croacia y cada gol está simultáneamente disponible en cientos de transmisiones no autorizadas.\u003c/p\u003e\n\u003cdiv class=\"details admonition warning open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z\"/\u003e\u003c/svg\u003e\u003c/span\u003eDisclosure\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003e\u003cstrong\u003eEsto es un análisis de vulnerabilidad.\u003c/strong\u003e No publico claves reales, URLs de streams, ni herramientas funcionales. Nombres de apps y dominios están redactados. La meta es documentar una debilidad sistémica en cómo las plataformas despliegan DRM — no habilitar la piratería.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"el-candado-con-la-combinación-atrás\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-candado-con-la-combinaci%c3%b3n-atr%c3%a1s\" class=\"header-mark\" aria-label=\"Header mark for 'El candado con la combinación atrás'\"\u003e\u003c/a\u003e1 El candado con la combinación atrás\u003c/h2\u003e\u003cp\u003eCada navegador que reproduce video encriptado usa Encrypted Media Extensions (EME) del W3C. EME soporta cuatro key systems. Tres de ellos — Widevine, FairPlay, PlayReady — usan servidores de licencia, intercambios de clave encriptados, y descifrado respaldado por hardware. El cuarto es ClearKey.\u003c/p\u003e\n\u003cp\u003eClearKey le manda la clave de descifrado al navegador en texto plano.\u003c/p\u003e\n\u003cp\u003eEso es todo. La misma encriptación AES-128 que Widevine. La misma entrega MPEG-DASH. Pero donde Widevine negocia claves a través de un canal seguro y descifra video dentro de una sandbox de hardware en la que el navegador no puede meter la nariz, ClearKey le entrega la clave cruda a JavaScript y dice \u0026ldquo;ahí la tienes\u0026rdquo;.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    WIDEVINE L1                          CLEARKEY\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ───────────────────────              ───────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Servidor de licencia (HTTPS)         Sin servidor de licencia\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Intercambio de clave encriptado      Clave en JS plano\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Descifrado en TEE de hardware        Descifrado por software\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    La clave nunca está en memoria JS    La clave ES el JS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Política por dispositivo, por sesión Sin política alguna\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl DASH Industry Forum dice que ClearKey está \u0026ldquo;recomendado solo para propósitos de prueba\u0026rdquo;. Algunas plataformas decidieron usarlo en producción de todas formas. Tanto los sitios web como la app que analicé dependen de esa decisión.\u003c/p\u003e\n\u003ch2 id=\"parte-1-los-sitios-web\" class=\"headerLink\"\u003e\n    \u003ca href=\"#parte-1-los-sitios-web\" class=\"header-mark\" aria-label=\"Header mark for 'Parte 1: Los sitios web'\"\u003e\u003c/a\u003e2 Parte 1: Los sitios web\u003c/h2\u003e\u003cp\u003eExaminé docenas de sitios de streaming pirata. Todos siguen el mismo patrón.\u003c/p\u003e\n\u003ch3 id=\"la-arquitectura\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-arquitectura\" class=\"header-mark\" aria-label=\"Header mark for 'La arquitectura'\"\u003e\u003c/a\u003e2.1 La arquitectura\u003c/h3\u003e\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌──────────────────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │                  CÓMO FUNCIONA EN REALIDAD                   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └──────────────────────────────────────────────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    PASO 1                    PASO 2                    PASO 3\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Usuario visita            Hace click en un canal    La página carga un \u0026lt;iframe\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    pirate-site.co            (ej. \u0026#34;Sky Sports\u0026#34;)        de un dominio distinto\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌──────────────┐          ┌──────────────┐          ┌──────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │              │          │  ┌────┐┌────┐│          │  player-host.co  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │  Página de   │  click   │  │ESPN││DAZN││  iframe  │                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │  Canales     │ ───────\u0026gt; │  ├────┤├────┤│ ───────\u0026gt; │  Shaka Player    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │              │          │  │Sky ││beIN││          │  + claves DRM    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │  (HTML       │          │  └────┘└────┘│          │  + URL manifest  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │   estático)  │          └──────────────┘          └────────┬─────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └──────────────┘                                             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                                 │ pide\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                                 ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    ┌──────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │  CDN LEGÍTIMO        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │  (akamaized.net,     │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │   skycdp.com,        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │   indazn.com)        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │                      │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │  Segmentos DASH      │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    │  encriptados         │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                    └──────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    El sitio pirata sirve CERO video.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    La cuenta del CDN le pertenece al proveedor legítimo.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    El sitio pirata aloja ~50KB de HTML.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl sitio pirata es solo un intermediario que conoce dos strings hexadecimales. Apunta el navegador del espectador a un CDN real, le entrega la clave de descifrado, y el navegador hace el resto. El CDN nunca sabe que el espectador no es un suscriptor de pago.\u003c/p\u003e\n\u003ch3 id=\"la-clave-está-en-el-código-fuente\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-clave-est%c3%a1-en-el-c%c3%b3digo-fuente\" class=\"header-mark\" aria-label=\"Header mark for 'La clave está en el código fuente'\"\u003e\u003c/a\u003e2.2 La clave está en el código fuente\u003c/h3\u003e\u003cp\u003eLa página del player host contiene un bloque \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e con las claves, envuelto en obfuscación que un script de Node de 30 líneas deshace en menos de un segundo:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ejavascript\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Antes: 40 líneas de _0x4a2f, IIFEs, lookups de string-arrays\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Después: esto es lo que en realidad hace\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKeyId\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a1b2c3d4e5f6a7b80000000000000000\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKey\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;0123456789abcdef0123456789abcdef\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003econfigure\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003edrm\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"nx\"\u003eclearKeys\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003edrmKeyId\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003edrmKey\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eplayer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;https://cdn.legitimate-provider.com/manifest.mpd\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos variables. Dos strings hexadecimales. Esa es toda la \u0026ldquo;protección DRM\u0026rdquo;. La obfuscación — renombrado de variables, arrays de strings, aplanado de control-flow — la deshace \u003ccode\u003eeval()\u003c/code\u003e, porque el navegador también tiene que correrla. Si el navegador la puede ejecutar, un script la puede ejecutar.\u003c/p\u003e\n\u003cp\u003eUna vez extraídas, las claves se distribuyen vía playlists M3U en GitHub y Telegram. Encontré un solo archivo M3U con \u003cstrong\u003e545 entradas de ClearKey en 92 CDNs\u003c/strong\u003e. Público, buscable, indexado por Google. Tiempo de extracción a distribución global: minutos. Tiempo para que la clave sea revocada: usualmente nunca.\u003c/p\u003e\n\u003ch2 id=\"parte-2-la-app-de-android\" class=\"headerLink\"\u003e\n    \u003ca href=\"#parte-2-la-app-de-android\" class=\"header-mark\" aria-label=\"Header mark for 'Parte 2: La app de Android'\"\u003e\u003c/a\u003e3 Parte 2: La app de Android\u003c/h2\u003e\u003cp\u003eNo toda operación pirata es una página estática. Algunas envían una app Android completa — con pantallas de login, analytics de Firebase, configs encriptados, y una UI profesional. Una app que analicé durante el Mundial toma este enfoque. 525 canales. 36 categorías. Una sección dedicada \u0026ldquo;MUNDIAL FIFA 2026\u0026rdquo; con ángulos multicámara para cada partido.\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/clearkey-drm-world-cup-2026/images/app-config.png\" title=\"\" data-thumbnail=\"/clearkey-drm-world-cup-2026/images/app-config.png\" data-sub-html=\"\u003ch2\u003eEl config encriptado de canales de la app — 525 canales en 36 categorías, con URLs encriptadas con AES y credenciales DRM. [URLs redactadas]\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/clearkey-drm-world-cup-2026/images/app-config.png\" srcset=\"/clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_800x0_resize_q75_h2_box_3.webp 800w, /clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_1200x0_resize_q75_h2_box_3.webp 1200w, /clearkey-drm-world-cup-2026/images/app-config_hua7b6385fc770026e215313f327bfe973_145312_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"1132\" width=\"1621\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eEl config encriptado de canales de la app — 525 canales en 36 categorías, con URLs encriptadas con AES y credenciales DRM. [URLs redactadas]\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003ch3 id=\"la-encriptación-aes-128-ecb\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-encriptaci%c3%b3n-aes-128-ecb\" class=\"header-mark\" aria-label=\"Header mark for 'La encriptación (AES-128-ECB)'\"\u003e\u003c/a\u003e3.1 La encriptación (AES-128-ECB)\u003c/h3\u003e\u003cp\u003eLa app pide su lista de canales como un JSON encriptado con AES desde un servidor remoto. Cada campo — URLs de stream, URIs de licencia DRM, headers HTTP — es ciphertext AES en base64. La clave de descifrado es un string de 16 caracteres guardado en Firebase Remote Config, descargado al arrancar.\u003c/p\u003e\n\u003cp\u003eSuena serio. Después miras el ciphertext:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    236 URLs comparten los mismos primeros 35 bloques encriptados.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Bloque 0: f91b7f273abccc6e...  ← idéntico en los 236\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Bloque 1: 932b23560f1d43ba...  ← idéntico\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Bloque 2: a7d788a399cea962...  ← idéntico\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Modo ECB. Mismo bloque plano = mismo bloque cifrado.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Todas las URLs empiezan con el mismo prefijo de dominio del CDN.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eAES-ECB es el ejemplo de manual de cómo \u003cem\u003eno\u003c/em\u003e usar AES. Cualquier curso de criptografía enseña esto con el \u003ca href=\"https://blog.filippo.io/the-ecb-penguin/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003epingüino ECB\u003c/a\u003e — encripta un bitmap con ECB y la imagen sigue siendo reconocible porque bloques idénticos en plano producen bloques idénticos en cifrado. Esta app encripta 236 URLs que arrancan con el mismo dominio de CDN, produciendo 236 ciphertexts con un \u003cstrong\u003eprefijo idéntico de 560 bytes\u003c/strong\u003e. El análisis de patrones por sí solo revela la estructura antes de que siquiera encuentres la clave.\u003c/p\u003e\n\u003ch3 id=\"el-servidor-de-licencia-que-no-lo-es\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-servidor-de-licencia-que-no-lo-es\" class=\"header-mark\" aria-label=\"Header mark for 'El \u0026amp;ldquo;servidor de licencia\u0026amp;rdquo; que no lo es'\"\u003e\u003c/a\u003e3.2 El \u0026ldquo;servidor de licencia\u0026rdquo; que no lo es\u003c/h3\u003e\u003cp\u003eUna vez descifradas, las credenciales de ClearKey no están en un intercambio de claves separado. Están en el \u003cstrong\u003elicense URI mismo\u003c/strong\u003e, como parámetros plano en el query string:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    drm_license_uri (después de descifrar):\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    https://[redactado]/?keyid=49eb924b...\u0026amp;key=6e131b04...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              ^^^^^^^^        ^^^^^^^^\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              ClearKey ID     ClearKey Key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                              (en la URL)     (en la URL)\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl \u0026ldquo;servidor de licencia\u0026rdquo; de la app es una URL que \u003cem\u003econtiene la clave\u003c/em\u003e. No hay reto-respuesta. No hay vinculación de sesión. La app pide la URL, la URL \u003cem\u003ees\u003c/em\u003e la clave, y la clave descifra el stream.\u003c/p\u003e\n\u003cp\u003eTres capas de indirección — Firebase Remote Config, encriptación AES, un endpoint de \u0026ldquo;servidor de licencia\u0026rdquo; — todas colapsando en el mismo fallo: la clave de descifrado termina en el cliente, en plano, porque ClearKey lo exige.\u003c/p\u003e\n\u003ch3 id=\"el-stack-completo-de-la-app\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-stack-completo-de-la-app\" class=\"header-mark\" aria-label=\"Header mark for 'El stack completo de la app'\"\u003e\u003c/a\u003e3.3 El stack completo de la app\u003c/h3\u003e\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌──────────────────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │              ARQUITECTURA DE LA APP ANDROID                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └──────────────────────────────────────────────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    1. La app arranca\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Firebase Remote Config\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Descarga clave AES (\u0026#34;claveapp\u0026#34;) + URLs de config\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    2. La app descarga JSON encriptado (525 canales)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Decodifica base64 ──\u0026gt; Descifra AES-128-ECB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; URLs de stream, URIs de licencia DRM, headers (plano)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    3. Para canales CLEARKEY (348 de 525):\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; \u0026#34;License URI\u0026#34; = https://[redactado]/?keyid=XXX\u0026amp;key=YYY\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Key ID y Key están EN LA URL\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    4. La app configura ExoPlayer con ClearKey\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──\u0026gt; Descarga el manifest DASH del CDN legítimo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            └──\u0026gt; Descifra el video con la clave del paso 3\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Package name: com.example.myapplication\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Encriptación: AES/ECB/PKCS5Padding (de libro, inseguro)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Almacén clave: Firebase Remote Config (una llamada API para extraerla)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Largo clave:  16 caracteres, estática, nunca rotada\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl package name \u003ccode\u003ecom.example.myapplication\u003c/code\u003e lo dice todo sobre el rigor de desarrollo detrás de esta operación. El template por defecto de Android Studio. Ni siquiera renombrado.\u003c/p\u003e\n\u003ch3 id=\"la-lista-de-canales-en-vivo-vive-en-un-repo-público\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-lista-de-canales-en-vivo-vive-en-un-repo-p%c3%bablico\" class=\"header-mark\" aria-label=\"Header mark for 'La lista de canales en vivo vive en un repo público'\"\u003e\u003c/a\u003e3.4 La lista de canales en vivo vive en un repo público\u003c/h3\u003e\u003cp\u003eLa parte más calladamente lapidaria — y la que muestra dónde \u003cem\u003erealmente\u003c/em\u003e hay sofisticación en esta operación, que es sobre todo logística — es dónde está alojado el config encriptado. La app descarga su JSON de canales desde un repositorio público de GitHub. Cualquiera puede navegarlo. Cualquiera puede \u003ccode\u003egit clone\u003c/code\u003e. Cualquiera puede ver el historial de commits.\u003c/p\u003e\n\u003cp\u003eY el historial de commits es lo interesante:\u003c/p\u003e\n\u003cfigure\u003e\u003ca class=\"lightgallery\" href=\"/clearkey-drm-world-cup-2026/images/commits.png\" title=\"\" data-thumbnail=\"/clearkey-drm-world-cup-2026/images/commits.png\" data-sub-html=\"\u003ch2\u003eEl repo público de GitHub que aloja la lista encriptada de canales de la app. Un commit cae cada 4–5 minutos — ediciones automáticas al JSON de canales mientras los streams upstream se rotan, se mueren o se reemplazan. Usuario y dueño del repo redactados.\u003c/h2\u003e\"\u003e\u003cimg  loading=\"lazy\" src=\"/clearkey-drm-world-cup-2026/images/commits.png\" srcset=\"/clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_800x0_resize_q75_h2_box_3.webp 800w, /clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_1200x0_resize_q75_h2_box_3.webp 1200w, /clearkey-drm-world-cup-2026/images/commits_hu2add8a55510f0a520df6ce349c817777_98640_1600x0_resize_q75_h2_box_3.webp 1600w\"   height=\"900\" width=\"1300\"\u003e\u003c/a\u003e\u003cfigcaption class=\"image-caption\"\u003eEl repo público de GitHub que aloja la lista encriptada de canales de la app. Un commit cae cada 4–5 minutos — ediciones automáticas al JSON de canales mientras los streams upstream se rotan, se mueren o se reemplazan. Usuario y dueño del repo redactados.\u003c/figcaption\u003e\n    \u003c/figure\u003e\n\u003cp\u003eUn commit cae cada 4–5 minutos. El diff siempre es contra \u003ccode\u003etv (10).json\u003c/code\u003e o \u003ccode\u003ebearer.json\u003c/code\u003e — la lista encriptada de canales, y las credenciales de auth usadas para pedir streams nuevos al upstream. Los mensajes de commit son todos \u003ccode\u003e\u0026quot;Actualizar tv (10).json desde tv.json\u0026quot;\u003c/code\u003e o \u003ccode\u003e\u0026quot;Actualizar bearer.json desde panel - \u0026lt;timestamp\u0026gt;\u0026quot;\u003c/code\u003e. Traducción: un job automatizado, en algún lado, está reescribiendo el config de canales cada pocos minutos y empujando el update para que los celulares corriendo la app levanten las nuevas URLs de stream y las credenciales DRM rotadas en el siguiente intervalo de polling.\u003c/p\u003e\n\u003cp\u003eEste es el verdadero foso de la operación. No el AES-ECB (roto). No el Firebase Remote Config (una sola llamada). No el \u0026ldquo;license server\u0026rdquo; (una URL con la clave dentro). El foso es \u003cstrong\u003eoperacional\u003c/strong\u003e: un pedazo de automatización que mantiene el JSON de canales alineado con cualquier feed upstream que esté vivo en cualquier minuto, alojado a la vista en un CDN gratis que ellos no tuvieron que construir. Cuando un proveedor legítimo rota una clave, el bot lo nota, descarga la nueva, re-encripta el config y commitea. Cuando un CDN bloquea la IP de origen, el bot encuentra otra. Los celulares de los clientes hacen polling, ven el commit nuevo, descargan el config nuevo, y siguen mirando el partido. El usuario ni siquiera ve que pasó la rotación.\u003c/p\u003e\n\u003cp\u003eLa defensa a la que todo el mundo recurre es \u0026ldquo;revocar la clave\u0026rdquo;. Pero no puedes revocar una clave más rápido de lo que un script puede empujar una nueva a un repo de GitHub. Ese desfase de cadencia es lo que decide la partida.\u003c/p\u003e\n\u003ch2 id=\"los-números\" class=\"headerLink\"\u003e\n    \u003ca href=\"#los-n%c3%bameros\" class=\"header-mark\" aria-label=\"Header mark for 'Los números'\"\u003e\u003c/a\u003e4 Los números\u003c/h2\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e\u003c/th\u003e\n\u003cth\u003eAuditoría web\u003c/th\u003e\n\u003cth\u003eApp Android\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eStreams\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e670\u003c/td\u003e\n\u003ctd\u003e525 (348 ClearKey)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eCDNs\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e169\u003c/td\u003e\n\u003ctd\u003e34\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003ePaíses\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e33\u003c/td\u003e\n\u003ctd\u003e~8 (foco LATAM)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eAlmacén de claves\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eBloque \u003ccode\u003e\u0026lt;script\u0026gt;\u003c/code\u003e en JS\u003c/td\u003e\n\u003ctd\u003eFirebase Remote Config\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eObfuscación\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eRenombrado JS + IIFEs\u003c/td\u003e\n\u003ctd\u003eAES-128-ECB (roto)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eClave en plano en el cliente\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eSí\u003c/td\u003e\n\u003ctd\u003eSí\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eCobertura del Mundial\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eDecenas de canales deportivos\u003c/td\u003e\n\u003ctd\u003e28 canales dedicados + multicam\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eLos dos enfoques — el sitio web estático y la app Android completa — terminan en el mismo lugar: un par de claves ClearKey en la memoria del cliente, sin mecanismo para prevenir la extracción, sin vinculación de sesión, sin revocación, y sin rotación de claves.\u003c/p\u003e\n\u003ch2 id=\"el-dinero-y-por-qué-no-va-a-parar\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-dinero-y-por-qu%c3%a9-no-va-a-parar\" class=\"header-mark\" aria-label=\"Header mark for 'El dinero, y por qué no va a parar'\"\u003e\u003c/a\u003e5 El dinero, y por qué no va a parar\u003c/h2\u003e\u003cp\u003eLa economía explica todo. Los sitios piratas alojan ~50KB de HTML estático y apuntan tu navegador al CDN de otra persona. La app es un wrapper delgado de ExoPlayer alrededor de la infraestructura de otra persona. Ninguno sirve video. Los dos monetizan a través de redes de publicidad.\u003c/p\u003e\n\u003cp\u003eStreamEast sirvió 1.6 mil millones de visitas antes de ser allanado en septiembre de 2025. La red de IPTV de \u0026ldquo;Operation Takendown\u0026rdquo; tenía 22 millones de usuarios y 250 millones de euros al mes. Un sitio de tier medio durante el Mundial — un millón de visitantes el día de partido, seis impresiones de anuncio cada uno, $1.50 CPM — saca \u003cstrong\u003e$9.000 al día\u003c/strong\u003e contra costos de hosting esencialmente cero.\u003c/p\u003e\n\u003cp\u003eLa aplicación de la ley sigue escalando — sentencias de prisión en España, fallos de $18.75 millones en Texas, 27.000 feeds dados de baja la semana antes del Mundial — y siguen apareciendo nuevos sitios. Porque la vulnerabilidad es arquitectónica. Ninguna cantidad de arrestos arregla una especificación que pone la clave de descifrado en el navegador. Y tampoco hay encriptación que valga, como lo demuestra la app de Android: tres capas de encriptación, y la clave igual termina en texto plano en el cliente, porque ClearKey lo exige.\u003c/p\u003e\n\u003cp\u003eEl arreglo es migrar de ClearKey por completo — a Widevine, FairPlay, o PlayReady. Hasta entonces, la combinación está escrita en la parte de atrás del candado, y el Mundial se está asegurando de que todos sepan dónde mirar.\u003c/p\u003e\n",
        "language": "es"
    },
    {
        "title" : "fastfn Parte 2: Cuando una Función No Es Suficiente (Servicios, Workloads y el docker-compose Que No Quería Escribir)",
        "date_published" : "2026-06-09T00:00:00Z",
        "date_modified" : "2026-06-09T00:00:00Z",
        "id" : "https://misael.org/es/fastfn-services-when-functions-arent-enough/",
        "url" : "https://misael.org/es/fastfn-services-when-functions-arent-enough/",
        "summary": "Las funciones son una forma excelente para request/response. Son una forma pésima para una base de datos. Esta es la parte de fastfn donde los servicios longevos se unieron al gateway — docker nativo, proceso nativo, microVM Firecracker — todos detrás de una sola configuración de workload.",
        "content_html" : "\u003cdiv class=\"details admonition info open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\"/\u003e\u003c/svg\u003e\u003c/span\u003eParte de una serie\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eEsta es la Parte 2 del write-up de fastfn. La Parte 1 — \u003ca href=\"/es/fastfn-lua-to-our-lives/\" rel=\"\"\u003e\u003cstrong\u003efastfn Parte 1: Tenía un Problema (y Le Metí Lua a Mi Vida)\u003c/strong\u003e\u003c/a\u003e — cubre el lado de las funciones: el gateway de Lua, el protocolo JSON, los runtimes políglotas. Este post continúa donde termina la Parte 1 y recorre servicios, workloads, y el camino de microVM Firecracker.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"capítulo-1-el-hartazgo-o-por-qué-una-función-no-puede-ser-un-postgres\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-1-el-hartazgo-o-por-qu%c3%a9-una-funci%c3%b3n-no-puede-ser-un-postgres\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 1: El Hartazgo (o, Por Qué una Función No Puede Ser un Postgres)'\"\u003e\u003c/a\u003e1 Capítulo 1: El Hartazgo (o, Por Qué una Función No Puede Ser un Postgres)\u003c/h2\u003e\u003cp\u003eAl final de la Parte 1, fastfn era una cosita feliz. Soltabas \u003ccode\u003eget.users.py\u003c/code\u003e en disco, el gateway lo mapeaba a una URL, un daemon Python lo recogía, y tenías un endpoint HTTP sin tocar un Dockerfile. Era elegante de la manera en que las cosas son elegantes cuando solo resuelven la mitad de tu problema.\u003c/p\u003e\n\u003cp\u003ePorque entonces intenté construir una app real con ello.\u003c/p\u003e\n\u003cp\u003eY en el momento en que intentas construir una app real, tus primitivos de request/response dejan de ser suficientes. Necesitas estado. Necesitas una base de datos. Necesitas el dashboard de administración que tu colega ya escribió como una app Next.js que espera correr con \u003ccode\u003enext start\u003c/code\u003e en el puerto 3000, no como un lambda sin estado que arranca, computa, y muere. Necesitas un Redis para sesiones, o un MinIO para blobs, o —en mi caso— una app Flask completamente formada que nunca iba a caber por el ojo de aguja de \u0026ldquo;un handler por archivo.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eMe quedé mirando esto por un rato. Luego dije lo que había estado esforzándome tanto por no decir:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eUna función es una forma excelente para request/response. Es una forma pésima para un Postgres.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eLo cual me hizo darme cuenta de algo incómodo. En todos los proyectos personales que había tenido, la respuesta a este hartazgo siempre había sido el mismo archivo: \u003ccode\u003edocker-compose.yml\u003c/code\u003e. Un servicio es mi app, el otro es una base de datos, quizás hay un sidecar, y todos hablan en una red implícita que compose conjuró de la nada. Es una pieza hermosa de ergonomía, y la uso constantemente. Pero si fastfn iba a ser mi plataforma local, y yo iba a correr \u003ccode\u003edocker-compose up\u003c/code\u003e junto a \u003ccode\u003efastfn dev\u003c/code\u003e, tendría dos espacios de URLs, dos modelos de salud, dos configuraciones de CORS, dos superficies de autenticación. El motivo mismo por el que existía el gateway era tener \u003cstrong\u003eun\u003c/strong\u003e lugar que conociera mi HTTP. Un \u003ccode\u003ecompose\u003c/code\u003e paralelo habría deshecho eso.\u003c/p\u003e\n\u003cp\u003eAsí que tenía que elegir. O acepto que fastfn maneja una porción de mi app y algo más maneja el resto. O extiendo fastfn para que las formas longevas —el Postgres, el Next.js, el Flask— vivan detrás del mismo gateway que las funciones. Mismo \u003ccode\u003efastfn.json\u003c/code\u003e, mismo endpoint de salud, mismo CORS, misma auth, una superficie HTTP unificada.\u003c/p\u003e\n\u003cp\u003eNo me propuse escribir docker-compose. Este post es sobre cómo escribí la mitad de docker-compose a propósito.\u003c/p\u003e\n\u003cp\u003eEl commit sobre el que principalmente trata este post es \u003ccode\u003e6a54c11\u003c/code\u003e en la rama \u003ccode\u003efirecracker-simple-images\u003c/code\u003e: \u003cem\u003e\u0026ldquo;Add simple native image apps and services\u0026rdquo;\u003c/em\u003e, 31 de marzo de 2026, 2254 inserciones en 26 archivos. Ahí es donde la idea de un \u003cstrong\u003eworkload\u003c/strong\u003e encontró su lugar de primera clase en fastfn. Todo lo que vino después —la red de pares vsock en \u003ccode\u003e5568b6c\u003c/code\u003e, los defaults keep-hot en \u003ccode\u003e6fd5fec\u003c/code\u003e, la matriz firewall + benchmark en \u003ccode\u003efd8a6b5\u003c/code\u003e— es una evolución de la misma abstracción.\u003c/p\u003e\n\u003ch2 id=\"capítulo-2-la-palabra-workload\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-2-la-palabra-workload\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 2: La Palabra \u0026amp;ldquo;Workload\u0026amp;rdquo;'\"\u003e\u003c/a\u003e2 Capítulo 2: La Palabra \u0026ldquo;Workload\u0026rdquo;\u003c/h2\u003e\u003cp\u003eLa palabra de la era CGI para lo que quería era \u0026ldquo;daemon.\u0026rdquo; La palabra de systemd es \u0026ldquo;unit.\u0026rdquo; Las palabras de Kubernetes son \u0026ldquo;Deployment\u0026rdquo; y \u0026ldquo;StatefulSet.\u0026rdquo; La palabra de Heroku es \u0026ldquo;entrada de Procfile.\u0026rdquo; La palabra de docker-compose es, confusamente, \u0026ldquo;service.\u0026rdquo; Elegí la palabra \u003cstrong\u003eworkload\u003c/strong\u003e como paraguas, y luego la dividí en dos.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   fastfn.json\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ┌──────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  functions-dir: \u0026#34;functions\u0026#34;                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  apps:                ← public HTTP faces    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │    admin: { ... routes: [\u0026#34;/admin/*\u0026#34;] }       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │  services:            ← private, injected    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │    mysql: { port: 3306, volume: \u0026#34;mysql-data\u0026#34; }│\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                                              │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   └──────────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa distinción es pequeña pero significativa. Una \u003cstrong\u003eapp\u003c/strong\u003e es un workload que tiene una cara pública: declara \u003ccode\u003eroutes\u003c/code\u003e, y el gateway expone esas rutas al mundo exterior. Un \u003cstrong\u003eservice\u003c/strong\u003e es un workload que se mantiene privado: otros workloads y funciones pueden alcanzarlo, pero el mundo exterior no puede. Un Postgres es un service. Un dashboard de administración Next.js es una app. En el extremo, la única diferencia entre ellos es si el campo routes está configurado y si el gateway los anuncia.\u003c/p\u003e\n\u003cp\u003eAmbos viven en el mismo archivo, y ambos son validados por el mismo camino de código (\u003ccode\u003ecli/internal/workloads/config.go:12-14\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:12-15\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eApps\u003c/span\u003e     \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eAppSpec\u003c/span\u003e     \u003cspan class=\"s\"\u003e`json:\u0026#34;apps,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\t\u003cspan class=\"nx\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eServiceSpec\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;services,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos listas hermanas. No dos mundos. El mismo normalizador recorre ambas, el mismo escritor de estado persiste ambas, el mismo gateway de Lua lee ambas del mismo archivo JSON en tiempo de petición.\u003c/p\u003e\n\u003cp\u003eEsto me hizo darme cuenta de algo casi trivial pero que vale la pena decir: una vez que tienes un gateway que sabe cómo enrutar, darle un nuevo tipo de destino no es un proyecto nuevo. Es una nueva fila en una tabla.\u003c/p\u003e\n\u003ch2 id=\"capítulo-3-la-forma-de-un-workload\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-3-la-forma-de-un-workload\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 3: La Forma de un Workload'\"\u003e\u003c/a\u003e3 Capítulo 3: La Forma de un Workload\u003c/h2\u003e\u003cp\u003eVeamos la forma real. Aquí está la configuración mínima de fastfn con una app y un service, tomada del diff del README en ese commit (\u003ccode\u003eREADME.md:178-202\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ejson\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// fastfn.json\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;functions-dir\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;functions\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;public-base-url\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://api.example.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;openapi-include-internal\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;admin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ghcr.io/acme/admin:latest\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;routes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/admin/*\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;mysql\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;image\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mysql:8.4\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;port\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3306\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;volume\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mysql-data\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eHay tres cosas que vale la pena mirar fijo. Primero, la fuente de imagen. Segundo, el puerto. Tercero, las rutas (solo en apps). Todo lo demás es un control opcional.\u003c/p\u003e\n\u003cp\u003eLa fuente de imagen es interesante porque hay tres maneras válidas de declarar una, y el código de configuración impone que elijas exactamente una de ellas (\u003ccode\u003ecli/internal/workloads/config.go:383-395\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:387-395\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eimageFile\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003edockerfile\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003esourceCount\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eErrorf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s\"\u003e\u0026#34;must set exactly one image source among image, image_file or dockerfile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEntonces tus tres opciones son:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eimage\u003c/code\u003e: una referencia de registro como \u003ccode\u003emysql:8.4\u003c/code\u003e, o un camino a un directorio de bundle Firecracker local en disco.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eimage_file\u003c/code\u003e: un archivo OCI o Docker local, convertido a un bundle Firecracker cacheado en el primer uso.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edockerfile\u003c/code\u003e: un camino a un Dockerfile que fastfn construirá a través de la API del Docker Engine, y luego igualmente convertirá.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eLa regla exactamente-uno es significativa. Dice \u0026ldquo;un workload, una fuente de verdad.\u0026rdquo; No puedes mezclar un bundle local con una imagen de registro y ver cuál gana. Por ese camino está la depuración que no quieres.\u003c/p\u003e\n\u003cp\u003eEl puerto es más simple: es el puerto del contenedor en el que escucha el workload, con una validación de que cae dentro de \u003ccode\u003e1..65535\u003c/code\u003e. Las rutas, para apps, son un array de prefijos de URL. La forma por defecto de una ruta soporta tanto coincidencia exacta (\u003ccode\u003e/admin\u003c/code\u003e) como glob de cola (\u003ccode\u003e/admin/*\u003c/code\u003e). Hay un normalizador que recorta las barras al final e impone una barra al comienzo (\u003ccode\u003ecli/internal/workloads/config.go:941-953\u003c/code\u003e), para que las tres maneras en que podrías escribir la ruta se conviertan en un string canónico.\u003c/p\u003e\n\u003cp\u003eEl AppSpec completo es más grande que este mínimo, porque una vez que empiezas a correr apps reales quieres controles. Aquí está toda la superficie (\u003ccode\u003ecli/internal/workloads/config.go:66-88\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:66-88\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eAppSpec\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;name\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eScopeDir\u003c/span\u003e      \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;scope_dir,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImage\u003c/span\u003e         \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;image,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImageFile\u003c/span\u003e     \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;image_file,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eDockerfile\u003c/span\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;dockerfile,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContext\u003c/span\u003e       \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;context,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e          \u003cspan class=\"kt\"\u003eint\u003c/span\u003e                \u003cspan class=\"s\"\u003e`json:\u0026#34;port\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e           \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"s\"\u003e`json:\u0026#34;env,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eCommand\u003c/span\u003e       \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e           \u003cspan class=\"s\"\u003e`json:\u0026#34;command,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eWorkingDir\u003c/span\u003e    \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;working_dir,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eUser\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e             \u003cspan class=\"s\"\u003e`json:\u0026#34;user,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e        \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolumeSpec\u003c/span\u003e        \u003cspan class=\"s\"\u003e`json:\u0026#34;volume,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolumes\u003c/span\u003e       \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolumeSpec\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;volumes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHealthcheck\u003c/span\u003e   \u003cspan class=\"nx\"\u003eHealthcheckSpec\u003c/span\u003e    \u003cspan class=\"s\"\u003e`json:\u0026#34;healthcheck,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eRoutes\u003c/span\u003e        \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e           \u003cspan class=\"s\"\u003e`json:\u0026#34;routes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eReplicas\u003c/span\u003e      \u003cspan class=\"kt\"\u003eint\u003c/span\u003e                \u003cspan class=\"s\"\u003e`json:\u0026#34;replicas,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePorts\u003c/span\u003e         \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003ePortSpec\u003c/span\u003e         \u003cspan class=\"s\"\u003e`json:\u0026#34;ports,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAccess\u003c/span\u003e        \u003cspan class=\"nx\"\u003eAccessSpec\u003c/span\u003e         \u003cspan class=\"s\"\u003e`json:\u0026#34;access,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eProcessGroups\u003c/span\u003e \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"nx\"\u003eProcessGroupSpec\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;process_groups,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHA\u003c/span\u003e            \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eHAConfig\u003c/span\u003e          \u003cspan class=\"s\"\u003e`json:\u0026#34;ha,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eLifecycle\u003c/span\u003e     \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;lifecycle,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003eServiceSpec\u003c/code\u003e es casi idéntico (\u003ccode\u003econfig.go:90-111\u003c/code\u003e), sin \u003ccode\u003eReplicas\u003c/code\u003e; para los services el grupo de procesos hace el conteo de réplicas. Por un momento estuve tentado de unificarlos en un struct con un \u003ccode\u003eIsPublic bool\u003c/code\u003e. Me alegra no haberlo hecho. Las dos formas tienen validadores sutilmente diferentes y defaults sutilmente diferentes, y tratar de doblarlos en un tipo seguía produciendo ternarios donde un segundo tipo producía claridad.\u003c/p\u003e\n\u003cp\u003eLa simetría oculta una asimetría sutil que aparece en \u003ccode\u003edefaultAppLifecycle\u003c/code\u003e vs \u003ccode\u003edefaultServiceLifecycle\u003c/code\u003e (\u003ccode\u003econfig.go:658-672\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/config.go:658-672\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003edefaultAppLifecycle\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eIdleAction\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e   \u003cspan class=\"s\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePauseAfterMS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e15000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePrewarm\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003edefaultServiceLifecycle\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eLifecycleSpec\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eIdleAction\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e   \u003cspan class=\"s\"\u003e\u0026#34;run\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePauseAfterMS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003ePrewarm\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLas apps obtienen un timer de inactividad de 15 segundos que pueden usar si alguna vez optan por \u003ccode\u003epause\u003c/code\u003e. Los services obtienen cero —porque no pausas un Postgres. Un Postgres pausado se llama \u0026ldquo;una interrupción.\u0026rdquo; La política por defecto en todas partes es \u003cem\u003eseguir corriendo, seguir caliente, y prewarm al arrancar\u003c/em\u003e. Dos modelos de ciclo de vida, un archivo de configuración, cero arrepentimientos.\u003c/p\u003e\n\u003ch2 id=\"capítulo-4-tres-backends-una-forma-de-configuración\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-4-tres-backends-una-forma-de-configuraci%c3%b3n\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 4: Tres Backends, Una Forma de Configuración'\"\u003e\u003c/a\u003e4 Capítulo 4: Tres Backends, Una Forma de Configuración\u003c/h2\u003e\u003cp\u003eAquí es donde la abstracción de workload demuestra su valor. Se supone que el mismo bloque JSON corre de tres maneras diferentes:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   fastfn.json workload\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            ├────────┬────────────┬──────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            │        │            │                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            ▼        ▼            ▼                  ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       docker   native process  Firecracker     (future\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       native   (no container)  microVM on       backends)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       (fallback)                Linux/KVM\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCuál corre es en parte una decisión de plataforma y en parte una decisión de rama. En esta rama (\u003ccode\u003efirecracker-simple-images\u003c/code\u003e), Firecracker es el objetivo en hosts Linux/KVM, y \u003ccode\u003edocker_native.go\u003c/code\u003e es el manager de respaldo —nota el build tag en la parte superior del archivo (\u003ccode\u003ecli/internal/workloads/docker_native.go:1\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//go:build !linux\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsa única línea hace más trabajo del que parece. Dice \u0026ldquo;en no-Linux, usa la implementación respaldada por Docker del manager de workloads.\u0026rdquo; En Linux el manager de Firecracker toma el control (\u003ccode\u003efirecracker_manager_linux.go\u003c/code\u003e). Ambas implementaciones satisfacen la misma interfaz interna \u003ccode\u003enativeImageWorkloadManager\u003c/code\u003e que \u003ccode\u003eprocess/runner.go\u003c/code\u003e conecta:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/process/runner.go (from the 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003enativeImageWorkloadManager\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStart\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eContext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStop\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eContext\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eTres métodos. Start levanta todos los workloads. Stop los baja. \u003ccode\u003eStatePath()\u003c/code\u003e le dice al resto del sistema dónde vive el archivo de estado JSON, para que el lado Lua pueda encontrarlo. Eso es todo. El manager Docker construye imágenes, crea una red, inicia contenedores, y abre puertos publicados. El manager Firecracker construye imágenes, las convierte en bundles, arranca microVMs, y las conecta a una red de pares vsock. Mecánicas diferentes, contrato público idéntico.\u003c/p\u003e\n\u003cp\u003eEl manager docker-native es, honestamente, el docker-compose que me negué a escribir. Crea una red por proyecto (\u003ccode\u003ecli/internal/workloads/docker_native.go:90-98\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:90-98\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;fastfn-\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"nf\"\u003eshortHash\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eProjectDir\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ecli\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNetworkCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ectx\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003edockertypes\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eNetworkCreate\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eCheckDuplicate\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eDriver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"s\"\u003e\u0026#34;bridge\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eErrorf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;create docker network: %w\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003enetworkName\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLuego para cada service y cada app adjunta el contenedor con dos alias —el nombre simple y un alias \u003ccode\u003e\u0026lt;nombre\u0026gt;.internal\u003c/code\u003e (\u003ccode\u003edocker_native.go:417-423\u003c/code\u003e)— para que las apps dentro de la red puedan alcanzar \u003ccode\u003emysql.internal:3306\u003c/code\u003e mientras el host alcanza \u003ccode\u003e127.0.0.1:\u0026lt;publicado\u0026gt;\u003c/code\u003e. Esa división es intencional: las apps públicas alcanzan el mundo exterior a través del gateway en una URL estable, los services privados se alcanzan entre sí a través del alias interno, y el host alcanza los puertos publicados para depuración.\u003c/p\u003e\n\u003cp\u003eUn service arrancando se ve aproximadamente así:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:251-266 (condensed)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eServiceState\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e        \u003cspan class=\"nf\"\u003efirstNonEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eImage\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eDockerfile\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eImageDigest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"nx\"\u003edigest\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"s\"\u003e\u0026#34;127.0.0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e         \u003cspan class=\"nx\"\u003ehostPort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;.internal\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContainerID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e  \u003cspan class=\"nx\"\u003econtainerID\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e       \u003cspan class=\"nx\"\u003eWorkloadHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"nx\"\u003eUp\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eReason\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;ok\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e       \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eVolume\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eBaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e      \u003cspan class=\"nf\"\u003ecloneEnvMap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildServiceURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e         \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e         \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildServiceURL\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eFunctionEnv\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildFunctionServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos observaciones. Primero, \u003ccode\u003eBuildServiceURL\u003c/code\u003e inspecciona el env y descifra el esquema —si ve \u003ccode\u003eMYSQL_USER\u003c/code\u003e construye una URL \u003ccode\u003emysql://\u003c/code\u003e, si ve \u003ccode\u003ePOSTGRES_USER\u003c/code\u003e una \u003ccode\u003epostgres://\u003c/code\u003e, si ve \u003ccode\u003eREDIS_*\u003c/code\u003e una \u003ccode\u003eredis://\u003c/code\u003e, y de lo contrario recurre a \u003ccode\u003etcp://\u003c/code\u003e (\u003ccode\u003estate.go:143-157\u003c/code\u003e). Es una inferencia de URL despreocupada, y significa que no tengo que escribir \u003ccode\u003eDATABASE_URL\u003c/code\u003e a mano en el noventa y nueve por ciento de los casos. Segundo, \u003ccode\u003eFunctionEnv\u003c/code\u003e es la bolsa de variables que cada función del proyecto verá en tiempo de petición. Ese es el puente: el service es privado, pero las funciones obtienen \u003ccode\u003eSERVICE_MYSQL_HOST\u003c/code\u003e, \u003ccode\u003eSERVICE_MYSQL_PORT\u003c/code\u003e, \u003ccode\u003eSERVICE_MYSQL_URL\u003c/code\u003e, más un alias directo \u003ccode\u003eMYSQL_HOST\u003c/code\u003e / \u003ccode\u003eMYSQL_PORT\u003c/code\u003e / \u003ccode\u003eMYSQL_URL\u003c/code\u003e cuando el nombre es inequívoco (\u003ccode\u003estate.go:107-123\u003c/code\u003e, \u003ccode\u003estate.go:193-203\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003eLo cual es, cuando lo miras de cerca, exactamente lo que docker-compose hace con sus \u003ccode\u003elinks\u003c/code\u003e e inyección de \u003ccode\u003edepends_on\u003c/code\u003e. Solo con un esquema más ajustado y una convención de nombres más afilada.\u003c/p\u003e\n\u003ch2 id=\"capítulo-5-cómo-lua-encuentra-un-workload\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-5-c%c3%b3mo-lua-encuentra-un-workload\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 5: Cómo Lua Encuentra un Workload'\"\u003e\u003c/a\u003e5 Capítulo 5: Cómo Lua Encuentra un Workload\u003c/h2\u003e\u003cp\u003eAhora la parte divertida. El gateway sigue siendo OpenResty + Lua —la misma cosa de la Parte 1. ¿Cómo le dice un proceso Go longevo del lado del CLI a Lua del lado del gateway que los workloads existen y están activos?\u003c/p\u003e\n\u003cp\u003eA través de un archivo JSON en disco. Eso es todo.\u003c/p\u003e\n\u003cp\u003eEl manager de workloads del CLI escribe en un archivo de estado en un camino conocido. El camino se exporta como una variable de entorno al gateway (\u003ccode\u003eprocess/runner.go\u003c/code\u003e en el diff de 6a54c11):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/process/runner.go (from 6a54c11)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eworkloadMgr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eStatePath\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e \u003cspan class=\"nx\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;FN_IMAGE_WORKLOADS_STATE_PATH=\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEn el lado Lua, \u003ccode\u003eimage_workloads.lua\u003c/code\u003e lee ese camino en cada petición:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/core/image_workloads.lua:22-48\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003estate_path\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos.getenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_IMAGE_WORKLOADS_STATE_PATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eload_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estate_path\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eapps\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eservices\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eread_json_file\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eapps\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eservices\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e     \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed.apps\u003c/span\u003e     \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed.services\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLectura JSON por petición. En producción cachearías esto por algún TTL pequeño; en dev es exactamente lo correcto. El estado de salud, las rutas, los hostnames internos, el estado del ciclo de vida —todo en ese archivo.\u003c/p\u003e\n\u003cp\u003eEl routing es el siguiente paso. El gateway ya hace un largo baile de coincidencias para las funciones (cubierto en la Parte 1). Para los workloads, le pregunta a \u003ccode\u003eimage_workloads.lua\u003c/code\u003e por candidatos cuyas rutas coincidan como prefijo con el camino de la petición, luego los puntúa por longitud de ruta (las rutas más largas ganan, porque \u003ccode\u003e/admin/api/v1/users\u003c/code\u003e es más específico que \u003ccode\u003e/admin/*\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/core/image_workloads.lua:123-151\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003epublic_http_candidates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_path\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003estate\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eload_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esorted_keys\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekind\u003c/span\u003e\u003cspan class=\"p\"\u003e]))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estate\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ekind\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload_public_endpoints\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eworkload\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint.protocol\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eroutes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eendpoint.routes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint.routes\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroutes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eroute_matches\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_path\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekind\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;service\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_name\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eendpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eroute_length\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroute\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecandidates\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos detalles que me gustan. El loop externo itera \u003ccode\u003e{\u0026quot;apps\u0026quot;,\u0026quot;services\u0026quot;}\u003c/code\u003e en vez de concatenarlos, lo cual preserva la prioridad de las apps sobre los services cuando la misma ruta es reclamada dos veces (lo cual no debería ocurrir, pero el código defensivo es barato). Y \u003ccode\u003eroute_length\u003c/code\u003e viaja con cada candidato para que el llamador pueda elegir la coincidencia más larga, lo cual coincide con la forma en que todo router sensato resuelve prefijos superpuestos.\u003c/p\u003e\n\u003cp\u003eEl gateway consume esta lista en tiempo de petición y hace proxy al endpoint ganador (\u003ccode\u003eopenresty/lua/fastfn/http/gateway.lua:950-969\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:950-969\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_host\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_authority\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequest_host_values\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_endpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eworkload_access_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ematch_public_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eimage_workloads.public_http_candidates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_host\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_authority\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003erequest_client_ip\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;table\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eexecute_public_workload_proxy\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ematched_workload\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ematched_endpoint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e502\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;unavailable\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003estatus\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e503\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ewrite_response\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                   \u003cspan class=\"n\"\u003ejson_error\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;public workload proxy failed: \u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ewrite_response\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eapp_resp.status\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"mi\"\u003e502\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp.headers\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e \u003cspan class=\"n\"\u003eapp_resp.body\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eY aquí el proxy real —HTTP de entrada, HTTP de salida, con los headers hop-by-hop eliminados de vuelta para que nada confunda al cliente (\u003ccode\u003egateway.lua:487-537\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:510-536\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehttp_client.request\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eurl\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003estring.format\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://%s:%d%s\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ehost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eport\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003engx.var\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequest_uri\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_method\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esanitize_app_request_headers\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_headers\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ebody\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e30000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003emax_body_bytes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003edrop\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;connection\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;keep-alive\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;transfer-encoding\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;content-length\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;upgrade\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003epairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresp.headers\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{})\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003edrop\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\u003cspan class=\"n\"\u003elower\u003c/span\u003e\u003cspan class=\"p\"\u003e()]\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eresp.headers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003efiltered\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eQuince megabytes de cuerpo máximo. Treinta segundos de timeout. Headers hop-by-hop eliminados. Este es el proxy HTTP inverso más aburrido que habrás leído nunca, que es exactamente lo que quieres que sea un gateway.\u003c/p\u003e\n\u003cp\u003eLo inteligente no está en el proxy. Lo inteligente es que \u003cem\u003ela misma fase ngx que resuelve funciones ahora también resuelve workloads\u003c/em\u003e, con una prioridad conocida, desde el mismo archivo de estado JSON, usando las mismas reglas de firewall de host/CIDR. Todo el conjunto pasa a ser una superficie HTTP unificada.\u003c/p\u003e\n\u003ch2 id=\"capítulo-6-ciclo-de-vida-o-convirtiendo-una-tabla-en-una-máquina-de-estados\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-6-ciclo-de-vida-o-convirtiendo-una-tabla-en-una-m%c3%a1quina-de-estados\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 6: Ciclo de Vida (o, Convirtiendo una Tabla en una Máquina de Estados)'\"\u003e\u003c/a\u003e6 Capítulo 6: Ciclo de Vida (o, Convirtiendo una Tabla en una Máquina de Estados)\u003c/h2\u003e\u003cp\u003eUna vez que los workloads existen, tienen que pasar por una vida. Arrancando. Saludable. No saludable. Detenido. Pausado. Reanudado. El ciclo de vida mantiene honesta la historia operacional.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ┌────────────┐   start()   ┌─────────────┐   healthcheck ok   ┌─────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    │  declared  │ ──────────▶ │  starting   │ ─────────────────▶ │ healthy │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    └────────────┘             └─────────────┘                    └────┬────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     │                                 │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     │ healthcheck fails               │ monitor tick\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                     ▼                                 │ reports failure\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               ┌─────────────┐                         ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               │  unhealthy  │ ◀──────────────── ┌─────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                               └──────┬──────┘                    │ flapping│\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                      │ stop()                   └─────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                      ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                ┌───────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                │  stopped  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                └───────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEn state.go, las piezas viven en un pequeño cluster de tipos (\u003ccode\u003estate.go:23-91\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/state.go:23-38\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorkloadHealth\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eUp\u003c/span\u003e     \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;up\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eReason\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;reason,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003ePublicEndpointState\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;name\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eProtocol\u003c/span\u003e      \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;protocol,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eHost\u003c/span\u003e          \u003cspan class=\"kt\"\u003estring\u003c/span\u003e   \u003cspan class=\"s\"\u003e`json:\u0026#34;host,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ePort\u003c/span\u003e          \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eContainerPort\u003c/span\u003e \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;container_port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eListenPort\u003c/span\u003e    \u003cspan class=\"kt\"\u003eint\u003c/span\u003e      \u003cspan class=\"s\"\u003e`json:\u0026#34;listen_port,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eRoutes\u003c/span\u003e        \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;routes,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAllowHosts\u003c/span\u003e    \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;allow_hosts,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eAllowCIDRs\u003c/span\u003e    \u003cspan class=\"p\"\u003e[]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"s\"\u003e`json:\u0026#34;allow_cidrs,omitempty\u0026#34;`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa señal de salud es un struct de dos campos —arriba, y una razón cuando no lo está. Cada workload mantiene una. El manager docker-native corre una goroutine de monitor que se despierta cada dos segundos, inspecciona cada contenedor, y actualiza el archivo de estado cuando algo cambió (\u003ccode\u003edocker_native.go:174-211\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/docker_native.go:174-211 (condensed)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eNativeManager\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003emonitor\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nb\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edoneCh\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNewTicker\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eSecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edefer\u003c/span\u003e \u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eStop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eselect\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003estopCh\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-\u003c/span\u003e\u003cspan class=\"nx\"\u003eticker\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nx\"\u003echanged\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eitem\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"k\"\u003erange\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003econtainers\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003ehealth\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003einspectHealth\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"c1\"\u003e// ... update m.state.Apps or m.state.Services,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e                \u003cspan class=\"c1\"\u003e//     setting changed = true when different\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003echanged\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ewriteState\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl comportamiento de escribir-solo-cuando-cambia importa. El lado Lua lee ese archivo de estado en cada petición. Si el lado Go escribiera en el archivo cada dos segundos sin condición alguna, cada petición vería un archivo nuevo y cada pequeña lectura de fs sería en vano. Escribir solo cuando algo \u003cem\u003erealmente se movió\u003c/em\u003e mantiene el archivo estable por largos períodos, que es lo que quieres de un mecanismo de IPC que es, con benevolencia, un archivo JSON.\u003c/p\u003e\n\u003cp\u003eEl arranque es serial a propósito: los services suben primero, luego las apps (\u003ccode\u003edocker_native.go:100-128\u003c/code\u003e). Las apps arrancan con el env del service ya poblado en su entorno —que es el momento donde la simetría limpia del archivo de configuración se afirma como un orden de dependencias. Los services existen para que las apps puedan usarlos; por lo tanto los services arrancan primero.\u003c/p\u003e\n\u003cp\u003eEl punto de entrada del CLI son las mismas dos líneas aburridas tanto en \u003ccode\u003edev\u003c/code\u003e como en \u003ccode\u003erun\u003c/code\u003e. Para \u003ccode\u003edev\u003c/code\u003e, del diff de 6a54c11:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/cmd/dev.go (from 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003eimageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ehasImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003econfiguredImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatalf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Invalid apps/services config: %v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003erunNative\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nf\"\u003econfiguredProjectRoot\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"nx\"\u003eabsPath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eimageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatalf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Native dev failed: %v\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003ehasImageWorkloads\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003edevFatal\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;apps/services are only supported in native mode for this branch; rerun with --native\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsa última rama es importante. Las apps y los services solo funcionan con \u003ccode\u003e--native\u003c/code\u003e en esta rama; el modo dev clásico de Docker sigue siendo solo para funciones. Es una limitación conocida, documentada explícitamente en la referencia de configuración (\u003ccode\u003edocs/en/reference/fastfn-config.md:13\u003c/code\u003e). Mezclar los dos ciclos de vida en el antiguo camino de gateway respaldado por Docker era el tipo de yak-shaving que decidí posponer. El modo nativo es el único camino honesto a seguir para esta funcionalidad.\u003c/p\u003e\n\u003cp\u003eEl helper \u003ccode\u003econfiguredImageWorkloads\u003c/code\u003e es el pegamento. Lee las claves \u003ccode\u003eapps\u003c/code\u003e y \u003ccode\u003eservices\u003c/code\u003e de viper y las normaliza a través de \u003ccode\u003eworkloads.NormalizeAppSpecs\u003c/code\u003e / \u003ccode\u003eNormalizeServiceSpecs\u003c/code\u003e (\u003ccode\u003ecli/cmd/root.go:171-186\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-21\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/cmd/root.go:171-186 (from 6a54c11 diff)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003econfiguredImageWorkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003evar\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eConfig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eapps\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eappsSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNormalizeAppSpecs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;apps\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservicesSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eworkloads\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eNormalizeServiceSpecs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;services\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eerr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eApps\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eapps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eServices\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservices\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003ecfg\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eappsSet\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003eservicesSet\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse es todo el cableado. Viper lee \u003ccode\u003efastfn.json\u003c/code\u003e, el paquete de workloads lo normaliza, y el runner nativo levanta un manager cuyo \u003ccode\u003eStatePath()\u003c/code\u003e queda estampado en el entorno de cada daemon de runtime y cada worker de OpenResty.\u003c/p\u003e\n\u003ch2 id=\"capítulo-7-el-giro-de-firecracker\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-7-el-giro-de-firecracker\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 7: El Giro de Firecracker'\"\u003e\u003c/a\u003e7 Capítulo 7: El Giro de Firecracker\u003c/h2\u003e\u003cp\u003eEn este punto todo lo que he descrito funciona en un laptop con Docker, y nada de lo que he descrito necesita Firecracker. Pero toda la rama se llama \u003ccode\u003efirecracker-simple-images\u003c/code\u003e, así que déjame contarte dónde entra Firecracker, y qué cambió realmente.\u003c/p\u003e\n\u003cp\u003eEn un host Linux/KVM, el manager no es \u003ccode\u003edocker_native.go\u003c/code\u003e. Es \u003ccode\u003efirecracker_manager_linux.go\u003c/code\u003e. Misma interfaz. Mismo archivo de estado. Mecánicas muy diferentes por debajo: la imagen OCI se convierte en un bundle Firecracker (un kernel \u003ccode\u003evmlinux\u003c/code\u003e más un \u003ccode\u003erootfs.ext4\u003c/code\u003e), el bundle se cachea bajo \u003ccode\u003e.fastfn/firecracker/images/\u003c/code\u003e, y una microVM arranca con una configuración mínima del kernel. El contrato público es idéntico: el workload escucha en su puerto declarado, el gateway hace proxy a él, el archivo de estado reporta salud.\u003c/p\u003e\n\u003cp\u003eLo que es diferente —y aquí es donde importa el commit \u003ccode\u003e5568b6c\u003c/code\u003e (\u0026ldquo;Add vsock peer networking for Firecracker workloads\u0026rdquo;)— es cómo el gateway realmente \u003cem\u003ealcanza\u003c/em\u003e a un guest Firecracker. Un contenedor Docker regular publica un puerto en un bridge y te conectas a \u003ccode\u003e127.0.0.1:\u0026lt;puertohost\u0026gt;\u003c/code\u003e. Una microVM Firecracker no tiene esa conveniencia por defecto. Así que el commit introduce una red de pares basada en vsock: un helper del lado del guest (\u003ccode\u003ecli/internal/firecrackerguest/main.go\u003c/code\u003e, 455 líneas en ese commit) que termina vsock, y un \u003ccode\u003eprivate_network.go\u003c/code\u003e del lado del host que une las piezas. El gateway sigue conectándose a un \u003ccode\u003eInternalHost:InternalPort\u003c/code\u003e; la plomería por debajo de ese host/puerto es vsock en vez de TCP en un bridge. La abstracción se mantiene.\u003c/p\u003e\n\u003cp\u003eEl commit de seguimiento \u003ccode\u003e6fd5fec\u003c/code\u003e (\u0026ldquo;Keep Firecracker image workloads hot by default\u0026rdquo;) es el que hizo esto útil en la práctica. Sin él, un workload Firecracker recién arrancado se sentiría genial en la primera petición y menos genial si alguna vez se pausaba. Con los defaults keep-hot, el workload se mantiene residente y se precalienta al arrancar \u003ccode\u003efastfn dev\u003c/code\u003e / \u003ccode\u003efastfn run\u003c/code\u003e. Los docs llaman a esto \u0026ldquo;speed-first\u0026rdquo; (\u003ccode\u003edocs/en/reference/fastfn-config.md:302-307\u003c/code\u003e): \u003ccode\u003eidle_action\u003c/code\u003e por defecto es \u003ccode\u003erun\u003c/code\u003e, \u003ccode\u003eprewarm\u003c/code\u003e por defecto es \u003ccode\u003etrue\u003c/code\u003e, tanto para apps como para services.\u003c/p\u003e\n\u003cp\u003eLos números reales —y estos están copiados de la matriz de benchmark en \u003ccode\u003edocs/en/explanation/performance-benchmarks.md\u003c/code\u003e— son la única razón por la que estoy dispuesto a llamar \u0026ldquo;terminado\u0026rdquo; a cualquiera de esto:\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eCase\u003c/th\u003e\n\u003cth\u003eSource\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eBuild/Pull\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eFirst OK\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p50\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p95\u003c/th\u003e\n\u003cth style=\"text-align:right\"\u003eHot p99\u003c/th\u003e\n\u003cth\u003eSame PID\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eFlask (\u003ccode\u003eflask-compose\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1168ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e5017ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1.94ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e3.05ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e4.10ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRegistry app (\u003ccode\u003etraefik/whoami:v1.10.2\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eRegistry image\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e98ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2508ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1.26ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.09ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.28ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eFastAPI + Postgres (\u003ccode\u003efastapi-realworld\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo + private service\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1202ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e17036ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e5.29ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e7.02ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e7.94ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eTwo equal \u003ccode\u003epostgres:16\u003c/code\u003e services\u003c/td\u003e\n\u003ctd\u003eSame OCI, same native \u003ccode\u003e5432\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e1246ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e22090ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e10.92ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e28.85ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e32.58ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRust + Postgres (\u003ccode\u003erust-postgres\u003c/code\u003e)\u003c/td\u003e\n\u003ctd\u003eDockerfile repo + private service\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e35139ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e47602ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e2.66ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e3.86ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:right\"\u003e\u003ccode\u003e10.27ms\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003etrue\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eCinco filas de una matriz de veinte casos (snapshot del 1 de abril de 2026; lista completa en \u003ccode\u003edocs/en/explanation/performance-benchmarks.md:46-54\u003c/code\u003e). Lo que leo en esta tabla es:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eLa construcción en frío + prewarm son segundos, a veces decenas de segundos para una construcción de Rust. Eso no es gratis. Pero ocurre una vez.\u003c/li\u003e\n\u003cli\u003eDespués del prewarm, el camino caliente son pocos milisegundos de un solo dígito para apps livianas y aún de un solo a bajo doble dígito para las respaldadas por BD.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esame_firecracker_pid = true\u003c/code\u003e en cada fila, lo que significa que el loop caliente realmente está reutilizando la misma microVM residente. El gateway no está secretamente reiniciando Firecracker entre mi petición y mi p95.\u003c/li\u003e\n\u003cli\u003eDos services \u003ccode\u003epostgres:16\u003c/code\u003e idénticos pueden compartir el mismo puerto nativo 5432 siempre que sus \u003cem\u003enombres\u003c/em\u003e de workload difieran. La red privada es por workload; el puerto es por proceso dentro de su propio guest.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eLa advertencia honesta —que el doc mismo reconoce, y me complace repetir (\u003ccode\u003edocs/en/explanation/performance-benchmarks.md:94-139\u003c/code\u003e)— es que la matriz de 20 casos no son \u0026ldquo;veinte apps upstream sin ediciones.\u0026rdquo; Algunos casos son upstream tal cual. Algunos tienen una capa de benchmark encima. Todos comparten el mismo camino de runtime de FastFN, pero el harness no es un benchmark sin tocar para todos ellos. Prefiero decirlo abiertamente a maquillarlo.\u003c/p\u003e\n\u003cp\u003eY sí, \u003ccode\u003efd8a6b5\u003c/code\u003e (\u0026ldquo;Add image workload firewall and benchmark matrix\u0026rdquo;) es el commit que trajo tanto el control de acceso \u003ccode\u003eallow_hosts\u003c/code\u003e / \u003ccode\u003eallow_cidrs\u003c/code\u003e en los puertos públicos como las herramientas que produjeron estos números. Cubriré el firewall en un post posterior —es toda una estética en sí misma— pero la versión corta es: una app pública puede restringirse a una lista de hosts permitidos y/o una lista de CIDR permitidos, ambas mostradas en la función de puntuación del gateway que cité en el Capítulo 5.\u003c/p\u003e\n\u003ch2 id=\"capítulo-8-las-funciones-se-encuentran-con-los-services-la-historia-de-la-inyección\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-8-las-funciones-se-encuentran-con-los-services-la-historia-de-la-inyecci%c3%b3n\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 8: Las Funciones Se Encuentran con los Services (La Historia de la Inyección)'\"\u003e\u003c/a\u003e8 Capítulo 8: Las Funciones Se Encuentran con los Services (La Historia de la Inyección)\u003c/h2\u003e\u003cp\u003eVolviendo a un detalle que pasé por alto. ¿Cómo ve exactamente una función a un service?\u003c/p\u003e\n\u003cp\u003eA través del env. Cuando arranca un service, el manager llama a \u003ccode\u003eBuildFunctionServiceEnv\u003c/code\u003e y guarda el resultado en el estado del service (\u003ccode\u003estate.go:107-123\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-22\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cli/internal/workloads/state.go:107-123\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003eBuildFunctionServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e \u003cspan class=\"nx\"\u003eServiceState\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kd\"\u003emap\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eappendScopedServiceEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003ebaseEnv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eupper\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nf\"\u003eserviceEnvToken\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e         \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%d\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e          \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalHost\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003efmt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSprintf\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;%d\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalPort\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003estrings\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eTrimSpace\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;SERVICE_\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eupper\u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_INTERNAL_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eInternalURL\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eappendDirectServiceAlias\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eserviceName\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eHost\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePort\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eURL\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEn el lado Lua, cuando una función está a punto de ser invocada, el gateway extrae la unión de los FunctionEnv de todos los services al envelope de evento que pasa al daemon de runtime (\u003ccode\u003eopenresty/lua/fastfn/http/gateway.lua:1169-1174\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003elua\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-23\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- openresty/lua/fastfn/http/gateway.lua:1171-1174\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimage_workloads.function_env\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003enext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eevent.env\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eservice_env\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLo que significa que una función Python simplemente puede hacer:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-24\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# a handler somewhere in functions/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ehost\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_HOST\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eport\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_PORT\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eurl\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SERVICE_MYSQL_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e…y funciona, ya sea que el MySQL de respaldo sea un contenedor Docker en macOS o una microVM Firecracker en Linux. Mismos nombres de variables, misma inferencia de esquema de URL, mismo código. El cambio de backend es invisible.\u003c/p\u003e\n\u003cp\u003eTambién hay un segundo camino. Una \u003cstrong\u003eapp\u003c/strong\u003e necesita el env del service al iniciar el proceso, no en tiempo de petición —porque una app Next.js lee \u003ccode\u003eprocess.env\u003c/code\u003e en \u003ccode\u003enext start\u003c/code\u003e, no por petición. Entonces el manager docker-native construye un mapa appServiceEnv de todos los services y lo pasa como el env del contenedor de cada app (\u003ccode\u003edocker_native.go:110-122\u003c/code\u003e, \u003ccode\u003e282-294\u003c/code\u003e). La app por lo tanto ve las mismas variables \u003ccode\u003eSERVICE_*\u003c/code\u003e, pero con scope a través de \u003ccode\u003eBuildAppServiceEnv\u003c/code\u003e en vez de \u003ccode\u003eBuildFunctionServiceEnv\u003c/code\u003e —la diferencia está en si usas el hostname interno (para apps, que viven en la misma red privada) o el host público (para funciones, que viven en el host).\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-25\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        service discovery fan-out\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        ────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       ┌──────────────┐      BuildFunctionServiceEnv\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       │   service    │ ───────────────────────────▶  event.env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       │  (e.g. mysql)│                               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       └──────┬───────┘                               ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │                                ┌─────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │                                │  function   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              │      BuildAppServiceEnv        └─────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              └──────────────────────▶\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        container.Env\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  ┌──────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  │   app    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  └──────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos consumidores. Una fuente de verdad. Sin \u003ccode\u003eDATABASE_URL\u003c/code\u003e escrito a mano a la vista.\u003c/p\u003e\n\u003ch2 id=\"capítulo-9-las-lecciones-que-realmente-me-llevo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-9-las-lecciones-que-realmente-me-llevo\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 9: Las Lecciones Que Realmente Me Llevo'\"\u003e\u003c/a\u003e9 Capítulo 9: Las Lecciones Que Realmente Me Llevo\u003c/h2\u003e\u003cp\u003eMirando hacia atrás al diff de 2254 líneas, esto es lo que le diría a mi yo del pasado antes de empezar.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUnificar el gateway es lo fundamental.\u003c/strong\u003e La tentación de dividir HTTP en \u0026ldquo;fastfn maneja las URLs de funciones\u0026rdquo; y \u0026ldquo;docker-compose maneja todo lo demás\u0026rdquo; es enorme porque es el camino de menor resistencia. Pero cada división de la superficie HTTP es un bug futuro en CORS, auth, observabilidad, o OpenAPI. Mantén el gateway único. Dale más tipos de objetivos, no más amigos.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDos modelos de ciclo de vida pueden coexistir si un archivo de configuración hace la compilación.\u003c/strong\u003e Las funciones tienen scope de petición. Los workloads son longevos. Son formas genuinamente diferentes con modos de falla diferentes. Ocultarlos a ambos detrás de un \u003ccode\u003efastfn.json\u003c/code\u003e funcionó porque la capa de configuración compila ambas formas hacia la misma representación de runtime \u0026ldquo;cosa-a-la-que-el-gateway-hace-proxy.\u0026rdquo; Dos modelos de ciclo de vida, un archivo de configuración, cero arrepentimientos.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDeclara la fuente de imagen exactamente una vez.\u003c/strong\u003e La regla exactamente-uno en \u003ccode\u003eimage\u003c/code\u003e/\u003ccode\u003eimage_file\u003c/code\u003e/\u003ccode\u003edockerfile\u003c/code\u003e parece pedante el día uno y te salva de mensajes de error ilegibles el día doscientos. Quieres que haya una manera en que un ingeniero mirando \u003ccode\u003efastfn.json\u003c/code\u003e pueda decir de dónde viene este workload.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUn archivo JSON es una superficie de IPC perfectamente válida entre tu CLI y tu gateway, si tienes cuidado.\u003c/strong\u003e Escribir solo cuando hay cambio, leer por petición, y exportar el camino a través de una variable de entorno es —en contra de mis priors— una manera extremadamente tranquila de mover información entre un proceso Go longevo y un proceso OpenResty longevo. Hay un futuro donde esto se convierte en un unix socket y un modelo de suscripción. Pero por ahora, el archivo es honesto y fácil de depurar con \u003ccode\u003ecat\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNombra los services por lo que son.\u003c/strong\u003e Casi no auto-generé los alias \u003ccode\u003eMYSQL_HOST\u003c/code\u003e / \u003ccode\u003eMYSQL_URL\u003c/code\u003e junto a los \u003ccode\u003eSERVICE_\u0026lt;NOMBRE\u0026gt;_HOST\u003c/code\u003e. Luego escribí mi primer handler real y recordé que los humanos no quieren escribir \u003ccode\u003eSERVICE_MYSQL_HOST\u003c/code\u003e. Los alias directos son una funcionalidad de usabilidad disfrazada de convención de nombres.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLos benchmarks te obligan a ser honesto.\u003c/strong\u003e La tabla de la matriz anterior es la única razón por la que creo las palabras \u0026ldquo;residente\u0026rdquo; y \u0026ldquo;caliente\u0026rdquo; en mi propia documentación. \u003ccode\u003esame_firecracker_pid = true\u003c/code\u003e significa que nadie está secretamente reiniciando la VM entre mi petición y mi p95. Ese chequeo existe porque al principio \u003cem\u003eno\u003c/em\u003e era verdad, y el benchmark fue lo único que me lo dijo. Mide la propiedad, no la intención.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eY unas palabras sobre las salvedades.\u003c/strong\u003e Hay mucho que deliberadamente no hice en esta rama. Las apps y los services solo funcionan con \u003ccode\u003e--native\u003c/code\u003e. Los workloads Firecracker solo funcionan en Linux/KVM. Los usuarios de macOS y Windows obtienen el manager Docker en vez de Firecracker por ahora, y ese camino no corre workloads de imagen en absoluto fuera del modo nativo en esta rama. Actualizaciones graduales, blue/green, autoscaling —todos ausentes, y está bien que estén ausentes, porque el objetivo de esta fase era clavar la abstracción, no las operaciones. Un manager de workloads que hace tres backends limpiamente vale mucho más que un manager de workloads que hace un backend con cada funcionalidad que Kubernetes alguna vez envió.\u003c/p\u003e\n\u003cp\u003e¿Qué sigue? Hay dos direcciones que puedo ver claramente. Una es llevar el camino de workload al modo Docker \u003ccode\u003efastfn dev\u003c/code\u003e para que los usuarios de macOS obtengan la misma ergonomía sin Firecracker. La otra es hacer el firewall más rico —más allá de \u003ccode\u003eallow_hosts\u003c/code\u003e y \u003ccode\u003eallow_cidrs\u003c/code\u003e, hacia algo que pueda expresar \u0026ldquo;este service solo es alcanzable desde estos workloads específicos en esta red privada.\u0026rdquo; Ambas se sienten como capítulos genuinamente nuevos, no como notas al pie.\u003c/p\u003e\n\u003cp\u003ePor ahora, lo esencial es pequeño, aburrido y correcto: si necesitas un Postgres junto a tus funciones, editas tu \u003ccode\u003efastfn.json\u003c/code\u003e, añades un bloque \u003ccode\u003eservices.postgres\u003c/code\u003e, y tus funciones obtienen \u003ccode\u003eSERVICE_POSTGRES_URL\u003c/code\u003e en su entorno. Eso es todo.\u003c/p\u003e\n\u003cp\u003eLas funciones son una gran forma para request/response. Los workloads son la forma para todo lo que no lo es. Y ahora viven en el mismo archivo de configuración, el mismo gateway, el mismo endpoint de salud, la misma matriz de benchmark, con los mismos defaults keep-hot.\u003c/p\u003e\n\u003cp\u003eNo quería escribir docker-compose. Terminé escribiendo algo que rima con la décima parte de docker-compose que realmente uso, y nada más. Lo cual, si soy honesto, es probablemente la primera vez en mi carrera que construí \u003cem\u003emenos\u003c/em\u003e plataforma de la que pensé que construiría.\u003c/p\u003e\n\u003cp\u003eNos vemos en la Parte 3, donde voy a desmontar en detalle la historia del firewall, la plomería vsock, y los defaults keep-hot. Hasta entonces: un archivo, una ruta, un workload, un gateway.\u003c/p\u003e\n",
        "language": "es"
    }
    {
        "title" : "fastfn Parte 1: Tenía un Problema (y Le Metí Lua a Mi Vida)",
        "date_published" : "2026-06-02T00:00:00Z",
        "date_modified" : "2026-06-02T00:00:00Z",
        "id" : "https://misael.org/es/fastfn-lua-to-our-lives/",
        "url" : "https://misael.org/es/fastfn-lua-to-our-lives/",
        "summary": "Cómo construir un FaaS personal terminó rimando con FastCGI — runtimes políglotas, un gateway en Lua y un protocolo de red en JSON. La Parte 1 cubre el lado de las funciones; la Parte 2 cubre los servicios.",
        "content_html" : "\u003ch2 id=\"capítulo-1-el-hartazgo-o-por-qué-un-hello-world-son-siete-archivos\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-1-el-hartazgo-o-por-qu%c3%a9-un-hello-world-son-siete-archivos\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 1: El Hartazgo, o \u0026amp;ldquo;¿Por Qué Un Hello World Son Siete Archivos?\u0026amp;rdquo;'\"\u003e\u003c/a\u003e1 Capítulo 1: El Hartazgo, o \u0026ldquo;¿Por Qué Un Hello World Son Siete Archivos?\u0026rdquo;\u003c/h2\u003e\u003cp\u003eTodo empezó con una queja que da un poco de vergüenza decir en voz alta en 2026: quería soltar un archivo Python en disco y que ese archivo fuera un endpoint HTTP. Nada más. Sin Dockerfile. Sin \u003ccode\u003erequirements.txt\u003c/code\u003e a menos que yo quisiera uno. Sin el boilerplate de \u003ccode\u003eapp = FastAPI()\u003c/code\u003e, sin invocar \u003ccode\u003euvicorn\u003c/code\u003e, sin el wizard de \u0026ldquo;crear un proyecto\u0026rdquo; que te deja diecisiete archivos de configuración que vas a pasar las próximas tres tardes borrando. Y —aquí es donde se pone opinionado— quería un cold start razonable. No Lambda-cold, donde la primera petición de la mañana parece un 404 con pasos de más. Algo tibio. A escala humana.\u003c/p\u003e\n\u003cp\u003eMe había cansado de la forma de los frameworks web modernos. Son hermosos, escalan, tienen ecosistemas, y también te piden que compiles mentalmente un modelo completo de su mundo antes de que tu primera ruta responda con \u003ccode\u003e{\u0026quot;hello\u0026quot;: \u0026quot;world\u0026quot;}\u003c/code\u003e. Para una herramienta interna desechable, eso es un impuesto que se paga en atención. Llevaba una década pagándolo. Quería parar.\u003c/p\u003e\n\u003cp\u003eLo otro que quería —y esta es la funcionalidad que silenciosamente impulsa el resto de la historia— era \u003cstrong\u003epolíglotas por defecto\u003c/strong\u003e. No políglotas como en \u0026ldquo;microservicios en distintos lenguajes comunicándose por gRPC.\u0026rdquo; Políglotas como en que el mismo árbol de URLs puede tener \u003ccode\u003eget.users.py\u003c/code\u003e, \u003ccode\u003epost.orders.js\u003c/code\u003e, y \u003ccode\u003eget.health.go\u003c/code\u003e, viviendo juntos en la misma carpeta, detrás del mismo gateway. Routing basado en archivos al estilo Next.js, pero agnóstico al runtime. Ese era el sueño.\u003c/p\u003e\n\u003cp\u003eEl objetivo estaba claro: un Function-as-a-Service, pero local-first, con un CLI como interfaz principal y un árbol de archivos como base de datos. Lo llamé \u003ccode\u003efastfn\u003c/code\u003e. El README describe la ambición en una sola línea: \u0026ldquo;Empieza con un archivo, un CLI amigable, y un árbol de rutas que puede crecer hasta convertirse en una API real o un SPA sin necesitar una reescritura después\u0026rdquo; (\u003ccode\u003eREADME.md:8\u003c/code\u003e). El resto de este post es la historia de cómo esa oración se convirtió en un gateway de Lua, un daemon de Python persistente, y un protocolo con un prefijo de longitud de 4 bytes. Es la historia de descubrir, lentamente y con cierta vergüenza, que estaba reinventando FastCGI.\u003c/p\u003e\n\u003ch2 id=\"capítulo-2-una-historia-breve-y-levemente-injusta-de-cgi\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-2-una-historia-breve-y-levemente-injusta-de-cgi\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 2: Una Historia Breve y Levemente Injusta de CGI'\"\u003e\u003c/a\u003e2 Capítulo 2: Una Historia Breve y Levemente Injusta de CGI\u003c/h2\u003e\u003cp\u003eAntes de llegar a lo que es \u003ccode\u003efastfn\u003c/code\u003e, hay que hablar de con qué rima.\u003c/p\u003e\n\u003ch3 id=\"cgi-el-serverless-original\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cgi-el-serverless-original\" class=\"header-mark\" aria-label=\"Header mark for 'CGI: el serverless original'\"\u003e\u003c/a\u003e2.1 CGI: el serverless original\u003c/h3\u003e\u003cp\u003eAl principio existía CGI. La Common Gateway Interface era serverless antes de que serverless fuera una marca. Ponías un script en \u003ccode\u003e/cgi-bin/\u003c/code\u003e, el servidor web hacía \u003ccode\u003efork\u003c/code\u003e y \u003ccode\u003eexec\u003c/code\u003e en cada petición, el script leía la petición desde variables de entorno y stdin, escribía la respuesta en stdout, y terminaba. El sistema operativo limpiaba todo. Cada petición era un proceso. Cada proceso era un universo que existía por 40 milisegundos y luego moría.\u003c/p\u003e\n\u003cp\u003eEs maravilloso. También es un crimen de rendimiento.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCGI request model (what dies for you every time)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  client                 web server                 /cgi-bin/hello.pl\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   HTTP GET /hello     |                            .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | --------------------\u0026gt; | fork()                     .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       | exec(\u0026#34;perl hello.pl\u0026#34;)  --\u0026gt; [ cold Perl VM ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            [ parse script ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            [ run handler ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       | \u0026lt;-- stdout --              [ die ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | \u0026lt;-- HTTP 200 --       |                            .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                       |                            .\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCada petición paga por la creación del proceso, el calentamiento del intérprete, la importación de librerías, y el teardown. En una máquina de 1996 dolía. En una de 2026 sigue doliendo, solo que distinto: Python con caché fría gasta un porcentaje no trivial de tiempo simplemente importando su propia librería estándar antes de que tu handler escriba un solo byte. No he hecho profiling de esto end-to-end específicamente para \u003ccode\u003efastfn\u003c/code\u003e, pero el orden de magnitud es suficientemente grande como para que el pool persistente al estilo FastCGI exista precisamente para amortizarlo.\u003c/p\u003e\n\u003ch3 id=\"fastcgi-el-arreglo-y-la-forma-que-terminé-copiando\" class=\"headerLink\"\u003e\n    \u003ca href=\"#fastcgi-el-arreglo-y-la-forma-que-termin%c3%a9-copiando\" class=\"header-mark\" aria-label=\"Header mark for 'FastCGI: el arreglo, y la forma que terminé copiando'\"\u003e\u003c/a\u003e2.2 FastCGI: el arreglo, y la forma que terminé copiando\u003c/h3\u003e\u003cp\u003eFastCGI fue inventado exactamente para solucionar esto. La idea es casi obvia en retrospectiva: no mates el handler después de cada petición. Mantén un pool pequeño de procesos de handler vivos, deja que el servidor web se comunique con ellos por un Unix socket, y enmarca las peticiones para poder multiplexar limpiamente. El servidor web es el frontend; el pool de handlers es el backend; entre ellos fluye un stream de registros con prefijo de longitud.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eFastCGI\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e \u003cspan class=\"n\"\u003emodel\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewhat\u003c/span\u003e \u003cspan class=\"n\"\u003edoesn\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;t die for you)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eclient\u003c/span\u003e         \u003cspan class=\"n\"\u003eweb\u003c/span\u003e \u003cspan class=\"n\"\u003eserver\u003c/span\u003e           \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e          \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"n\"\u003epool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eGET\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e-----------\u0026gt;\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003epack\u003c/span\u003e \u003cspan class=\"n\"\u003erecord\u003c/span\u003e           \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e--------\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e------------\u0026gt;\u003c/span\u003e       \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003ealready\u003c/span\u003e \u003cspan class=\"n\"\u003ewarm\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003erun\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e              \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;-------\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"o\"\u003e|\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e------------\u003c/span\u003e        \u003cspan class=\"p\"\u003e[\u003c/span\u003e \u003cspan class=\"n\"\u003esleep\u003c/span\u003e \u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;---\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"o\"\u003e--\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e                       \u003cspan class=\"o\"\u003e|\u003c/span\u003e                    \u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLos handlers son longevos. El intérprete está caliente. El estado global de tu handler sobrevive entre peticiones (para bien y para mal). El transporte es un socket aburrido con registros enmarcados. Cambias el aislamiento por proceso por algo más parecido a una llamada por red. Es un trato muy bueno y es esencialmente lo que los servidores Python WSGI modernos, PHP-FPM y —seamos honestos— el pool de warm-start de AWS Lambda están haciendo internamente.\u003c/p\u003e\n\u003cp\u003eNo me propuse construir un FastCGI. Me propuse construir algo serverless donde sueltas un archivo y se convierte en una ruta. Resulta que una vez que quieres warm starts, handlers políglotas y un árbol de rutas direccionable por el sistema de archivos, el espacio de diseño te empuja hacia algo que parece FastCGI usando un trenchcoat de JSON. Más sobre el trenchcoat después.\u003c/p\u003e\n\u003ch2 id=\"capítulo-3-tenía-un-problema-y-le-metí-lua-a-mi-vida\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-3-ten%c3%ada-un-problema-y-le-met%c3%ad-lua-a-mi-vida\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 3: Tenía un Problema… y Le Metí Lua a Mi Vida'\"\u003e\u003c/a\u003e3 Capítulo 3: Tenía un Problema… y Le Metí Lua a Mi Vida\u003c/h2\u003e\u003cp\u003eAquí es donde el título cobra sentido.\u003c/p\u003e\n\u003cp\u003eEl gateway —la pieza que termina HTTP, lee la petición, determina qué archivo en disco debe manejarla, y reenvía la llamada— es la parte más importante y más irritante de cualquier FaaS. Tiene que ser rápido. Tiene que recargar cuando guardas un archivo. Tiene que hacer routing, auth, cookies, CORS, OpenAPI, y tiene que hacer todo eso sin convertirse en un proceso de Node de 30 MB con un cold start de 400 ms.\u003c/p\u003e\n\u003cp\u003eIntenté escribirlo en Go. Estuvo bien. También fue mucho código para lo que esencialmente es \u0026ldquo;toma una petición, busca un archivo, abre un socket, escribe, lee, escribe la respuesta.\u0026rdquo; Entonces una noche recordé que OpenResty —nginx con Lua embebido— ya hace las partes difíciles (parseo HTTP, TLS, epoll, memoria compartida) y simplemente me deja programar la capa de política en un lenguaje de scripting con startup de submilisegundo. No arrancas OpenResty por petición. OpenResty arranca una vez, al inicio del proceso, y luego tu Lua corre dentro de los hooks de fase de petición. Piénsalo como el servidor web invitando a tu código a vivir dentro de su event loop como huésped.\u003c/p\u003e\n\u003cp\u003eAsí que le metí Lua a mi vida. No fue una decisión tanto como un árbol interno de opciones que seguía ramificándose: cada tercera cosa que necesitaba resultaba ser \u0026ldquo;oh, puedo hacer esto en Lua y funciona.\u0026rdquo; ¿Descubrimiento de rutas? Lua recorriendo un directorio. ¿Tabla de rutas en memoria compartida? \u003ccode\u003engx.shared.DICT\u003c/code\u003e. ¿Recarga caliente al guardar? Un pequeño timer en Lua que hace \u003ccode\u003estat\u003c/code\u003e del directorio de funciones y reconstruye una tabla de rutas en un shared dict. ¿Parseo de cookies de sesión? Doce líneas:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/http/gateway.lua:70-82\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003eparse_cookies\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;string\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003epair\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003ecookie_header\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003egmatch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;[^;]+\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epair\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ematch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;^%s*([^=]+)%s*=%s*(.-)%s*$\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003ek\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ev\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003ecookies\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDoce líneas. Sin \u003ccode\u003enpm install cookie-parser\u003c/code\u003e. Sin \u003ccode\u003ego get github.com/gorilla/sessions\u003c/code\u003e. Sin dependencias transitivas abandonadas en 2019 y ahora mantenidas por un bot. Doce líneas de Lua que corren dentro de la fase de petición de nginx, y el gateway ya entiende cookies de sesión.\u003c/p\u003e\n\u003cdiv class=\"details admonition info open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\"/\u003e\u003c/svg\u003e\u003c/span\u003eUna nota sobre Lua\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eLua es un lenguaje raro. Indexa desde 1. Tiene una sola estructura de datos (la tabla) y nada que se parezca a un sistema de módulos real. Su biblioteca estándar es agresivamente mínima. Nada de eso duele aquí — el gateway es corto, los hot paths se quedan en LuaJIT, y las cosas que Lua \u003cem\u003eno tiene\u003c/em\u003e (un runtime grande, un ecosistema de paquetes, un sistema de tipos complicado) son exactamente las cosas que no quieres en un hot path dentro de nginx.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003eY es perfecto para este trabajo. El runtime es pequeño, el código es pequeño, la latencia es pequeña, y el gateway entero —routing, parseo de sesiones, marshalling de peticiones, un-marshalling de respuestas, endpoint de OpenAPI, servicio de Swagger UI— vive en un puñado de archivos Lua bajo \u003ccode\u003eopenresty/lua/fastfn/\u003c/code\u003e. El subárbol \u003ccode\u003ehttp/\u003c/code\u003e tiene exactamente los módulos que esperarías de un gateway real: \u003ccode\u003egateway.lua\u003c/code\u003e, \u003ccode\u003eassets.lua\u003c/code\u003e, \u003ccode\u003ecatalog.lua\u003c/code\u003e, \u003ccode\u003eopenapi_endpoint.lua\u003c/code\u003e, \u003ccode\u003eswagger_ui.lua\u003c/code\u003e, \u003ccode\u003ereload.lua\u003c/code\u003e. El \u003ccode\u003egateway.lua\u003c/code\u003e principal tiene 1341 líneas; el \u003ccode\u003eclient.lua\u003c/code\u003e completo que implementa el protocolo de red tiene 110 líneas. Voy a citar la mayor parte de él en un momento.\u003c/p\u003e\n\u003ch3 id=\"la-tabla-de-rutas-es-un-shared-dict\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-tabla-de-rutas-es-un-shared-dict\" class=\"header-mark\" aria-label=\"Header mark for 'La tabla de rutas es un shared dict'\"\u003e\u003c/a\u003e3.1 La tabla de rutas es un shared dict\u003c/h3\u003e\u003cp\u003eEl truco que hace rápido al gateway de Lua no es inteligente. Es que la tabla de rutas no vive en una base de datos, no vive en Redis, ni siquiera vive en la memoria por worker. Vive en \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e, una zona de memoria compartida de nginx legible por cada proceso worker. Cuando arranca \u003ccode\u003efastfn dev\u003c/code\u003e, Lua recorre el directorio de funciones, construye un índice —\u0026quot;\u003ccode\u003eGET /hello\u003c/code\u003e → \u003ccode\u003e/functions/get.hello.py\u003c/code\u003e → runtime \u003ccode\u003epython\u003c/code\u003e\u0026quot;— y lo mete en el shared dict. Al cambiar un archivo, un chunk Lua de recarga lo reconstruye. Las peticiones hacen una sola búsqueda en el shared dict en la fase \u003ccode\u003eaccess_by_lua\u003c/code\u003e y luego despachan. No hay \u0026ldquo;framework.\u0026rdquo; Hay una tabla hash y una convención.\u003c/p\u003e\n\u003ch2 id=\"capítulo-4-fastcgi-con-un-trenchcoat-de-json\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-4-fastcgi-con-un-trenchcoat-de-json\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 4: FastCGI con un Trenchcoat de JSON'\"\u003e\u003c/a\u003e4 Capítulo 4: FastCGI con un Trenchcoat de JSON\u003c/h2\u003e\u003cp\u003eAhora el protocolo. Aquí es donde accidentalmente reconstruí FastCGI.\u003c/p\u003e\n\u003cp\u003eCuando el gateway de Lua ha decidido que \u003ccode\u003eGET /hello\u003c/code\u003e debe ser servida por el runtime de Python, necesita llevar la petición al daemon de Python. El daemon de Python es un proceso longevo escuchando en un Unix socket (por defecto \u003ccode\u003e/tmp/fastfn/fn-python.sock\u003c/code\u003e, configurable via \u003ccode\u003eFN_PY_SOCKET\u003c/code\u003e, que puedes ver en \u003ccode\u003esrv/fn/runtimes/python-daemon.py:25\u003c/code\u003e). El gateway abre el socket, escribe la petición, y lee la respuesta.\u003c/p\u003e\n\u003cp\u003eEl formato de trama es deliberadamente aburrido:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"mi\"\u003e4\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e            \u003cspan class=\"n\"\u003eN\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e---------------\u003c/span\u003e    \u003cspan class=\"o\"\u003e----------------------------------------\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003ebig\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eendian\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e                                      \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003euint32\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e    \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eJSON\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erequest\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e---------------\u003c/span\u003e    \u003cspan class=\"o\"\u003e----------------------------------------\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCuatro bytes de longitud en big-endian. Luego esa cantidad de bytes de JSON. Ese es el protocolo entero. Es el enmarcado de registros de FastCGI, simplificado a un solo tipo de registro, con el cuerpo del registro siendo JSON en vez de la codificación binaria de clave-valor de FastCGI. Si FastCGI usara un trenchcoat e intentara pasar por una REST API moderna, así se vestiría.\u003c/p\u003e\n\u003cp\u003eEl lado Lua del protocolo es suficientemente pequeño para citarlo completo. Aquí está el cliente que envía una petición y parsea una respuesta:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/client.lua:22-106\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ecall_unix\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esocket_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson.encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid_request\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;failed to encode request\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003econnect_timeout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.max\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mf\"\u003e0.2\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.max\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e50\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etimeout_ms\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.socket\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etcp\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003esettimeouts\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econnect_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eio_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esocket_uri\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eis_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;connect timeout\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;connect_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003esent\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esend_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003esend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003esent\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eis_timeout\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esend_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;timeout\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;send timeout\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;send_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esend_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eheader_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ereceive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;receive_error\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eheader_err\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eunpack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid_response\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;invalid frame length\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebody_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003ereceive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebody_len\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esock\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- decode, validate, return\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa función \u003ccode\u003epack_u32\u003c/code\u003e manual es un bonito recordatorio de que Lua 5.1 (que usa OpenResty) no trae \u003ccode\u003estring.pack\u003c/code\u003e, así que la implemento yo mismo:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/client.lua:5-11\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003epack_u32\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb1\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e16777216\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e65536\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb3\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emath.floor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eb4\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"mi\"\u003e256\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003estring.char\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eb1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eb4\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl tope de trama es 10 MB (\u003ccode\u003ebody_len \u0026gt; 10 * 1024 * 1024\u003c/code\u003e). El lado Python impone un límite simétrico via \u003ccode\u003eFN_MAX_FRAME_BYTES\u003c/code\u003e, que por defecto es 2 MB (\u003ccode\u003esrv/fn/runtimes/python-daemon.py:26\u003c/code\u003e). Sí, los dos valores por defecto no coinciden. Eso es a propósito: el gateway es un poco más permisivo que el daemon para que un daemon mal configurado falle en el daemon, no en una lectura confusa a medias en el gateway. Es la clase de asimetría que se lee mal en una revisión de código y se lee bien cuando estás depurando un incidente en producción a las 2 AM.\u003c/p\u003e\n\u003ch3 id=\"el-ciclo-de-vida-de-una-petición-con-milisegundos-aproximados\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-ciclo-de-vida-de-una-petici%c3%b3n-con-milisegundos-aproximados\" class=\"header-mark\" aria-label=\"Header mark for 'El ciclo de vida de una petición, con milisegundos aproximados'\"\u003e\u003c/a\u003e4.1 El ciclo de vida de una petición, con milisegundos aproximados\u003c/h3\u003e\u003cp\u003eUna vez que tienes el protocolo de red, el ciclo de vida completo de una petición es fácil de dibujar. Aquí una petición warm, con tiempos aproximados que medí en un laptop de mediados de 2024 corriendo Linux:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.00\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eclient\u003c/span\u003e \u003cspan class=\"n\"\u003esends\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eGET\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehello\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.10\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003enginx\u003c/span\u003e \u003cspan class=\"n\"\u003eaccept\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eparse\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eheaders\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.20\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e \u003cspan class=\"n\"\u003eaccess_by_lua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eroute\u003c/span\u003e \u003cspan class=\"n\"\u003elookup\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshared\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efn_cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.30\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eencode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ereq_obj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eJSON\u003c/span\u003e \u003cspan class=\"n\"\u003epayload\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.40\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esocket\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etcp\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\u003cspan class=\"n\"\u003econnect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/tmp/fastfn/fn-python.sock\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.50\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003esend\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.60\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edecode\u003c/span\u003e \u003cspan class=\"n\"\u003elength\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.70\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003eN\u003c/span\u003e \u003cspan class=\"n\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eloads\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.80\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edispatch\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e \u003cspan class=\"n\"\u003efrom\u003c/span\u003e \u003cspan class=\"n\"\u003e_HANDLER_CACHE\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.10\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003ereturns\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;body\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;...\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.20\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"n\"\u003edaemon\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esend\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e\u003cspan class=\"n\"\u003eB\u003c/span\u003e \u003cspan class=\"n\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eJSON\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.30\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003erecv\u003c/span\u003e \u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edecode\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.40\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003eLua\u003c/span\u003e \u003cspan class=\"n\"\u003econtent_by_lua\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003engx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esay\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.50\u003c/span\u003e \u003cspan class=\"n\"\u003ems\u003c/span\u003e   \u003cspan class=\"n\"\u003enginx\u003c/span\u003e \u003cspan class=\"n\"\u003eflushes\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse\u003c/span\u003e \u003cspan class=\"n\"\u003eto\u003c/span\u003e \u003cspan class=\"n\"\u003eclient\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsos números son aproximados —no he publicado un benchmark end-to-end para el camino de función pura por sí solo, así que toma el desglose por línea en milisegundos como estimación, no como evangelio. La forma es correcta aunque los números exactos varíen. Los cold starts son una conversación muy diferente y llegaremos a eso.\u003c/p\u003e\n\u003ch2 id=\"capítulo-5-runtimes-políglotas-o-lambda-sin-amazon\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-5-runtimes-pol%c3%adglotas-o-lambda-sin-amazon\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 5: Runtimes Políglotas, o \u0026amp;ldquo;Lambda Sin Amazon\u0026amp;rdquo;'\"\u003e\u003c/a\u003e5 Capítulo 5: Runtimes Políglotas, o \u0026ldquo;Lambda Sin Amazon\u0026rdquo;\u003c/h2\u003e\u003cp\u003eEl protocolo de red es agnóstico al lenguaje a propósito. El daemon de Python es una implementación. Hay un daemon de Node. Hay un camino para handlers en Rust, Go, PHP, y Lua también. Cada daemon de runtime hace la misma promesa: abre un Unix socket, habla el protocolo de 4 bytes de longitud + JSON, y cuando llega una petición, despacha a una función con la firma que AWS Lambda hizo famosa.\u003c/p\u003e\n\u003cp\u003eAquí un handler real, de los ejemplos del tutorial políglotas:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/polyglot-tutorial/step-2/index.py:1-18\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ejson\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003equery\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eevent\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;query\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;friend\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;status\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;headers\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;body\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ejson\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edumps\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;step\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e from Python.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;runtime\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;python\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse es el contrato entero. \u003ccode\u003edef handler(event): return {...}\u003c/code\u003e. Forma Lambda sin Amazon. Forma Cloudflare Workers sin Cloudflare. También es, si lo miras con los ojos entrecerrados, simplemente CGI con las variables de entorno y stdin reemplazadas por un envelope JSON y un socket caliente. La forma ha sido estable desde los 90s porque es la forma correcta: una petición es un dict, una respuesta es un dict, y el handler es una función pura de uno al otro.\u003c/p\u003e\n\u003cp\u003eEl daemon soporta tres adaptadores de invocación explícitamente (\u003ccode\u003esrv/fn/runtimes/python-daemon.py:51-53\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_NATIVE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;native\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_AWS_LAMBDA\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;aws-lambda\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_INVOKE_ADAPTER_CLOUDFLARE_WORKER\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare-worker\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u0026ldquo;native\u0026rdquo; es mi forma por defecto. Los otros dos existen para que puedas levantar un Lambda o Cloudflare Worker existente en fastfn básicamente sin cambios. Esa interoperabilidad no fue gratis; me costó un cuidadoso traductor de forma de eventos. Pero significa que puedes tomar código que ya tienes corriendo en el serverless de alguien más y ejecutarlo localmente con \u003ccode\u003efastfn dev\u003c/code\u003e, lo cual es genuinamente útil cuando la consola de AWS de tu empleador está teniendo una de sus semanas.\u003c/p\u003e\n\u003ch3 id=\"un-pool-de-workers-dentro-del-daemon\" class=\"headerLink\"\u003e\n    \u003ca href=\"#un-pool-de-workers-dentro-del-daemon\" class=\"header-mark\" aria-label=\"Header mark for 'Un pool de workers dentro del daemon'\"\u003e\u003c/a\u003e5.1 Un pool de workers dentro del daemon\u003c/h3\u003e\u003cp\u003eEl daemon de Python no solo despacha en el hilo principal. Tiene un \u003ccode\u003eThreadPoolExecutor\u003c/code\u003e delante de un pool de workers de handlers, restringido por un presupuesto de slots. Los controles son todos variables de entorno:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003esrv/fn/runtimes/python-daemon.py:38-40\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_ACQUIRE_TIMEOUT_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_ACQUIRE_TIMEOUT_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;5000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_IDLE_TTL_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_IDLE_TTL_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;300000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eRUNTIME_POOL_REAPER_INTERVAL_MS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_PY_POOL_REAPER_INTERVAL_MS\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;2000\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEntonces: timeout de adquisición de 5 segundos, TTL de inactividad de 5 minutos, el reaper corre cada 2 segundos. Son números de pool FastCGI vestidos con ropa de Python. El hilo reaper recorre el pool, mata workers inactivos que superaron su TTL, y reduce el footprint. El timeout de adquisición es la señal de \u0026ldquo;estoy al límite, retrocede.\u0026rdquo;\u003c/p\u003e\n\u003ch2 id=\"capítulo-6-lua-hasta-el-fondo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-6-lua-hasta-el-fondo\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 6: Lua Hasta el Fondo'\"\u003e\u003c/a\u003e6 Capítulo 6: Lua Hasta el Fondo\u003c/h2\u003e\u003cp\u003eEl Capítulo 3 fue el momento en que le metí Lua a mi vida. Lo que no aprecié entonces es qué tan rápido colonizaría cada rincón del plano de control. Me propuse resolver un problema en Lua —\u0026ldquo;déjame parsear una cookie\u0026rdquo;— y al final de la tarde la tabla de rutas, el generador de OpenAPI, el rate limiter, y el dashboard de administración también eran Lua, corriendo en el mismo worker de nginx, compartiendo las mismas zonas de \u003ccode\u003engx.shared.DICT\u003c/code\u003e, sin ningún salto entre procesos.\u003c/p\u003e\n\u003cp\u003eEste es el capítulo donde listo los lugares donde corre Lua. Lua no es un arma secreta. Es un lenguaje pequeño con un JIT rápido, embebido en un servidor web que ya hace las partes difíciles, y resulta que encaja en este problema como una llave en una cerradura.\u003c/p\u003e\n\u003ch3 id=\"routing-y-descubrimiento-el-árbol-de-archivos-es-la-base-de-datos\" class=\"headerLink\"\u003e\n    \u003ca href=\"#routing-y-descubrimiento-el-%c3%a1rbol-de-archivos-es-la-base-de-datos\" class=\"header-mark\" aria-label=\"Header mark for 'Routing y descubrimiento: el árbol de archivos \u0026lt;em\u0026gt;es\u0026lt;/em\u0026gt; la base de datos'\"\u003e\u003c/a\u003e6.1 Routing y descubrimiento: el árbol de archivos \u003cem\u003ees\u003c/em\u003e la base de datos\u003c/h3\u003e\u003cp\u003eEl primer lugar donde ganó Lua fue el routing. Las rutas no viven en un archivo YAML ni en un objeto de configuración; viven en el sistema de archivos. \u003ccode\u003ecore/routes.lua\u003c/code\u003e recorre el directorio de funciones al arrancar, lee metadata de cada archivo de handler, y construye un catálogo de \u0026ldquo;\u003ccode\u003eGET /hello\u003c/code\u003e → Python → \u003ccode\u003eget.hello.py\u003c/code\u003e.\u0026rdquo; Luego cachea el catálogo en \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e para que todos los workers tengan la misma vista de forma gratuita. El punto de entrada del descubrimiento es simplemente esto:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/routes.lua:2228-2237\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ediscover_functions\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforce\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eforce\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eraw\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eCACHE\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;catalog:raw\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eraw\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecjson.decode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eraw\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eparsed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- ... walk functions_root, populate catalog.runtimes / mapped_routes ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003ecore/routes.lua\u003c/code\u003e es el grande —aproximadamente 3,245 líneas— porque el routing es donde eventualmente se encuentran todas las preocupaciones: métodos, prefijos reservados, conflictos, rangos de fuente. Junto a él vive \u003ccode\u003ecore/fs.lua\u003c/code\u003e (aproximadamente 392 líneas), un pequeño wrapper FFI alrededor de \u003ccode\u003estat\u003c/code\u003e, \u003ccode\u003eopendir\u003c/code\u003e, \u003ccode\u003ereaddir\u003c/code\u003e, y similares. Existe porque el FFI de LuaJIT me permite saltarme el módulo C de LuaFileSystem y llamar directamente a libc, manteniendo la imagen de OpenResty ligera:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/fs.lua:263-280\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003elist_dirs_recursive\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eM.is_dir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;function\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eskip_fn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"o\"\u003e#\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edir\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003e_\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003echild\u003c/span\u003e \u003cspan class=\"kr\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eipairs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eM.list_dirs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edir\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"kr\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003echild\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ewalk\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003etable.sort\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse es el recorrido del sistema de archivos. \u003ccode\u003ereaddir\u003c/code\u003e en Lua-land, con una \u003ccode\u003eskip_fn\u003c/code\u003e enchufable para podar \u003ccode\u003e.git\u003c/code\u003e, \u003ccode\u003enode_modules\u003c/code\u003e, \u003ccode\u003e__pycache__\u003c/code\u003e, y \u003ccode\u003e.fastfn\u003c/code\u003e. Devuelve una lista ordenada de directorios que tanto \u003ccode\u003eroutes.lua\u003c/code\u003e como \u003ccode\u003ewatchdog.lua\u003c/code\u003e usan. Lo cual me hizo pensar: si las rutas son un árbol en disco, entonces la especificación OpenAPI es una proyección de ese mismo árbol, y el dashboard es solo una UI sobre él.\u003c/p\u003e\n\u003ch3 id=\"openapi-gratis\" class=\"headerLink\"\u003e\n    \u003ca href=\"#openapi-gratis\" class=\"header-mark\" aria-label=\"Header mark for 'OpenAPI, gratis'\"\u003e\u003c/a\u003e6.2 OpenAPI, gratis\u003c/h3\u003e\u003cp\u003eEsa idea de \u0026ldquo;proyección del mismo árbol\u0026rdquo; se convirtió en \u003ccode\u003ecore/openapi.lua\u003c/code\u003e, un módulo de 1,309 líneas que toma el catálogo producido por \u003ccode\u003eroutes.lua\u003c/code\u003e y emite un documento OpenAPI 3. No hay decoradores en las funciones de handler. No hay \u003ccode\u003e@app.route(\u0026quot;/hello\u0026quot;)\u003c/code\u003e. El handler es simplemente \u003ccode\u003edef handler(event): return {...}\u003c/code\u003e, y la especificación se genera a partir del árbol de rutas más una capa delgada de metadata por función (\u003ccode\u003efn.config.json\u003c/code\u003e, si el handler se molestó en escribir uno).\u003c/p\u003e\n\u003cp\u003eEl lado HTTP de esto es un archivo Lua de 101 líneas que lo conecta todo:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/http/openapi_endpoint.lua:92-101\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003espec\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eopenapi.build\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecatalog\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eserver_url\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eserver_url\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eruntime_order\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eroutes_mod.get_runtime_order\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einclude_internal\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eenv_bool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;FN_OPENAPI_INCLUDE_INTERNAL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einvoke_meta_lookup\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003einvoke_meta_lookup\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.status\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e200\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.header\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003engx.say\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecjson.encode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003espec\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse es el endpoint \u003ccode\u003e/openapi.json\u003c/code\u003e completo. Recarga tu handler, accede a la URL, la especificación lo refleja. Swagger UI (\u003ccode\u003ehttp/swagger_ui.lua\u003c/code\u003e, 119 líneas) sirve una página estática que apunta a ese JSON y tienes documentación interactiva sin correr un servidor de docs separado. El generador de OpenAPI también se apoya en \u003ccode\u003ecore/invoke_rules.lua\u003c/code\u003e para normalizar la política de invocación por función —qué métodos están permitidos, qué rutas están reservadas:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/invoke_rules.lua:4-14\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eM.ALLOWED_METHODS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eGET\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePOST\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePUT\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ePATCH\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eDELETE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eM.RESERVED_ROUTE_PREFIXES\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;/_fn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;/console\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLo cual me hizo darme cuenta de cuánto del tooling moderno de APIs es coincidencia de strings y listas de permitidos. La \u0026ldquo;especificación\u0026rdquo; no es una especificación, es una vista.\u003c/p\u003e\n\u003ch3 id=\"observabilidad-y-límites-rate-concurrencia-salud\" class=\"headerLink\"\u003e\n    \u003ca href=\"#observabilidad-y-l%c3%admites-rate-concurrencia-salud\" class=\"header-mark\" aria-label=\"Header mark for 'Observabilidad y límites: rate, concurrencia, salud'\"\u003e\u003c/a\u003e6.3 Observabilidad y límites: rate, concurrencia, salud\u003c/h3\u003e\u003cp\u003eEl truco de memoria compartida que hace barato el routing es el mismo truco que hace baratos los límites de concurrencia por función. \u003ccode\u003ecore/limits.lua\u003c/code\u003e tiene 133 líneas, la mayoría boilerplate, y todo el mecanismo es una danza de \u003ccode\u003eincr\u003c/code\u003e/\u003ccode\u003edecr\u003c/code\u003e en una zona \u003ccode\u003engx.shared.fn_conc\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/limits.lua:22-39\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003efunction\u003c/span\u003e \u003cspan class=\"nc\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003etry_acquire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003efn_key\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ekey\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekey_for\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efn_key\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eincr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;counter_error:\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ecurrent\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003elimit\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eincr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;busy\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsa función es el primitivo completo de \u0026ldquo;no más de N invocaciones en vuelo de la función X en todo el gateway.\u0026rdquo; \u003ccode\u003edict:incr\u003c/code\u003e es atómico entre workers porque \u003ccode\u003engx.shared.DICT\u003c/code\u003e es memoria compartida con un lock por debajo. No hay round trip a Redis. No hay pod de admission-controller. Es un contador en RAM, y es correcto porque nginx me lo da correcto.\u003c/p\u003e\n\u003cp\u003eSobre los límites vive \u003ccode\u003ecore/watchdog.lua\u003c/code\u003e (299 líneas), un watcher Linux \u003ccode\u003einotify\u003c/code\u003e escrito en LuaJIT FFI. Abre \u003ccode\u003einotify_init1(IN_NONBLOCK | IN_CLOEXEC)\u003c/code\u003e, recorre el árbol de funciones, añade un watch en cada subdirectorio, y programa una recarga debounced cuando cambia un archivo:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/watchdog.lua:268-284\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok_poll\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epoll_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.timer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eevery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epoll_interval\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epremature\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epremature\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003echanged\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eread_err\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edrain_events\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e-- ...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.now\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003edebounce_s\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epending_since\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003enil\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eschedule_reload\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eUn debounce de 150 ms, un callback de recarga compartido, y la tabla de rutas se reconstruye en su lugar. Guarda un archivo, la ruta está activa antes de que yo cambie al navegador.\u003c/p\u003e\n\u003ch3 id=\"scheduling-en-proceso-ngxtimerat-como-cron\" class=\"headerLink\"\u003e\n    \u003ca href=\"#scheduling-en-proceso-ngxtimerat-como-cron\" class=\"header-mark\" aria-label=\"Header mark for 'Scheduling en proceso: \u0026lt;code\u0026gt;ngx.timer.at\u0026lt;/code\u0026gt; como cron'\"\u003e\u003c/a\u003e6.4 Scheduling en proceso: \u003ccode\u003engx.timer.at\u003c/code\u003e como cron\u003c/h3\u003e\u003cp\u003eSi puedo programar una recarga con \u003ccode\u003engx.timer\u003c/code\u003e, puedo programar cualquier cosa. \u003ccode\u003ecore/scheduler.lua\u003c/code\u003e (1,712 líneas) y \u003ccode\u003ecore/jobs.lua\u003c/code\u003e (993 líneas) juntos implementan un runner de jobs estilo cron, con reintentos y persistencia opcional, que vive \u003cem\u003edentro\u003c/em\u003e del worker de nginx. Sin daemon \u003ccode\u003ecron\u003c/code\u003e separado, sin Celery, sin RabbitMQ:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/core/scheduler.lua:1659-1667\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.timer\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eevery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003einterval\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epremature\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003epremature\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eok2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eerr2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epcall\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etick_once\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003eok2\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003engx.log\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003engx.ERR\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;scheduler tick failed: \u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etostring\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eerr2\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse único \u003ccode\u003engx.timer.every\u003c/code\u003e es el tick loop. En cada tick recorre los schedules activos, verifica las expresiones cron contra el reloj de pared, y encola runs via \u003ccode\u003engx.timer.at(0, ...)\u003c/code\u003e. Los reintentos con backoff exponencial salen del mismo primitivo. La persistencia es un blob JSON escrito en disco cada 15 segundos. Es mucho código para una idea pequeña: \u003cem\u003eel event loop que ya tengo es suficiente scheduler para un FaaS de un solo nodo.\u003c/em\u003e\u003c/p\u003e\n\u003ch4 id=\"un-ejemplo-concreto-un-digest-de-telegram-con-ia-que-corre-cada-hora\" class=\"headerLink\"\u003e\n    \u003ca href=\"#un-ejemplo-concreto-un-digest-de-telegram-con-ia-que-corre-cada-hora\" class=\"header-mark\" aria-label=\"Header mark for 'Un ejemplo concreto: un digest de Telegram con IA que corre cada hora'\"\u003e\u003c/a\u003e6.4.1 Un ejemplo concreto: un digest de Telegram con IA que corre cada hora\u003c/h4\u003e\u003cp\u003eEl scheduler es abstracto hasta que ves una función que lo usa. El más limpio del repo es \u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-ai-digest\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eexamples/functions/node/telegram-ai-digest\u003c/code\u003e\u003c/a\u003e —una función Node que obtiene mensajes de un grupo de Telegram, los resume con OpenAI, y envía el digest de vuelta al chat. Lo que lo hace interesante para esta sección es el bloque \u003ccode\u003eschedule\u003c/code\u003e en su configuración:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/fn.config.json\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;timeout_ms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e30000\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;invoke\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;summary\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Telegram AI digest: fetches group messages, summarizes with OpenAI, sends digest\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;methods\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;content_type\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;application/json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;schedule\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;enabled\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;every_seconds\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e3600\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;method\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsa es la interfaz completa de \u0026ldquo;convierte esto en un cron job\u0026rdquo;: un bloque \u003ccode\u003eschedule\u003c/code\u003e de tres líneas en la propia configuración de la función, junto al handler. Sin crontab externo, sin paso de registro separado, sin pipeline YAML. Cuando fastfn arranca, el scheduler lee el \u003ccode\u003efn.config.json\u003c/code\u003e de cada función, ve que este tiene \u003ccode\u003eschedule.enabled = true\u003c/code\u003e, y lo añade al tick loop con \u003ccode\u003eevery_seconds = 3600\u003c/code\u003e. Cada hora, el scheduler emite una petición \u003ccode\u003eGET\u003c/code\u003e interna a la función como si un cliente la hubiera llamado —mismo routing, mismo handler, mismo logging— y la función hace su trabajo.\u003c/p\u003e\n\u003cp\u003eEl mismo patrón aparece en los ejemplos hermanos: \u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-ai-reply\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etelegram-ai-reply\u003c/code\u003e\u003c/a\u003e es un webhook (handler \u003ccode\u003ePOST\u003c/code\u003e, sin schedule —Telegram lo llama cuando llega un mensaje), \u003ca href=\"https://github.com/misaelzapata/fastfn/tree/main/examples/functions/node/telegram-send\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etelegram-send\u003c/code\u003e\u003c/a\u003e es una función estilo librería que puedes invocar desde otros handlers para enviar un mensaje (\u003ccode\u003edry_run\u003c/code\u003e por defecto, que es el tipo de detalle de seguridad que aprendí a añadir después del segundo bot que se suponía debía estar en silencio). Tres funciones, tres ciclos de vida —webhook, llamada de librería, cron— y todas son simplemente archivos en \u003ccode\u003efunctions/\u003c/code\u003e. Lo único que hace que el digest sea \u0026ldquo;un cron\u0026rdquo; son esas tres líneas de configuración.\u003c/p\u003e\n\u003cp\u003eMe gusta esta forma porque el \u0026ldquo;modelo de despliegue\u0026rdquo; de la función y su \u0026ldquo;modelo de invocación\u0026rdquo; están en el mismo lugar. Si un compañero se pregunta \u0026ldquo;¿cómo corre esto?\u0026rdquo;, abre \u003ccode\u003efn.config.json\u003c/code\u003e, ve \u003ccode\u003eschedule.every_seconds = 3600\u003c/code\u003e, lo sabe. Sin buscar en un archivo cron en un servidor al que no tiene acceso SSH.\u003c/p\u003e\n\u003ch4 id=\"configuración-y-tokens-fnenvjson-y-el-flag-is_secret\" class=\"headerLink\"\u003e\n    \u003ca href=\"#configuraci%c3%b3n-y-tokens-fnenvjson-y-el-flag-is_secret\" class=\"header-mark\" aria-label=\"Header mark for 'Configuración y tokens: \u0026lt;code\u0026gt;fn.env.json\u0026lt;/code\u0026gt; y el flag \u0026lt;code\u0026gt;is_secret\u0026lt;/code\u0026gt;'\"\u003e\u003c/a\u003e6.4.2 Configuración y tokens: \u003ccode\u003efn.env.json\u003c/code\u003e y el flag \u003ccode\u003eis_secret\u003c/code\u003e\u003c/h4\u003e\u003cp\u003eLa función del digest programado es inútil sin dos secretos y un identificador: un token de bot de Telegram, una API key de OpenAI, y el ID del chat al que publicar. Este es el punto en cada tutorial de FaaS donde o bien se hacen gestos vagos sobre variables de entorno o se pega un párrafo sobre Vault. fastfn no hace ninguna de las dos. Pone la configuración junto a la función, en un archivo hermano llamado \u003ccode\u003efn.env.json\u003c/code\u003e, con un poco de estructura que hace explícito el \u003cem\u003esignificado\u003c/em\u003e de cada valor:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/fn.env.json\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;TELEGRAM_BOT_TOKEN\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;TELEGRAM_CHAT_ID\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;OPENAI_API_KEY\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026#34;is_secret\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos cosas a notar. Primero, cada clave es un objeto, no un string suelto —el valor vive en \u003ccode\u003e.value\u003c/code\u003e, y los metadatos viven junto a él. Segundo, ese booleano \u003ccode\u003eis_secret\u003c/code\u003e es significativo. El runtime lo usa para decidir si un valor se enmascara en logs y en el dashboard de administración, si puede ser devuelto por un endpoint \u003ccode\u003e_fn/ui_state\u003c/code\u003e, y si el botón \u0026ldquo;ver\u0026rdquo; del dashboard puede revelarlo en texto claro. \u003ccode\u003eTELEGRAM_CHAT_ID\u003c/code\u003e no es un secreto —es solo un número, y querrás verlo en la UI mientras depuras \u0026ldquo;¿por qué no apareció mi digest?\u0026rdquo;. \u003ccode\u003eTELEGRAM_BOT_TOKEN\u003c/code\u003e sí lo es —y si accidentalmente lo filtras en logs, la reacción de Telegram es invalidar el token, así que el scheduler dejaría de funcionar silenciosamente hasta que te des cuenta. El flag \u003ccode\u003eis_secret\u003c/code\u003e es toda la diferencia entre esos dos resultados.\u003c/p\u003e\n\u003cp\u003eEl valor \u003ccode\u003e\u0026quot;\u0026lt;set-me\u0026gt;\u0026quot;\u003c/code\u003e tampoco es un bug —es un patrón que el repo usa deliberadamente. El handler en \u003ccode\u003ecore.js\u003c/code\u003e trata \u003ccode\u003e\u0026lt;set-me\u0026gt;\u003c/code\u003e, \u003ccode\u003eset-me\u003c/code\u003e, \u003ccode\u003echangeme\u003c/code\u003e, \u003ccode\u003e\u0026lt;changeme\u0026gt;\u003c/code\u003e, y \u003ccode\u003ereplace-me\u003c/code\u003e como centinelas de \u0026ldquo;no configurado\u0026rdquo;:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/core.js:1-8\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunction\u003c/span\u003e \u003cspan class=\"nx\"\u003eisUnsetConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003eundefined\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003evalue\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003etrim\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etoLowerCase\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;set-me\u0026gt;\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;set-me\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;changeme\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;changeme\u0026gt;\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e||\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;replace-me\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEso significa que puedes hacer commit de \u003ccode\u003efn.env.json\u003c/code\u003e en git con placeholders \u003ccode\u003e\u0026quot;\u0026lt;set-me\u0026gt;\u0026quot;\u003c/code\u003e, y la función falla limpio en vez de pretender correr con un string vacío. Los despliegues reales reemplazan esos placeholders (en el dashboard, a través de la API, o editando el archivo en un host controlado) con valores reales; las entradas \u003ccode\u003eis_secret: true\u003c/code\u003e se enmascaran inmediatamente al guardar.\u003c/p\u003e\n\u003cp\u003eDentro del handler, el runtime entrega estos valores en \u003ccode\u003eevent.env\u003c/code\u003e, junto al \u003ccode\u003eevent.method\u003c/code\u003e, \u003ccode\u003eevent.query\u003c/code\u003e, etc. con scope de petición. El \u003ccode\u003ecore.js\u003c/code\u003e del digest los lee con un pequeño fallback que prefiere el env local de la función, cayendo de vuelta al \u003ccode\u003eprocess.env\u003c/code\u003e del ambiente que el daemon puso en whitelist al arrancar:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eexamples/functions/node/telegram-ai-digest/core.js:86-88\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-21\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003ebotToken\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_BOT_TOKEN\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_BOT_TOKEN\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003echatId\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_CHAT_ID\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e   \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eTELEGRAM_CHAT_ID\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eapiKey\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003echooseConfigValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eOPENAI_API_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e     \u003cspan class=\"nx\"\u003eprocess\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eOPENAI_API_KEY\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsta es la forma de configuración a la que sigo volviendo: un archivo JSON junto al handler, un valor por secreto, un flag explícito \u003ccode\u003eis_secret\u003c/code\u003e que impulsa el enmascaramiento en cada superficie posterior, y un \u003ccode\u003eevent.env\u003c/code\u003e a nivel de lenguaje que hace que la dependencia del handler en esos valores sea trivialmente buscable con grep. No es una idea nueva —es básicamente la misma forma que las config vars de Heroku o un par \u003ccode\u003eSecret\u003c/code\u003e/\u003ccode\u003eConfigMap\u003c/code\u003e de Kubernetes. Pero vive en la misma carpeta que el código que lo usa, es versionable con placeholders seguros, y no requiere un gestor de secretos externo para empezar.\u003c/p\u003e\n\u003cp\u003ePon el scheduler, la configuración, y el handler en un directorio, y el digest de Telegram pasa de \u0026ldquo;un vago cron job en algún lugar\u0026rdquo; a \u0026ldquo;cuatro archivos que puedo leer en un minuto y pasarle a un compañero.\u0026rdquo;\u003c/p\u003e\n\u003ch3 id=\"el-dashboard-es-lua\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-dashboard-es-lua\" class=\"header-mark\" aria-label=\"Header mark for 'El dashboard es Lua'\"\u003e\u003c/a\u003e6.5 El dashboard es Lua\u003c/h3\u003e\u003cp\u003eEl lugar donde Lua más me sorprendió fue la consola de administración. No me propuse escribir una app web en Lua. Asumí que eventualmente conectaría un SPA pequeño, probablemente Vue o Svelte, porque \u0026ldquo;todo el mundo hace eso.\u0026rdquo; Entonces escribí \u003ccode\u003econsole/login_endpoint.lua\u003c/code\u003e en una tarde y me di cuenta de que no necesitaba el SPA.\u003c/p\u003e\n\u003cp\u003eEl endpoint de login tiene 95 líneas y hace todo lo que un endpoint de login debe hacer: verificación de método, rate limit en \u003ccode\u003engx.shared.fn_cache\u003c/code\u003e, comparación en tiempo constante, verificación de contraseña PBKDF2, cookie de sesión. Aquí el fragmento del rate limit:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/console/login_endpoint.lua:38-46\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-22\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003eclient_ip\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.var\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eremote_addr\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;unknown\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.shared\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efn_cache\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003efail_count\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erate_store\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogin_rate_key\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eclient_ip\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eLOGIN_MAX_ATTEMPTS\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efail_count\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;number\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e     \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003efail_count\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e \u003cspan class=\"n\"\u003eLOGIN_MAX_ATTEMPTS\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e429\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;too many login attempts, try again later\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCinco segundos de aritmética con shared dict y tengo un bloqueo funcional. \u003ccode\u003econsole/auth.lua\u003c/code\u003e (484 líneas) maneja la cookie de sesión, PBKDF2 con un mínimo de 100,000 iteraciones, secretos opcionales desde archivo para la contraseña de admin, y un TTL de 12 horas. \u003ccode\u003econsole/guard.lua\u003c/code\u003e (387 líneas) es el middleware que llama cada endpoint del dashboard primero para imponer auth, límites de body, CSRF, y gates de escritura. \u003ccode\u003econsole/data.lua\u003c/code\u003e es el grande —aproximadamente 2,623 líneas— y es la \u0026ldquo;capa de servicio\u0026rdquo; de respaldo a la que delegan los endpoints del dashboard: listar funciones, leer el código de una función, escribir código, listar versiones, leer logs, leer schedules, agregar métricas del dashboard.\u003c/p\u003e\n\u003cp\u003eLos endpoints en sí son diminutos. El endpoint de métricas del dashboard tiene 21 líneas:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003eopenresty/lua/fastfn/console/dashboard_endpoint.lua:9-21\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-23\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003engx.req\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_method\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003emethod\u003c/span\u003e \u003cspan class=\"o\"\u003e~=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;GET\u0026#34;\u003c/span\u003e \u003cspan class=\"kr\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e405\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eerror\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;method not allowed\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- Aggregate metrics from shared dicts or external systems\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003emetrics\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003econsole.get_dashboard_metrics\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eguard.write_json\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003emetrics\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003einvocations\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eerrors\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003elatency\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eAlrededor de eso viven \u003ccode\u003efunctions_endpoint.lua\u003c/code\u003e (34 líneas), \u003ccode\u003elogout_endpoint.lua\u003c/code\u003e (15 líneas), \u003ccode\u003eui_state_endpoint.lua\u003c/code\u003e (50 líneas), \u003ccode\u003esecrets_endpoint.lua\u003c/code\u003e (70 líneas), \u003ccode\u003epacks.lua\u003c/code\u003e (77 líneas), \u003ccode\u003eui.lua\u003c/code\u003e (66 líneas), y el más grande \u003ccode\u003einvoke_endpoint.lua\u003c/code\u003e (526 líneas) que impulsa la consola de \u0026ldquo;invoca esta función desde el navegador.\u0026rdquo; No hay una app web de administración separada. No hay \u003ccode\u003enpm run dev\u003c/code\u003e. No hay un segundo sistema de build. Hay Lua, renderizando HTML y JSON desde el mismo worker de nginx que sirve el tráfico del gateway.\u003c/p\u003e\n\u003ch3 id=\"lua-como-runtime-y-un-cliente-http-muy-pequeño\" class=\"headerLink\"\u003e\n    \u003ca href=\"#lua-como-runtime-y-un-cliente-http-muy-peque%c3%b1o\" class=\"header-mark\" aria-label=\"Header mark for 'Lua como runtime, y un cliente HTTP muy pequeño'\"\u003e\u003c/a\u003e6.6 Lua como runtime, y un cliente HTTP muy pequeño\u003c/h3\u003e\u003cp\u003eTambién añadí \u003ccode\u003ecore/lua_runtime.lua\u003c/code\u003e (399 líneas), que deja que archivos Lua en el directorio de funciones \u003cem\u003esean\u003c/em\u003e handlers —mismo contrato \u003ccode\u003ehandler(event) -\u0026gt; response\u003c/code\u003e que Python y Node, excepto que no hay salto entre procesos porque el handler corre en el mismo worker. No he publicado un benchmark para este camino, así que no voy a poner un número en milisegundos. Para handlers que necesitan llamar hacia afuera, \u003ccode\u003ecore/http_client.lua\u003c/code\u003e (356 líneas) envuelve \u003ccode\u003engx.socket.tcp\u003c/code\u003e con parseo de URLs, keepalive, y timeouts, para que los handlers llamen a APIs upstream sin meter \u003ccode\u003eluasocket\u003c/code\u003e en la imagen. \u003ccode\u003ecore/home.lua\u003c/code\u003e (234 líneas) sirve la página de aterrizaje por defecto en \u003ccode\u003e/\u003c/code\u003e, y \u003ccode\u003ehttp/function_code.lua\u003c/code\u003e / \u003ccode\u003ehttp/function_file_content.lua\u003c/code\u003e (50 y 76 líneas) hacen round-trip del código fuente de las funciones entre el editor del dashboard y el disco.\u003c/p\u003e\n\u003ch3 id=\"los-compromisos-honestos\" class=\"headerLink\"\u003e\n    \u003ca href=\"#los-compromisos-honestos\" class=\"header-mark\" aria-label=\"Header mark for 'Los compromisos honestos'\"\u003e\u003c/a\u003e6.7 Los compromisos honestos\u003c/h3\u003e\u003cp\u003eEntonces — ¿es Lua magnífico aquí? Sí, con comprobantes. Los comprobantes son:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eEl cold start es esencialmente gratuito.\u003c/strong\u003e OpenResty arranca una vez. Mi código Lua es un conjunto de módulos cargados en la inicialización del worker. El archivo \u003ccode\u003egateway.lua\u003c/code\u003e tiene 1,341 líneas; cargarlo no añade prácticamente nada a la latencia de petición porque los hooks de fase de petición ya están compilados por JIT cuando llega la primera petición. No tengo un número limpio de \u0026ldquo;tiempo de carga del módulo Lua\u0026rdquo; que citar aquí, así que no voy a poner un número en milisegundos —pero cada p50 publicado del camino caliente en el repo, en todos los workloads, está en los milisegundos de un solo dígito end-to-end a través de este gateway de Lua, y ese es el único número que importa a un usuario.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLa memoria compartida es casi gratuita.\u003c/strong\u003e \u003ccode\u003engx.shared.DICT\u003c/code\u003e es una tabla hash en nginx, protegida por un spinlock. No pago por un contenedor Redis para mantener contadores.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eUn módulo por responsabilidad, y cada uno es pequeño.\u003c/strong\u003e Dejando de lado el core de descubrimiento de routing, la mayoría de los módulos del plano de control están en el rango de 100–500 líneas. \u003ccode\u003elimits.lua\u003c/code\u003e tiene 133 líneas. \u003ccode\u003ewatchdog.lua\u003c/code\u003e tiene 299. \u003ccode\u003elogin_endpoint.lua\u003c/code\u003e tiene 95. Son números a distancia de lectura. Puedo mantener cualquiera de ellos en mi cabeza.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eY los costos, porque los hay:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePeculiaridades numéricas de Lua 5.1.\u003c/strong\u003e El LuaJIT de OpenResty es Lua-5.1-ish con algunas extensiones 5.2/5.3. No hay \u003ccode\u003estring.pack\u003c/code\u003e nativo, que es por qué existe el \u003ccode\u003epack_u32\u003c/code\u003e del Capítulo 4. La confusión entero-vs-double me mordió dos veces mientras escribía el rate limiter; \u003ccode\u003engx.shared.DICT:incr\u003c/code\u003e devuelve números que son doubles, y tuve que ser cuidadoso con \u003ccode\u003emath.floor\u003c/code\u003e en contadores que planeaba comparar contra valores \u003ccode\u003etonumber(env)\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDisciplina de \u003ccode\u003epcall\u003c/code\u003e en todas partes.\u003c/strong\u003e Un error no capturado en un callback \u003ccode\u003engx.timer\u003c/code\u003e mata el timer silenciosamente y loggea en \u003ccode\u003eerror.log\u003c/code\u003e. Todo timer que escribo está envuelto en \u003ccode\u003epcall\u003c/code\u003e, y el tick del scheduler, el callback del watchdog, y el escritor de persistencia siguen el mismo patrón. Sáltate esa disciplina y obtienes un scheduler que misteriosamente deja de tickear a las 3 AM.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTodo es un string, hasta que no lo es.\u003c/strong\u003e Los shared dicts almacenan strings y números, no tablas, lo que significa encode/decode JSON en el límite. Me apoyo en \u003ccode\u003ecjson.safe\u003c/code\u003e para que la entrada mala devuelva \u003ccode\u003enil, err\u003c/code\u003e en vez de lanzar, y aún así obtengo el ocasional \u003ccode\u003ecannot encode sparse array\u003c/code\u003e cuando una tabla tiene huecos numéricos.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eNinguno de estos es un bloqueante. Son el precio de elegir un lenguaje pequeño con un JIT rápido embebido en un servidor web, y ese trato sigue ganando. Todo el plano de control —routing, OpenAPI, límites, watchdog, scheduler, consola, auth— es un puñado de archivos que puedo leer en una tarde. La mayoría de las preocupaciones son de 20 a 40 KB de Lua legible cada una. Todo el directorio \u003ccode\u003ecore/\u003c/code\u003e ocupa aproximadamente 304 KB en disco; todo el directorio \u003ccode\u003econsole/\u003c/code\u003e es aproximadamente 156 KB. Eso es menos Lua, por amplio margen, que la cantidad de JavaScript que un SPA moderno envía antes de llegar a la primera ruta. Y el Lua es \u003cem\u003eel producto\u003c/em\u003e, no el andamiaje.\u003c/p\u003e\n\u003cp\u003eLo cual me lleva a la parte donde intento extraer las lecciones de este montón.\u003c/p\u003e\n\u003ch2 id=\"capítulo-7-lecciones-que-solo-esperaba-a-medias\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-7-lecciones-que-solo-esperaba-a-medias\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 7: Lecciones Que Solo Esperaba a Medias'\"\u003e\u003c/a\u003e7 Capítulo 7: Lecciones Que Solo Esperaba a Medias\u003c/h2\u003e\u003ch3 id=\"haz-whitelist-al-env-del-host-siempre\" class=\"headerLink\"\u003e\n    \u003ca href=\"#haz-whitelist-al-env-del-host-siempre\" class=\"header-mark\" aria-label=\"Header mark for 'Haz whitelist al env del host, siempre'\"\u003e\u003c/a\u003e7.1 Haz whitelist al env del host, siempre\u003c/h3\u003e\u003cp\u003eTodo FaaS tiene la misma tentación: pasar las variables de entorno del host al handler. Es conveniente. También es como se filtran los secretos. Si tu pipeline CI/CD exporta \u003ccode\u003eAWS_SECRET_ACCESS_KEY\u003c/code\u003e para scripts de despliegue, y tu daemon Python hereda ese env en cada handler, enhorabuena, le has dado a cada handler en la máquina una credencial raíz de la nube.\u003c/p\u003e\n\u003cp\u003eEl arreglo es una whitelist, no una blocklist. Descubrí esto de la manera aburrida: leyendo reportes de incidentes de otras personas y decidiendo que no quería escribir el mío propio. El daemon viene con una lista conservadora de claves de env del ambiente que los handlers pueden ver (\u003ccode\u003esrv/fn/runtimes/python-daemon.py:59-103\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-24\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_ALLOWED_WORKER_ENV_KEYS\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;USER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;LOGNAME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... language runtimes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PYTHONHOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;PYTHONPATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;GOPATH\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;GOCACHE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;CARGO_HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;RUSTUP_HOME\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... CA bundles\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;SSL_CERT_FILE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;SSL_CERT_DIR\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;REQUESTS_CA_BUNDLE\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# ... locale\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;LANG\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;TZ\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e_ALLOWED_WORKER_ENV_PREFIXES\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;LC_\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl comentario encima de esto dice en voz alta lo que se suele callar: \u003cem\u003e\u0026ldquo;Los secretos definidos por el usuario deben venir de fn.env.json o del event.env con scope de petición, no del env del host ambiente.\u0026rdquo;\u003c/em\u003e La whitelist es la aplicación. Todo lo que no esté en ese conjunto, o que comience con \u003ccode\u003eLC_\u003c/code\u003e, se elimina antes de que corra el handler. Es higiene de seguridad, no teatro de seguridad.\u003c/p\u003e\n\u003ch3 id=\"precedencia-de-configuración-flag--env--config--default\" class=\"headerLink\"\u003e\n    \u003ca href=\"#precedencia-de-configuraci%c3%b3n-flag--env--config--default\" class=\"header-mark\" aria-label=\"Header mark for 'Precedencia de configuración: flag \u0026amp;gt; env \u0026amp;gt; config \u0026amp;gt; default'\"\u003e\u003c/a\u003e7.2 Precedencia de configuración: flag \u0026gt; env \u0026gt; config \u0026gt; default\u003c/h3\u003e\u003cp\u003eEsta es la clase de regla que suena obvia y sin embargo es incorrecta en la mitad de los CLIs que he usado. Mi propio CLI lo hace bien porque lo hice mal la primera vez. De \u003ccode\u003ecli/cmd/run.go:64-73\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-25\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// Resolve hot-reload: flag \u0026gt; env \u0026gt; config \u0026gt; default (true)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003erunHotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// Explicit --hot-reload flag always wins\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGetenv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;FN_HOT_RELOAD\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;0\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;false\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;off\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003eenvVal\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;no\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eIsSet\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;hot-reload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ehotReload\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eviper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eGetBool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;hot-reload\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eNota el comentario en la línea 64. No está ahí para mí —está para la próxima persona que intente \u0026ldquo;arreglar\u0026rdquo; esto haciendo que el archivo de configuración gane sobre el env. Todo sistema de configuración que alguna vez ha invertido este orden ha causado un incidente en producción en el que alguien estableció una variable de entorno en CI, y luego se preguntó por qué el contenedor seguía leyendo un valor obsoleto de un archivo commiteado.\u003c/p\u003e\n\u003ch3 id=\"todo-vive-en-el-sistema-de-archivos\" class=\"headerLink\"\u003e\n    \u003ca href=\"#todo-vive-en-el-sistema-de-archivos\" class=\"header-mark\" aria-label=\"Header mark for 'Todo vive en el sistema de archivos'\"\u003e\u003c/a\u003e7.3 Todo vive en el sistema de archivos\u003c/h3\u003e\u003cp\u003eSin base de datos. Sin registro. Sin etcd. Las rutas son archivos. La configuración es \u003ccode\u003efastfn.json\u003c/code\u003e. El env es \u003ccode\u003efn.env.json\u003c/code\u003e. Las sobrescrituras locales de funciones son \u003ccode\u003efn.config.json\u003c/code\u003e. El estado de dependencias es \u003ccode\u003e.fastfn-deps-state.json\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eEsta decisión se tomó por pereza y resultó ser una funcionalidad. Un FaaS filesystem-first puede ser versionado, sincronizado con rsync, snapshotted, y comparado con diff usando herramientas que ya tengo. No necesitas una \u0026ldquo;plataforma\u0026rdquo; para inspeccionarlo. \u003ccode\u003els\u003c/code\u003e es la UI de administración. \u003ccode\u003egrep\u003c/code\u003e es el depurador. Esta es la misma lección que aprendió \u003ccode\u003einetd\u003c/code\u003e en 1986: a veces el plano de control debería ser un archivo de texto.\u003c/p\u003e\n\u003ch2 id=\"capítulo-8-la-moraleja-de-la-historia-parte-1\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cap%c3%adtulo-8-la-moraleja-de-la-historia-parte-1\" class=\"header-mark\" aria-label=\"Header mark for 'Capítulo 8: La Moraleja de la Historia (Parte 1)'\"\u003e\u003c/a\u003e8 Capítulo 8: La Moraleja de la Historia (Parte 1)\u003c/h2\u003e\u003cp\u003eMe propuse construir \u0026ldquo;suelta un archivo Python, obtén una URL.\u0026rdquo; Terminé reconstruyendo FastCGI a propósito, con un gateway de Lua porque OpenResty es genuinamente la herramienta correcta para este trabajo, un protocolo de red JSON porque los formatos de registro binarios son un mal tradeoff para un sistema local-first en 2026, y un contrato de runtime políglotas porque \u003ccode\u003edef handler(event): return {...}\u003c/code\u003e es la abstracción correcta y lo ha sido desde que Lambda fue lanzado.\u003c/p\u003e\n\u003cp\u003eLo que no esperaba es cuántas de las decisiones de diseño fueron hechas de antemano por la historia. El costo de fork-por-petición que mató CGI es el mismo costo que hace que el serverless de contenedor-por-petición se sienta lento hoy. El pool persistente + socket enmarcado de FastCGI es la misma forma a la que convergen internamente las plataformas serverless modernas. Cloudflare Workers son, en su esencia, FastCGI con un aislado v8 en vez de un proceso. Los pools warm-start de AWS Lambda son FastCGI con un API Gateway enfrente. \u003ccode\u003efastfn\u003c/code\u003e —al menos en la forma que acabo de describir— es FastCGI con tramas JSON y un gateway OpenResty.\u003c/p\u003e\n\u003cp\u003eCerraré la Parte 1 con el único arrepentimiento que tengo: podría haber empezado con Lua un año antes. Cada vez que escribí \u0026ldquo;solo un pequeño gateway en Go\u0026rdquo; estaba escribiendo una versión peor de lo que nginx ya hace, con un cold start mayor, más código, y menos observabilidad. La lección —y no es una lección nueva, que es la parte vergonzosa— es que cuando la forma de tu problema coincide con una pieza de infraestructura existente y bien amada, deberías simplemente usar esa infraestructura. FastCGI me dijo cómo debería verse el plano de datos en 1996. OpenResty me dijo cómo debería verse el plano de control en 2010. Lambda me dijo cómo debería verse el contrato del handler en 2014. Todo lo que hizo la Parte 1 de \u003ccode\u003efastfn\u003c/code\u003e fue escuchar.\u003c/p\u003e\n\u003cp\u003eHay una segunda rama de esta historia que este post deliberadamente no cubre: qué pasa cuando una función no es la forma correcta —cuando necesitas un servicio longevo como una base de datos, una app Flask, o un frontend Next.js viviendo dentro del mismo gateway. Ese trabajo está actualmente en curso en una rama de funcionalidad y aún no está publicado, así que lo dejo para la Parte 2, donde el aislamiento de microVM Firecracker, la configuración de \u003ccode\u003eapps\u003c/code\u003e / \u003ccode\u003eworkloads\u003c/code\u003e, y la red de pares vsock reciben el espacio que necesitan. Las funciones son la Parte 1. Los servicios son la Parte 2.\u003c/p\u003e\n\u003cdiv class=\"details admonition tip open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 352 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z\"/\u003e\u003c/svg\u003e\u003c/span\u003eSeguir leyendo\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eParte 2 — \u003ca href=\"/es/fastfn-services-when-functions-arent-enough/\" rel=\"\"\u003e\u003cstrong\u003efastfn Parte 2: Cuando una Función No Es Suficiente\u003c/strong\u003e\u003c/a\u003e — continúa donde este post termina: servicios, workloads, microVMs Firecracker, y la red vsock que deja que una función llegue a un VM de Postgres en \u003ccode\u003epostgres.internal\u003c/code\u003e sin exponer un solo puerto en el host.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n",
        "language": "es"
    },
    {
        "title" : "Enseñándole a un teclado Gigabyte a hablar Linux",
        "date_published" : "2026-05-26T00:00:00Z",
        "date_modified" : "2026-05-26T00:00:00Z",
        "id" : "https://misael.org/es/teaching-a-gigabyte-keyboard-to-speak-linux/",
        "url" : "https://misael.org/es/teaching-a-gigabyte-keyboard-to-speak-linux/",
        "summary": "Un teclado Gigabyte Aero RGB, /dev/hidraw, un checksum que no es XOR, y una regla udev que convierte \u0026lsquo;sudo cada vez\u0026rsquo; en \u0026lsquo;simplemente funciona\u0026rsquo;.",
        "content_html" : "\u003ch2 id=\"el-dolor-de-un-teclado-oscuro\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-dolor-de-un-teclado-oscuro\" class=\"header-mark\" aria-label=\"Header mark for 'El dolor de un teclado oscuro'\"\u003e\u003c/a\u003e1 El dolor de un teclado oscuro\u003c/h2\u003e\u003cp\u003eCompré una Gigabyte Aero X16 con la intención de hacerla mi \u0026ldquo;máquina de uso diario que también corre builds sin quejarse\u0026rdquo;. En Windows, lo primero que pasó cuando la encendí fue teatral: el teclado se iluminó en una ola de cian, como una pequeña banda de LEDs marchando, y el Gigabyte Control Center me insistía alegremente en que eligiera entre una docena de efectos RGB. Se sentía como un teclado que quería hablarme.\u003c/p\u003e\n\u003cp\u003eLuego instalé Linux, y el teclado se calló. Sin un destello. Sin un brillo. Nada.\u003c/p\u003e\n\u003cp\u003ePara ser justo, la máquina seguía escribiendo. Las teclas seguían produciendo caracteres. Pero hay un tipo particular de dolor peculiar de los desarrolladores que pagaron por RGB y luego pasaron una noche mirando plástico apagado. Ese dolor me sugirió una idea: si el hardware sigue funcionando y el OS es lo único que cambió, entonces en algún lugar entre mi sesión de usuario y el firmware del teclado hay una conversación que simplemente no está ocurriendo — y las conversaciones que no ocurren pueden, en principio, iniciarse. Miré herramientas existentes: hay un maravilloso proyecto de Windows llamado \u003ca href=\"https://gitlab.com/wtwrp/aeroctl\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ewtwrp/aeroctl\u003c/code\u003e\u003c/a\u003e que hace todo lo que uno podría querer, si uno está dispuesto a también correr Windows. Yo no lo estaba. Así que hice lo que uno hace: abrí una terminal, murmuré algo no imprimible sobre \u003ccode\u003elsusb\u003c/code\u003e, y empecé a explorar.\u003c/p\u003e\n\u003cp\u003eUnas noches después, tenía un pequeño proyecto Python que llamé \u003ca href=\"https://github.com/misaelzapata/aeroctl-linux-indicator\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eaeroctl-linux-indicator\u003c/code\u003e\u003c/a\u003e. Son 593 líneas de fuente a lo largo del paquete (\u003ccode\u003edevice.py\u003c/code\u003e + \u003ccode\u003ecli.py\u003c/code\u003e + \u003ccode\u003etray.py\u003c/code\u003e), tiene alrededor de 80% de cobertura por tests, incluye un CLI y un tray indicator, y su único propósito es convencer a un controlador ITE-829X enterrado dentro de un chassis Gigabyte de que Linux es, de hecho, un sistema operativo legítimo que merece luz de colores.\u003c/p\u003e\n\u003cp\u003eEsta es la historia de cómo llegué ahí. Es también la historia de cómo un commit — \u003ccode\u003e68dbdae\u003c/code\u003e, \u0026ldquo;chore: remove unsupported RGB effects\u0026rdquo; — es la línea más triste del repo.\u003c/p\u003e\n\u003cp\u003eEse commit, sin embargo, es el final del hilo. El principio es una pregunta más inocente — una que solo hice \u003cem\u003edespués\u003c/em\u003e de un par de noches de decepción con las respuestas de otros: ¿qué está realmente conectado al bus USB con lo que aún no he hablado, y cómo lo sabría siquiera?\u003c/p\u003e\n\u003ch2 id=\"el-arte-previo-cuatro-proyectos-ninguno-para-esta-laptop\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-arte-previo-cuatro-proyectos-ninguno-para-esta-laptop\" class=\"header-mark\" aria-label=\"Header mark for 'El arte previo: cuatro proyectos, ninguno para esta laptop'\"\u003e\u003c/a\u003e2 El arte previo: cuatro proyectos, ninguno para esta laptop\u003c/h2\u003e\u003cp\u003eAntes de escribir una sola línea de Python, hice lo que se supone que hace todo ingeniero: busqué trabajo existente. La noticia decepcionante-pero-alentadora es que hay bastante arte previo sobre \u0026ldquo;teclado RGB de laptop Gigabyte desde Linux\u0026rdquo;. La parte decepcionante es que ninguno funcionó en \u003cem\u003emi\u003c/em\u003e laptop específica sin tener que tocar nada. La parte alentadora es que cada uno me enseñó algo que terminé usando.\u003c/p\u003e\n\u003cp\u003eAquí está el árbol que recorrí antes de seguir mi propio camino.\u003c/p\u003e\n\u003ch3 id=\"wtwrpaeroctl--el-nombre-que-tomé-prestado\" class=\"headerLink\"\u003e\n    \u003ca href=\"#wtwrpaeroctl--el-nombre-que-tom%c3%a9-prestado\" class=\"header-mark\" aria-label=\"Header mark for 'wtwrp/aeroctl — el nombre que tomé prestado'\"\u003e\u003c/a\u003e2.1 wtwrp/aeroctl — el nombre que tomé prestado\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://gitlab.com/wtwrp/aeroctl\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ewtwrp/aeroctl\u003c/code\u003e\u003c/a\u003e es el AeroCtl original, una aplicación C# / .NET para Windows. Es el proyecto que le dio nombre al mío, y leerlo es la forma más fácil de entender cómo se ve un Gigabyte Aero bien soportado desde el lado del host: control de ventiladores, política de carga, teclas Fn no estándar, RGB del teclado, GPU boost, todo pasando por el driver ACPI WMI de Gigabyte (\u003ccode\u003eacpimof.dll\u003c/code\u003e y amigos). La sección de RGB en particular es informativa: confirma que el teclado se accede a través de un HID feature report y no de algún puerto IO propietario o blob WMI.\u003c/p\u003e\n\u003cp\u003eEl problema es obvio: es Windows. Toda la premisa es que ya instalaste Gigabyte ControlCenter o SmartManager para que \u003ccode\u003eacpimof.dll\u003c/code\u003e esté presente. Nada de eso existe en mi laptop, que corre Linux exclusivamente. Pero el código de muestra de RGB en \u003ccode\u003eSamples/\u003c/code\u003e — la parte que es simplemente \u0026ldquo;abre el dispositivo HID, escribe un feature report, ciérralo\u0026rdquo; — es portable en espíritu. Esa es la pieza que me llevé.\u003c/p\u003e\n\u003ch3 id=\"aerocontrolcenter--el-port-de-linux-que-pedía-un-driver-del-kernel\" class=\"headerLink\"\u003e\n    \u003ca href=\"#aerocontrolcenter--el-port-de-linux-que-ped%c3%ada-un-driver-del-kernel\" class=\"header-mark\" aria-label=\"Header mark for 'AeroControlCenter — el port de Linux que pedía un driver del kernel'\"\u003e\u003c/a\u003e2.2 AeroControlCenter — el port de Linux que pedía un driver del kernel\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/tangalbert919/AeroControlCenter\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003etangalbert919/AeroControlCenter\u003c/code\u003e\u003c/a\u003e es un port Linux del Gigabyte Control Center, escrito en C++/Qt, dirigido al Aero 15 Classic (SA/WA/XA/YA). Su enfoque es más ambicioso que el mío: quiere manejar control de ventiladores, política de batería, \u003cem\u003ey\u003c/em\u003e RGB, y para hacerlo limpiamente depende de un driver de kernel compañero, \u003ca href=\"https://github.com/tangalbert919/gigabyte-laptop-wmi\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003egigabyte-laptop-wmi\u003c/code\u003e\u003c/a\u003e, que expone la interfaz WMI como nodos sysfs de Linux.\u003c/p\u003e\n\u003cp\u003eAdmiro la arquitectura y no pude usarla. Mi máquina no está en la lista probada, \u003ccode\u003egigabyte-laptop-wmi\u003c/code\u003e no se enlaza limpiamente en kernels modernos en mi chassis, y no tenía ganas de depurar una tabla ACPI WMI en una laptop que también necesitaba seguir funcionando para el correo. Lo que AeroControlCenter \u003cem\u003esí\u003c/em\u003e me dio fue el archivo \u003ccode\u003e70-keyboard.rules\u003c/code\u003e — es decir, la confirmación de que el enfoque udev era la forma correcta para el problema de permisos. Escribí mi propia regla, pero la escribí porque la regla de ese proyecto me dijo dónde ponerla.\u003c/p\u003e\n\u003ch3 id=\"fusion-kbd-controller--libusb-kernel-unbind-y-una-advertencia\" class=\"headerLink\"\u003e\n    \u003ca href=\"#fusion-kbd-controller--libusb-kernel-unbind-y-una-advertencia\" class=\"header-mark\" aria-label=\"Header mark for 'fusion-kbd-controller — libusb, kernel unbind, y una advertencia'\"\u003e\u003c/a\u003e2.3 fusion-kbd-controller — libusb, kernel unbind, y una advertencia\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/martin31821/fusion-kbd-controller\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003emartin31821/fusion-kbd-controller\u003c/code\u003e\u003c/a\u003e es un pequeño binario C de userspace usando libusb, dirigido al AERO 15X. Leer el README es toda una experiencia: necesita root, desenlaza temporalmente el dispositivo USB del driver \u003ccode\u003eusbhid\u003c/code\u003e del kernel, y el autor advierte alegremente que \u0026ldquo;es posible brickear tu teclado enviando valores sin sentido aquí.\u0026rdquo;\u003c/p\u003e\n\u003cp\u003eFunciona, en un 15X, con libusb, con kernel detach. Yo no quería ninguna de esas cosas. Quería \u003ccode\u003e/dev/hidraw\u003c/code\u003e para poder hablarle al dispositivo mientras el kernel seguía siendo propietario de la interfaz HID (para que las teclas siguieran escribiendo mientras enviaba comandos de color), quería una regla udev para no necesitar root, y \u003cem\u003erealmente\u003c/em\u003e no quería brickear mi propio teclado aprendiendo qué significaban \u0026ldquo;valores sin sentido\u0026rdquo; para el hardware de alguien más. Pero fusion-kbd-controller es donde adquirí el modelo mental de \u0026ldquo;paquete de bytes con un modo y una región de parámetros\u0026rdquo;, que resultó generalizarse al paquete de feature-report del ITE-829X que terminé escribiendo.\u003c/p\u003e\n\u003ch3 id=\"keyboard-fusion-rgb--el-lenguaje-correcto-el-vendor-equivocado\" class=\"headerLink\"\u003e\n    \u003ca href=\"#keyboard-fusion-rgb--el-lenguaje-correcto-el-vendor-equivocado\" class=\"header-mark\" aria-label=\"Header mark for 'keyboard-fusion-rgb — el lenguaje correcto, el vendor equivocado'\"\u003e\u003c/a\u003e2.4 keyboard-fusion-rgb — el lenguaje correcto, el vendor equivocado\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://github.com/rcassani/keyboard-fusion-rgb\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003ercassani/keyboard-fusion-rgb\u003c/code\u003e\u003c/a\u003e es un driver Python HIDAPI para el AORUS 15G. Es lo más cercano a \u0026ldquo;lo que terminé haciendo\u0026rdquo; — Python, HID, userspace, reverse-engineered desde el AORUS Control Center de Windows — excepto por un detalle: los IDs de vendor/producto son \u003ccode\u003e0x1044:0x7a3c\u003c/code\u003e (Chu Yuen Enterprise Co., Ltd.), que es una familia de controladores de teclado completamente distinta a la de mi laptop (\u003ccode\u003e0x0414:0x8104\u003c/code\u003e, el ITE-829X).\u003c/p\u003e\n\u003cp\u003eEl código no puede usarse tal cual — el formato de wire es distinto, el layout por tecla es distinto, los IDs de modo son distintos. Pero el \u003cem\u003eenfoque\u003c/em\u003e es exactamente el que yo quería: feature reports estilo HIDAPI desde Python, decodificados observando lo que escribe la herramienta de Windows. La existencia de ese proyecto fue también un empujón moral: si \u003ccode\u003ercassani\u003c/code\u003e pudo decodificar una sub-familia Gigabyte viendo el tráfico USB, yo podía decodificar la de mi laptop de la misma manera.\u003c/p\u003e\n\u003ch3 id=\"openrgb--el-gigante-que-aún-no-conoce-esta-máquina\" class=\"headerLink\"\u003e\n    \u003ca href=\"#openrgb--el-gigante-que-a%c3%ban-no-conoce-esta-m%c3%a1quina\" class=\"header-mark\" aria-label=\"Header mark for 'OpenRGB — el gigante que aún no conoce esta máquina'\"\u003e\u003c/a\u003e2.5 OpenRGB — el gigante que aún no conoce esta máquina\u003c/h3\u003e\u003cp\u003e\u003ca href=\"https://gitlab.com/CalcProgrammer1/OpenRGB\" target=\"_blank\" rel=\"noopener noreferrer\"\u003e\u003ccode\u003eCalcProgrammer1/OpenRGB\u003c/code\u003e\u003c/a\u003e es la herramienta RGB universal — Windows, Linux, macOS, cada fabricante de motherboard y RAM bajo el sol. Es el proyecto que quería poder usar y no pude. Su \u003ca href=\"https://gitlab.com/CalcProgrammer1/OpenRGB/-/issues/2288\" target=\"_blank\" rel=\"noopener noreferrer\"\u003eissue abierto #2288\u003c/a\u003e sobre el Gigabyte Aero 15 OLED YD es, esencialmente, \u0026ldquo;aún no tenemos este modelo\u0026rdquo;. Mi laptop es un SKU diferente al de ese issue, pero lo suficientemente cercano como para saber que la respuesta era la misma: todavía no.\u003c/p\u003e\n\u003cp\u003eMenciono OpenRGB explícitamente porque en un mundo mejor este post no existe — hubiera abierto su panel de configuración, elegido un efecto, y seguido adelante. En cambio voy a decodificar un paquete, escribir 593 líneas de Python, y pretender que estoy bien con esto.\u003c/p\u003e\n\u003ch3 id=\"lo-que-tomé-del-árbol\" class=\"headerLink\"\u003e\n    \u003ca href=\"#lo-que-tom%c3%a9-del-%c3%a1rbol\" class=\"header-mark\" aria-label=\"Header mark for 'Lo que tomé del árbol'\"\u003e\u003c/a\u003e2.6 Lo que tomé del árbol\u003c/h3\u003e\u003cp\u003eEl resumen de una hora de investigación se veía algo así:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  prior art                         usable for my laptop?\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ----------------------------      ---------------------\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  wtwrp/aeroctl (Windows, C#)       no  (wrong OS, WMI-based)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — packet shape intuition\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  AeroControlCenter (Linux, Qt)     no  (different model; wants kernel driver)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — udev rule as the permissions story\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  fusion-kbd-controller (C, libusb) no  (wrong model; kernel unbind; root only)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — \u0026#34;mode byte + params + checksum\u0026#34; mental model\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  keyboard-fusion-rgb (Py, HIDAPI)  no  (different vendor, 0x1044 vs 0x0414)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — Python + HID is the right shape\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  OpenRGB (C++, everywhere)         no  (my SKU not yet supported)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                    yes — confirmation that nobody else had this\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEse es el truco de la investigación de arte previo en este rincón del mundo: la respuesta a \u0026ldquo;¿hay algo para mi máquina?\u0026rdquo; es casi siempre \u003cem\u003eno\u003c/em\u003e, pero la respuesta a \u0026ldquo;¿qué han descubierto otras personas que no debería re-derivar?\u0026rdquo; es casi siempre \u003cem\u003ealgo\u003c/em\u003e. Cerré las pestañas del navegador con un plan a medio formar: Python, \u003ccode\u003e/dev/hidraw\u003c/code\u003e, una regla udev para no necesitar sudo, un paquete que probablemente sea \u0026ldquo;modo + parámetros + checksum8\u0026rdquo;, y — el gran riesgo — un algoritmo de checksum que tendría que adivinar porque ninguno del arte previo usa exactamente este controlador.\u003c/p\u003e\n\u003cp\u003eLo que me devolvió a la pregunta inocente: ¿qué está \u003cem\u003erealmente\u003c/em\u003e en mi bus USB, y qué quiere escuchar?\u003c/p\u003e\n\u003ch2 id=\"reverse-engineering-del-teclado\" class=\"headerLink\"\u003e\n    \u003ca href=\"#reverse-engineering-del-teclado\" class=\"header-mark\" aria-label=\"Header mark for 'Reverse-engineering del teclado'\"\u003e\u003c/a\u003e3 Reverse-engineering del teclado\u003c/h2\u003e\u003cp\u003eLo primero que aprendí es que hacer reverse-engineering de un periférico USB es 10% trabajo de detective inteligente y 90% leer la salida de \u003ccode\u003edmesg\u003c/code\u003e mientras tu pareja pregunta qué estás haciendo a la 1am. El teclado del Gigabyte Aero no es un dongle separado, claro — es un dispositivo USB interno, y \u003ccode\u003elsusb | grep 0414\u003c/code\u003e lo confirmó sin chistar:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBus 001 Device 003: ID 0414:8104 Gigabyte Technology Co.,Ltd ...\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eVendor \u003ccode\u003e0x0414\u003c/code\u003e, producto \u003ccode\u003e0x8104\u003c/code\u003e. Ese es el ITE-829X. Busqué todo lo que pude encontrar, lo crucé con las fuentes de la herramienta de Windows, y formé una hipótesis: el control RGB vive detrás de un HID feature report, expuesto en una interfaz cuya usage page es \u003ccode\u003e0xFF01\u003c/code\u003e — una página HID definida por el vendor, que es el equivalente USB de un camino de tierra marcado como \u0026ldquo;privado\u0026rdquo;.\u003c/p\u003e\n\u003ch3 id=\"encontrando-el-devhidraw-correcto\" class=\"headerLink\"\u003e\n    \u003ca href=\"#encontrando-el-devhidraw-correcto\" class=\"header-mark\" aria-label=\"Header mark for 'Encontrando el \u0026lt;code\u0026gt;/dev/hidraw\u0026lt;/code\u0026gt; correcto'\"\u003e\u003c/a\u003e3.1 Encontrando el \u003ccode\u003e/dev/hidraw\u003c/code\u003e correcto\u003c/h3\u003e\u003cp\u003eAquí es donde el reverse engineering deja de sentirse como magia y empieza a sentirse como arqueología de sistema de archivos. Linux expone cada dispositivo HID bajo \u003ccode\u003e/sys/bus/hid/devices/\u003c/code\u003e, y para cada dispositivo te da un \u003ccode\u003ereport_descriptor\u003c/code\u003e — un blob de bytes que te dice (si entrecierras los ojos) sobre qué está dispuesto a hablar el dispositivo.\u003c/p\u003e\n\u003cp\u003eEscribí un scanner. Dado el par vendor/producto correcto, globa el directorio sysfs, lee cada \u003ccode\u003ereport_descriptor\u003c/code\u003e, y busca los tres bytes \u003ccode\u003e\\x06\\x01\\xff\u003c/code\u003e — jerga HID para \u0026ldquo;usage page \u003ccode\u003e0xFF01\u003c/code\u003e\u0026rdquo;. Cuando encuentro una coincidencia, sigo el symlink hacia su subdirectorio \u003ccode\u003ehidraw\u003c/code\u003e y tomo el nodo \u003ccode\u003e/dev/hidrawN\u003c/code\u003e que le pertenece.\u003c/p\u003e\n\u003cp\u003ePiensa en udev + sysfs aquí como una especie de \u003ccode\u003e/proc\u003c/code\u003e para periféricos: un documento vivo de solo lectura que describe lo que el kernel cree actualmente sobre cada dispositivo conectado. Y como \u003ccode\u003e/proc\u003c/code\u003e, la forma correcta de usarlo no es confiar en un número de dispositivo particular — \u003ccode\u003e/dev/hidraw0\u003c/code\u003e hoy podría ser \u003ccode\u003e/dev/hidraw3\u003c/code\u003e mañana si conectas un ratón — sino escanear, filtrar y elegir por propiedad.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:99-119\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@staticmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_find_hidraw\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evendor_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eVID_DEFAULT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct_id\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ePID_DEFAULT\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eusage_page_bytes\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sa\"\u003eb\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\x06\\x01\\xff\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;\u0026#34;\u0026#34;Find the hidraw node with Usage Page 0xFF01 by scanning /sys.\u0026#34;\u0026#34;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epattern\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/sys/bus/hid/devices/0003:\u003c/span\u003e\u003cspan class=\"si\"\u003e%04X\u003c/span\u003e\u003cspan class=\"s2\"\u003e:\u003c/span\u003e\u003cspan class=\"si\"\u003e%04X\u003c/span\u003e\u003cspan class=\"s2\"\u003e.*\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e%\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evendor_id\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eproduct_id\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003epath\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003esorted\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eglob\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eglob\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epattern\u003c/span\u003e\u003cspan class=\"p\"\u003e)):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003erd_path\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;report_descriptor\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"nb\"\u003eopen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003erd_path\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;rb\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003erd\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ef\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eOSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eusage_page_bytes\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003erd\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003ehidraw_dir\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;hidraw\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eentries\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elistdir\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehidraw_dir\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003edevpath\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/dev/\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eentries\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eexists\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edevpath\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003edevpath\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"ne\"\u003eOSError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl prefijo \u003ccode\u003e0003:\u003c/code\u003e en el patrón del glob vale una pausa. Los dispositivos HID se registran en el kernel con IDs de bus, y \u003ccode\u003e0003\u003c/code\u003e es USB. El patrón completo se expande, para mi máquina, a \u003ccode\u003e/sys/bus/hid/devices/0003:0414:8104.*\u003c/code\u003e. El \u003ccode\u003e*\u003c/code\u003e está ahí porque un solo dispositivo físico puede exponer varias interfaces HID — una para las teclas normales, una para las teclas multimedia, una para el canal de control del vendor — y yo quiero aquella cuyo report descriptor anuncia \u003ccode\u003e0xFF01\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eAsí quedó el flujo de descubrimiento:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  /sys/bus/hid/devices/0003:0414:8104.*   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  glob-expand (one entry per HID iface)   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  open report_descriptor, scan for        │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  b\u0026#39;\\x06\\x01\\xff\u0026#39;  ==  usage page 0xFF01  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │ match\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  ls \u0026lt;iface\u0026gt;/hidraw/  -\u0026gt;  \u0026#34;hidraw0\u0026#34;       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  devpath = \u0026#34;/dev/\u0026#34; + entry               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └─────────────────────┬────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                       ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 ┌──────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  os.open(devpath, O_RDWR)                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 │  fcntl.ioctl(fd, HIDIOCSFEATURE(9), buf) │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 └──────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eUna vez que ese hidraw path está en mano, todo lo demás es \u003ccode\u003efcntl.ioctl\u003c/code\u003e. Los ioctls \u003ccode\u003eHIDIOCSFEATURE\u003c/code\u003e y \u003ccode\u003eHIDIOCGFEATURE\u003c/code\u003e son la forma estándar de enviar y recibir HID feature reports desde userspace. Hice los números mágicos a mano porque no quería depender de \u003ccode\u003ehidapi\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:56-62\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etyp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e16\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eord\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etyp\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003enr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHIDIOCSFEATURE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mh\"\u003e0x06\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eHIDIOCGFEATURE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003e_ioc\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;H\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mh\"\u003e0x07\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003en\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eREPORT_SIZE\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e9\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa clase \u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e (\u003ccode\u003esrc/aeroctl_linux_indicator/device.py:77-240\u003c/code\u003e) es toda la abstracción de hardware en un solo lugar: abre el dispositivo, escribe paquetes de 9 bytes, lee respuestas de 9 bytes, cierra al salir. También es un context manager, porque me gustan mis file descriptors como me gustan mis laptops caras: cerrados cuando termino con ellos.\u003c/p\u003e\n\u003cp\u003eTener un file descriptor es una cosa. Tener los \u003cem\u003ebytes correctos para enviarle\u003c/em\u003e es otra, y ahí es donde golpeé la primera pared que parecía un rompecabezas en lugar de un problema de fontanería — que, resulta, es exactamente cuando el reverse engineering se pone interesante.\u003c/p\u003e\n\u003ch2 id=\"el-paquete-nueve-bytes-y-un-checksum-que-no-es-xor\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-paquete-nueve-bytes-y-un-checksum-que-no-es-xor\" class=\"header-mark\" aria-label=\"Header mark for 'El paquete: nueve bytes y un checksum que no es XOR'\"\u003e\u003c/a\u003e4 El paquete: nueve bytes y un checksum que no es XOR\u003c/h2\u003e\u003cp\u003eAhora, el paquete. Esta es la parte graciosa.\u003c/p\u003e\n\u003cp\u003eUno esperaría — yo ciertamente lo hice — que un modesto paquete de control de 9 bytes para un controlador LED se verificaría a sí mismo con XOR. Todo el mundo usa XOR. XOR es barato, XOR es a lo que llega el ingeniero de firmware cuando son las 4pm del viernes y quiere irse a casa. Miré las capturas de Wireshark de la herramienta de Windows. Escribí un pequeño script Python para probar XOR sobre cada posible slice del paquete. Nada coincidió.\u003c/p\u003e\n\u003cp\u003eProbé la suma simple. Tampoco.\u003c/p\u003e\n\u003cp\u003eProbé \u003ccode\u003e256 - suma\u003c/code\u003e. Cerca. Sospechosamente cerca. Desviado por uno.\u003c/p\u003e\n\u003cp\u003eLa respuesta, resultó, es esta:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003echecksum = (0xFF - (sum(bytes[1..7]) \u0026amp; 0xFF)) \u0026amp; 0xFF\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eNo XOR. \u003cstrong\u003eNo XOR.\u003c/strong\u003e Es una cosa tipo complemento a uno de 8 bits: toma la suma de los bytes de payload, enmascárala a un byte, luego réstala de \u003ccode\u003e0xFF\u003c/code\u003e. Un checksum de \u0026ldquo;hacer que todo totalice \u003ccode\u003e0xFF\u003c/code\u003e\u0026rdquo;. Lo cual es perfectamente razonable, de la manera en que todos los protocolos de hardware son perfectamente razonables una vez que dejas de esperar que sean de otra forma.\u003c/p\u003e\n\u003cp\u003eSi estás pensando \u0026ldquo;ah, eso es solo \u003ccode\u003e~suma + 1 - 1\u003c/code\u003e, ¿verdad?\u0026rdquo; — sí, y también, no, ya basta, no es complemento a dos. Es un invariante de \u0026ldquo;suma más checksum debe igualar \u003ccode\u003e0xFF\u003c/code\u003e\u0026rdquo;. Lo cual es elegante, si ligeramente inusual, y la primera regla del reverse engineering es: haz lo que hace el hardware, aunque el hardware sea un poco excéntrico.\u003c/p\u003e\n\u003cp\u003eAquí está toda la implementación:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/device.py:94-96\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@staticmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_checksum8\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003esum\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xFF\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDos líneas, más un decorador. El \u003ccode\u003e\u0026amp; 0xFF\u003c/code\u003e de afuera es por pura precaución — la aritmética en Python no tiene límite, y quiero que esto quepa en un byte sin importar cuántos bytes le pase.\u003c/p\u003e\n\u003cp\u003eEl layout del paquete en sí es este:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  Offset  ┬───────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B0     │  0x00                        ← HID report ID / padding\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B1     │  command   (e.g. 0x08 = SET_EFFECT, 0x80 = GET_FIRMWARE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B2     │  sub-arg   (often 0x00)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B3     │  effect id (1=static, 2=breathing, 3=wave, ...)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B4     │  speed\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B5     │  brightness\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B6     │  color id  (0..8)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B7     │  direction\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   B8     │  checksum  =  (0xFF - (sum(B0..B7) \u0026amp; 0xFF)) \u0026amp; 0xFF\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ────────┴───────────────────────────────────────────────────────────\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           9 bytes total, sent via HIDIOCSFEATURE ioctl on /dev/hidrawN\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eVale la pena admirar un momento la elección del framing. El byte 0 es el report ID de HID, y porque este dispositivo particular usa report ID \u003ccode\u003e0\u003c/code\u003e, es efectivamente padding — pero el ABI de Linux \u003ccode\u003ehidraw\u003c/code\u003e siempre quiere un report ID como primer byte de un feature report, así que lo cargo conmigo. Los bytes 1–7 son donde vive el comando real. El byte 8 es el checksum en el que estuve equivocado durante dos horas.\u003c/p\u003e\n\u003cp\u003eSi has escrito emuladores de terminal, la analogía que me hizo click fue esta: \u003cstrong\u003eun paquete de HID feature report es básicamente un código de escape ANSI con un checksum.\u003c/strong\u003e Tienes un carácter de inicio (el byte de comando), tienes parámetros (efecto, velocidad, brillo, color, dirección), y tienes un terminador — excepto que el terminador, en lugar de ser una letra imprimible como \u003ccode\u003em\u003c/code\u003e para \u0026ldquo;set graphics mode,\u0026rdquo; es un byte de checksum que verifica que el firmware no recibió basura. Envía el byte equivocado y el controlador te ignora educadamente. Envía los bytes correctos y tu teclado se convierte en una discoteca.\u003c/p\u003e\n\u003cp\u003eEl resto de \u003ccode\u003edevice.py\u003c/code\u003e es solo vocabulario. \u003ccode\u003eset_effect\u003c/code\u003e es \u003ccode\u003e_send_feature(_packet(0x08, 0x00, effect, speed, brightness, color, direction))\u003c/code\u003e. \u003ccode\u003eget_firmware_version\u003c/code\u003e es \u003ccode\u003e_send_feature(_packet(0x80))\u003c/code\u003e seguido de un \u003ccode\u003e_get_feature(9)\u003c/code\u003e. \u003ccode\u003ereset\u003c/code\u003e es \u003ccode\u003e_send_feature(_packet(0x13, 0xFF))\u003c/code\u003e. Cada comando es una línea. Cada línea es una oración en un idioma que eventualmente aprendí a hablar.\u003c/p\u003e\n\u003cp\u003eHablar ese idioma correctamente, sin embargo, asumía que podía abrir el file descriptor en primer lugar — y una vez que escribí mi contraseña de sudo por cuadragésima vez en un día, la siguiente rama del árbol prácticamente se anunció sola.\u003c/p\u003e\n\u003ch2 id=\"udev-el-guardián-de-la-felicidad\" class=\"headerLink\"\u003e\n    \u003ca href=\"#udev-el-guardi%c3%a1n-de-la-felicidad\" class=\"header-mark\" aria-label=\"Header mark for 'udev: el guardián de la felicidad'\"\u003e\u003c/a\u003e5 udev: el guardián de la felicidad\u003c/h2\u003e\u003cp\u003eHay un círculo especial del purgatorio del escritorio Linux reservado para las herramientas que funcionan perfectamente, pero solo si las corres con \u003ccode\u003esudo\u003c/code\u003e. Cada vez que cambiaba el teclado, tenía que escribir mi contraseña. Cada vez que el tray indicator quería leer el efecto actual, no podía, porque un proceso de usuario normal no puede abrir \u003ccode\u003e/dev/hidraw0\u003c/code\u003e para lectura-escritura por defecto. Eso era un dealbreaker para un \u0026ldquo;tray indicator\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eLa solución es una regla \u003ccode\u003eudev\u003c/code\u003e de cuatro líneas, y es una pequeña obra maestra:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ebash\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# install.sh:14-21\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eUDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/etc/udev/rules.d/70-gigabyte-kbd.rules\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e ! -f \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$UDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;SUBSYSTEMS==\u0026#34;usb\u0026#34;, ATTRS{idVendor}==\u0026#34;0414\u0026#34;, ATTRS{idProduct}==\u0026#34;8104\u0026#34;, MODE=\u0026#34;0660\u0026#34;, TAG+=\u0026#34;uaccess\u0026#34;\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e sudo tee \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$UDEV_RULE_FILE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u0026gt; /dev/null\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    sudo udevadm control --reload-rules \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo udevadm trigger\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;udev rules created and applied.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;udev rules already exist.\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efi\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa palabra mágica aquí es \u003ccode\u003eTAG+=\u0026quot;uaccess\u0026quot;\u003c/code\u003e. Esta etiqueta le dice a systemd-logind: \u0026ldquo;quien esté actualmente conectado en la sesión local debería poder acceder a este dispositivo\u0026rdquo;. Sin ella, \u003ccode\u003e/dev/hidraw0\u003c/code\u003e es propiedad de \u003ccode\u003eroot:root\u003c/code\u003e con modo \u003ccode\u003e0660\u003c/code\u003e, y tu proceso de usuario rebota contra él como una polilla contra una ventana.\u003c/p\u003e\n\u003cp\u003eCon ella, systemd agrega un ACL al nodo del dispositivo dándole a tu usuario conectado permisos de lectura+escritura, automáticamente, mientras estés en el teclado. Cierras sesión, el ACL desaparece. Conectas un segundo teclado Aero, el ACL aparece en el nuevo también. Es el equivalente Linux de una tarjeta de hotel: tienes acceso mientras eres el huésped, el sistema te lo quita cuando te vas.\u003c/p\u003e\n\u003cp\u003eMe gusta pensar en las reglas udev como una \u003cstrong\u003ecarta de amor a tu yo del futuro\u003c/strong\u003e. Las escribes una vez, las dejas en el disco, y para siempre después, la máquina da la bienvenida a tu yo futuro — el que habrá olvidado todo sobre este proyecto y solo quiere que el teclado se ilumine — como si nada hubiera sido difícil alguna vez. El script \u003ccode\u003einstall.sh\u003c/code\u003e hace este regalo automáticamente en la primera instalación. Nunca volverás a ver la carta de amor, porque nunca la necesitarás.\u003c/p\u003e\n\u003cp\u003eLa regla udev arregló el baile de permisos, pero resolverla también sacó a la superficie una pregunta más silenciosa: ahora que cualquier usuario conectado podía manejar el dispositivo, ¿\u003cem\u003ecómo\u003c/em\u003e deberían manejarlo realmente? ¿En la línea de comandos a la 1am? ¿Desde un menú del tray mientras escribes correo? Ambos, idealmente — y en el momento en que dije \u0026ldquo;ambos\u0026rdquo; en voz alta, la siguiente rama del proyecto se trazó sola.\u003c/p\u003e\n\u003ch2 id=\"dos-frontends-un-cerebro\" class=\"headerLink\"\u003e\n    \u003ca href=\"#dos-frontends-un-cerebro\" class=\"header-mark\" aria-label=\"Header mark for 'Dos frontends, un cerebro'\"\u003e\u003c/a\u003e6 Dos frontends, un cerebro\u003c/h2\u003e\u003cp\u003eUna vez que la capa del dispositivo funcionó, había una decisión que tomar: ¿CLI o GUI? Dije: por qué no ambos. Pero también dije: \u003cstrong\u003eno dos veces\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eLa forma en que terminé es un clásico \u0026ldquo;dos frontends compartiendo un cerebro\u0026rdquo; — la misma clase \u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e en el fondo, dos UIs muy distintas encima. Ninguna UI sabe nada sobre HID. Ninguna UI hace su propio cálculo de checksum. Ambas se apoyan en la misma clase de dispositivo, y el único estado que comparten es un pequeño archivo JSON en el directorio de cache del usuario.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   ┌──────────────────────────┐    ┌─────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   aeroctl-kbd  (CLI)     │    │   aeroctl-kbd-tray  (GTK)   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │                          │    │                             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   argparse, stdin/stdout │    │   AppIndicator menu         │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   │   src/.../cli.py:69-162  │    │   src/.../tray.py:44-150    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   └──────────────┬───────────┘    └──────────────┬──────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  │                               │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  │         both talk to          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  ▼                               ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ┌────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │           AeroKeyboardRGB (device.py)          │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │                                                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    _find_hidraw  →  os.open  →  ioctl dance    │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │                                                │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    set_effect / get_effect / firmware / reset  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         └────────────────────────┬───────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                  ▼\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                  /dev/hidraw?   ────────►   ITE-829X   ────►  RGB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                           (keyboard LEDs)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ┌────────────────────────────────────────────────┐\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    ~/.cache/aeroctl-kbd-state.json             │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    { effect, speed, brightness, color, dir }   │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    written on \u0026#34;off\u0026#34;, read on \u0026#34;on\u0026#34;/\u0026#34;toggle\u0026#34;     │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         │    shared by CLI and tray                      │\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         └────────────────────────────────────────────────┘\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl CLI vive en \u003ccode\u003esrc/aeroctl_linux_indicator/cli.py:69-162\u003c/code\u003e. Es una puerta \u003ccode\u003eargparse\u003c/code\u003e con subcomandos — \u003ccode\u003edevices\u003c/code\u003e, \u003ccode\u003estatus\u003c/code\u003e, \u003ccode\u003efirmware\u003c/code\u003e, \u003ccode\u003eset\u003c/code\u003e, \u003ccode\u003eoff\u003c/code\u003e, \u003ccode\u003eon\u003c/code\u003e, \u003ccode\u003etoggle\u003c/code\u003e, \u003ccode\u003ereset\u003c/code\u003e, \u003ccode\u003ekeyboard-mode\u003c/code\u003e, \u003ccode\u003eraw\u003c/code\u003e — y cada subcomando es un bloque \u003ccode\u003ewith kb:\u003c/code\u003e que abre el dispositivo, hace su trabajo, y cierra.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/cli.py:95-118  (off/on, state round-trip)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;off\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003e_save_state\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;static\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;black\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: off\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecmd\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;on\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003est\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003e_load_state\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: on (restored)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ekb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;static\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;white\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ok: on (default white)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl comando \u0026ldquo;off\u0026rdquo; no es \u0026ldquo;por favor oscurécete\u0026rdquo;. Por dentro es: \u0026ldquo;captura el efecto actual al disco, luego pon el brillo en cero sobre un efecto estático negro\u0026rdquo;. El firmware del teclado no tiene un apagado real — tiene \u003ccode\u003ebrightness = 0\u003c/code\u003e. El comando \u0026ldquo;on\u0026rdquo; es simétrico: lee el JSON y vuelve a aplicar el efecto anterior. Si no hay estado previo (primer arranque, instalación limpia), recurre a un blanco cómodo al 100% de brillo.\u003c/p\u003e\n\u003cp\u003eEl tray indicator (\u003ccode\u003esrc/aeroctl_linux_indicator/tray.py:44-184\u003c/code\u003e) hace el mismo baile, pero manejado por clics de ratón. Construye un menú GTK con tres submenús — Brightness, Color, Effect — más \u003ccode\u003eToggle On/Off\u003c/code\u003e, \u003ccode\u003eReset\u003c/code\u003e, \u003ccode\u003eStatus\u003c/code\u003e, \u003ccode\u003eQuit\u003c/code\u003e. Cada elemento del menú está conectado a los mismos helpers:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/tray.py:62-83\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003espeed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003elambda\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003espeed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCOLORS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set_brightness\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAeroKeyboardRGB\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;effect\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebrightness\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_set_effect_only\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eAeroKeyboardRGB\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ecur\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebri\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;brightness\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003edev\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset_effect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEFFECTS\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eeffect\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;speed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ebri\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;direction\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003e_safe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e_op\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl wrapper \u003ccode\u003e_safe\u003c/code\u003e es una confesión de tres líneas: \u0026ldquo;soy un tray app, y si la apertura del dispositivo falla no debería crashear todo el escritorio\u0026rdquo;. Se traga las excepciones y las registra en stderr. No es elegante. Es pragmático. Una app del tray crasheada es una peor experiencia de usuario que una app del tray que falla silenciosamente en iluminar tu teclado, y si algo sale mal — cable desconectado, regla udev faltante, alguien quitó la batería — todavía tienes una sesión GNOME funcional desde donde arreglarlo.\u003c/p\u003e\n\u003cp\u003ePorque ambos frontends escriben al mismo \u003ccode\u003e~/.cache/aeroctl-kbd-state.json\u003c/code\u003e, alternar desde el tray y luego desde el CLI Hace Lo Correcto. \u0026ldquo;Apagar desde el tray, encender desde el CLI\u0026rdquo; restaura los mismos colores y efectos que tenías. Esa pequeña consistencia es, honestamente, la razón por la que el indicator se siente pulido.\u003c/p\u003e\n\u003cp\u003ePulido en mi laptop, de todos modos. El pensamiento que me seguía rondando era: \u0026ldquo;pulido en mi laptop\u0026rdquo; es una trampa — un tray indicator que solo corre en exactamente la distro, exactamente el Python, y exactamente el build de AppIndicator que casualmente tengo instalado no es una herramienta, es un truco de circo. Así fue como caí en el laberinto de PyInstaller.\u003c/p\u003e\n\u003ch2 id=\"pyinstaller--appindicator-un-acto-de-diplomacia-entre-distros\" class=\"headerLink\"\u003e\n    \u003ca href=\"#pyinstaller--appindicator-un-acto-de-diplomacia-entre-distros\" class=\"header-mark\" aria-label=\"Header mark for 'PyInstaller \u0026#43; AppIndicator: un acto de diplomacia entre distros'\"\u003e\u003c/a\u003e7 PyInstaller + AppIndicator: un acto de diplomacia entre distros\u003c/h2\u003e\u003cp\u003eLos escritorios Linux son muchas cosas, pero \u0026ldquo;consistentes sobre las APIs de system tray\u0026rdquo; no es una de ellas. La librería original para esto se llamaba \u003ccode\u003eAppIndicator3\u003c/code\u003e, mantenida por Canonical para Unity. Cuando Unity murió, la adoptó MATE. Luego GNOME la mantuvo viva a través de una extensión de shell. Luego salió Ubuntu 24.04 — y con él, el renombre: \u003ccode\u003eAyatanaAppIndicator3\u003c/code\u003e, el fork-del-fork que se supone que todos deben usar ahora.\u003c/p\u003e\n\u003cp\u003eLas instalaciones nuevas en 24.04 solo traen Ayatana. Sistemas más viejos y algunas distros solo traen la versión legacy. Unas pocas traen ambas. Necesitaba que mi binario funcionara en las tres.\u003c/p\u003e\n\u003cp\u003eEl tray lo maneja en el momento del import con un pequeño try/except que prueba el nombre moderno primero y hace fallback:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# src/aeroctl_linux_indicator/tray.py:12-17\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003egi\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequire_version\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;AyatanaAppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eAppIndicator\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimportlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eimport_module\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;gi.repository.AyatanaAppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"ne\"\u003eImportError\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003egi\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erequire_version\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;AppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;0.1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eAppIndicator\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eimportlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eimport_module\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;gi.repository.AppIndicator3\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eBien para un import de Python. No bien para PyInstaller, que tiene que saber en tiempo de build qué módulos empaquetar. Así que agregué un sniff-test correspondiente al script de build: corre un pequeño snippet inline de Python contra el intérprete activo, verifica cuál de las dos librerías está realmente instalada, e imprime el path de import. La shell luego pasa ese path a PyInstaller como \u003ccode\u003e--hidden-import\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ebash\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# scripts/build-binaries.sh:14-24\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eAPPINDICATOR_IMPORT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eAPPINDICATOR_IMPORT\u003c/span\u003e\u003cspan class=\"k\"\u003e:-$(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$PYTHON_BIN\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e - \u003cspan class=\"s\"\u003e\u0026lt;\u0026lt;\u0026#39;PY\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eimport gi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003etry:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    gi.require_version(\u0026#34;AyatanaAppIndicator3\u0026#34;, \u0026#34;0.1\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    print(\u0026#34;gi.repository.AyatanaAppIndicator3\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003eexcept ValueError:\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    gi.require_version(\u0026#34;AppIndicator3\u0026#34;, \u0026#34;0.1\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003e    print(\u0026#34;gi.repository.AppIndicator3\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s\"\u003ePY\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEste es el tipo de detalle que no llega a los títulos de los posts, porque \u0026ldquo;agregué un heredoc de 18 líneas a mi script de build\u0026rdquo; no es un titular. Pero es genuinamente la diferencia entre \u0026ldquo;corre en mi máquina\u0026rdquo; y \u0026ldquo;corre en la máquina del usuario también\u0026rdquo;. Cuando alguien clona el repo en Fedora, el sniff-test resuelve a Ayatana. Cuando alguien lo clona en una máquina LTS 20.04 que nunca se enteró del cambio, resuelve al nombre legacy. PyInstaller empaqueta el correcto y el binario Simplemente Funciona.\u003c/p\u003e\n\u003cp\u003eEl binario CLI se construye con \u003ccode\u003e--onefile\u003c/code\u003e. El binario del tray se construye con \u003ccode\u003e--onedir\u003c/code\u003e, porque el runtime de GTK necesita muchos archivos hermanos (caches de íconos, archivos de schema, typelibs) que PyInstaller no puede incrustar en un solo ejecutable sin sufrir. El script \u003ccode\u003einstall.sh\u003c/code\u003e luego empaqueta el árbol onedir, lo extrae en \u003ccode\u003e~/.local/bin/aeroctl-kbd-tray/\u003c/code\u003e, y crea una entrada de escritorio \u003ccode\u003eautostart\u003c/code\u003e que apunta al binario del tray dentro.\u003c/p\u003e\n\u003cp\u003eEn este punto, la historia del software estaba efectivamente terminada — la parte de distribución, de todos modos. Lo que quedaba era el lado de la historia del hardware, que solo podía leer en retrospectiva, a través del commit log. Y uno de esos commits es la razón por la que estoy escribiendo este ensayo.\u003c/p\u003e\n\u003ch2 id=\"límites-del-hardware-contados-por-el-commit-log\" class=\"headerLink\"\u003e\n    \u003ca href=\"#l%c3%admites-del-hardware-contados-por-el-commit-log\" class=\"header-mark\" aria-label=\"Header mark for 'Límites del hardware, contados por el commit log'\"\u003e\u003c/a\u003e8 Límites del hardware, contados por el commit log\u003c/h2\u003e\u003cp\u003eSi lees el git log del proyecto de arriba a abajo, se lee casi como un cuento corto. Voy a citar hashes de commits — los reales — porque aquí es donde la narrativa se volvió real.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e550ee28  feat: setup project, cli, tray and configure auto-releases      (1077 LoC)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e00055f2  test: add test suite, mock hardware IO and integrate pytest     (coverage to CI)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e74646e6  fix: tray mock attribute error and boost test coverage to ~78%\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecaea8df  test: add missing branches coverage to cli.py reaching +80%\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ef39a4b1  fix(install): use python environment for pyinstaller build\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ef4a204e  fix(install): auto-start the tray indicator immediately\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e68dbdae  chore: remove unsupported RGB effects                           ← el triste\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ee1ac392  docs: add supported device model AERO X16 2WH\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e48c0f46  docs: clarify native hardware RGB effect limitations\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003e550ee28\u003c/code\u003e es el volcado inicial: 1,077 líneas de una sola vez, 16 archivos, el CLI, el tray, el script de build, el script de instalación, el README. Así es siempre como se ve el primer commit de un proyecto de reverse engineering — no porque no sepas sobre buenas prácticas de commit, sino porque acabas de pasar un mes con todo viviendo en un solo directorio sin versionar mientras descubrías si la cosa iba a funcionar.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e00055f2\u003c/code\u003e es el primer test suite. Mockeé la capa de hardware (\u003ccode\u003e/dev/hidraw\u003c/code\u003e no existe en CI), para que el harness de pruebas pudiera ejercer la construcción de paquetes, el round-tripping de estado, y el parsing de argumentos del CLI sin necesitar un teclado físico en el runner.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e74646e6\u003c/code\u003e es el momento vergonzoso. Había mockeado \u003ccode\u003eAppIndicator.Indicator.new\u003c/code\u003e pero olvidado un atributo que retorna, y los tests del tray fallaron con un \u003ccode\u003eAttributeError\u003c/code\u003e profundo dentro del cableado de GTK. Una línea, un commit, 35 nuevas líneas de test, y un salto de cobertura a ~78%.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ecaea8df\u003c/code\u003e me llevó sobre el 80% de cobertura lógica en \u003ccode\u003ecli.py\u003c/code\u003e — agregó 56 líneas en \u003ccode\u003etests/test_cli.py\u003c/code\u003e tocando cada rama de subcomando. Para un proyecto de 593 líneas, el 80% es el punto justo donde cada ruta principal queda ejercitada pero no estoy escribiendo tests para los internos de \u003ccode\u003eargparse\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ef39a4b1\u003c/code\u003e y \u003ccode\u003ef4a204e\u003c/code\u003e son los achaques de juventud del script de instalación. \u003ccode\u003ef39a4b1\u003c/code\u003e hizo dos cosas: tradujo todo el script del español (uno de los idiomas nativos del autor) al inglés para que los no-hispanohablantes pudieran auditarlo, y hizo que el build de PyInstaller usara un virtualenv local para que el script no tropezara con PEP-668 en distros modernas basadas en Debian. \u003ccode\u003ef4a204e\u003c/code\u003e es el pulido diminuto que dice \u0026ldquo;después de una instalación nueva, también lanza el tray ahora mismo, en lugar de hacer que el usuario cierre sesión y vuelva a abrirla\u0026rdquo;. Esa es la llamada \u003ccode\u003enohup\u003c/code\u003e al final de \u003ccode\u003einstall.sh\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eY luego está \u003ccode\u003e68dbdae\u003c/code\u003e: \u003cstrong\u003e\u0026ldquo;chore: remove unsupported RGB effects.\u0026rdquo;\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSon seis líneas eliminadas de \u003ccode\u003edevice.py\u003c/code\u003e y ocho líneas cambiadas en \u003ccode\u003etray.py\u003c/code\u003e. Es también el momento en que aprendí que mi flamante Aero X16 era menos capaz que un Aero 15 de 2019. Mira el diff:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003epython\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# commit 68dbdae, src/aeroctl_linux_indicator/device.py, removed:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;ripple\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e6\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;neon\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;rainbow\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;circle\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e11\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;hedge\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e12\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;rotate\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e13\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEsos son efectos reales. Existen en el protocolo de firmware de Windows. Están documentados. Simplemente no funcionan en el Aero 16 de 12va generación. El firmware acepta el comando, pero el teclado no renderiza el efecto. Los modelos más nuevos delegan esos efectos al \u003cstrong\u003eGigabyte Control Center\u003c/strong\u003e — un servicio solo para Windows que pinta teclas cuadro por cuadro sobre USB desde userspace, porque el firmware ya no sabe cómo hacerlos él mismo.\u003c/p\u003e\n\u003cdiv class=\"details admonition note open\"\u003e\n    \u003cdiv class=\"details-summary admonition-title\"\u003e\n        \u003cspan class=\"icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z\"/\u003e\u003c/svg\u003e\u003c/span\u003ePor qué `68dbdae` es la línea más triste del repo\u003cspan class=\"details-icon\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"details-content\"\u003e\n        \u003cdiv class=\"admonition-content\"\u003eNo es un bug fix. No es un refactor. Es un reconocimiento de que Gigabyte tomó una decisión de negocios — \u0026ldquo;que la app de Windows haga el renderizado, recorta el firmware\u0026rdquo; — y esa decisión es permanentemente visible en mi teclado Linux en la forma de seis nombres de efectos que ya no existen.\u003c/div\u003e\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003ee1ac392\u003c/code\u003e y \u003ccode\u003e48c0f46\u003c/code\u003e son los commits del README que agregaron una sección dedicada explicando esto a los usuarios, para que nadie más tenga que aprenderlo como lo hice yo.\u003c/p\u003e\n\u003cp\u003eCon lo que me quedo son los siete efectos que el firmware todavía habla de manera nativa: \u003ccode\u003estatic\u003c/code\u003e, \u003ccode\u003ebreathing\u003c/code\u003e, \u003ccode\u003ewave\u003c/code\u003e, \u003ccode\u003efade\u003c/code\u003e, \u003ccode\u003emarquee\u003c/code\u003e, \u003ccode\u003eflash\u003c/code\u003e, \u003ccode\u003eraindrop\u003c/code\u003e. Son suficientes. Son hermosos. Antes de \u003ccode\u003e68dbdae\u003c/code\u003e, estaban acompañados por \u003ccode\u003eripple\u003c/code\u003e y \u003ccode\u003eneon\u003c/code\u003e y \u003ccode\u003erainbow\u003c/code\u003e, y los echo de menos.\u003c/p\u003e\n\u003ch2 id=\"lecciones\" class=\"headerLink\"\u003e\n    \u003ca href=\"#lecciones\" class=\"header-mark\" aria-label=\"Header mark for 'Lecciones'\"\u003e\u003c/a\u003e9 Lecciones\u003c/h2\u003e\u003cp\u003eSi fuera a hacer esto de nuevo, llevaría un puñado de lecciones conmigo. No muchas. Estos proyectos pequeños enseñan sus lecciones en pequeñas dosis.\u003c/p\u003e\n\u003ch3 id=\"la-detección-es-frágil-entre-generaciones\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-detecci%c3%b3n-es-fr%c3%a1gil-entre-generaciones\" class=\"header-mark\" aria-label=\"Header mark for 'La detección es frágil entre generaciones'\"\u003e\u003c/a\u003e9.1 La detección es frágil entre generaciones\u003c/h3\u003e\u003cp\u003eCada generación de Gigabyte Aero tiene sus propias peculiaridades. El vendor ID se mantiene en \u003ccode\u003e0x0414\u003c/code\u003e, pero el product ID cambia, el report descriptor cambia, la tabla de efectos cambia. Mi scanner usa la usage page \u003ccode\u003e0xFF01\u003c/code\u003e como ancla precisamente porque esa es la señal más estable — si un modelo futuro cambia los product IDs, mientras el vendor siga usando la familia del controlador ITE, mi glob de sysfs y sniff de descriptor probablemente lo encontrarán. La alternativa (hardcodear PIDs) se habría podrido dentro de un año.\u003c/p\u003e\n\u003ch3 id=\"el-fallback-ayatanalegacy-es-el-pulido-que-importa\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-fallback-ayatanalegacy-es-el-pulido-que-importa\" class=\"header-mark\" aria-label=\"Header mark for 'El fallback Ayatana/legacy es el pulido que importa'\"\u003e\u003c/a\u003e9.2 El fallback Ayatana/legacy es el pulido que importa\u003c/h3\u003e\u003cp\u003eLas 18 líneas de heredoc en \u003ccode\u003ebuild-binaries.sh:14-24\u003c/code\u003e no son glamorosas. Nadie escribirá un post sobre el \u003ccode\u003etry/except ValueError\u003c/code\u003e en \u003ccode\u003etray.py:12-17\u003c/code\u003e. Pero juntos son la diferencia entre \u0026ldquo;usuarios Linux con la distro correcta\u0026rdquo; y \u0026ldquo;usuarios Linux\u0026rdquo;. Si vas a distribuir una utilidad de escritorio en 2026, haces el baile de Ayatana. Es lo opuesto al overengineering: son diez minutos de trabajo que silenciosamente cubren tres años de fragmentación de distros.\u003c/p\u003e\n\u003ch3 id=\"un-codebase-de-593-loc-puede-llegar-al-80-de-cobertura--si-la-capa-de-hardware-está-mockeada\" class=\"headerLink\"\u003e\n    \u003ca href=\"#un-codebase-de-593-loc-puede-llegar-al-80-de-cobertura--si-la-capa-de-hardware-est%c3%a1-mockeada\" class=\"header-mark\" aria-label=\"Header mark for 'Un codebase de 593 LoC puede llegar al 80% de cobertura — si la capa de hardware está mockeada'\"\u003e\u003c/a\u003e9.3 Un codebase de 593 LoC puede llegar al 80% de cobertura — si la capa de hardware está mockeada\u003c/h3\u003e\u003cp\u003eLa mayor ganancia para los tests fue comprometerse con un \u003ccode\u003eAeroKeyboardRGB\u003c/code\u003e falso en \u003ccode\u003etests/conftest.py\u003c/code\u003e. Una vez que el hardware era mockeable, cada subcomando del CLI se convirtió en una función pura de sus inputs: argparse adentro, interacciones de dispositivo falsas afuera. El CI corre sin un teclado conectado al runner. Los tests corren en fracción de segundo. Y los commits que subieron la cobertura (\u003ccode\u003e74646e6\u003c/code\u003e, \u003ccode\u003ecaea8df\u003c/code\u003e) agregaron menos de 100 líneas de test entre los dos. Codebase pequeño, test suite pequeño, cobertura real. No se puede pedir más.\u003c/p\u003e\n\u003ch3 id=\"confía-en-el-checksum-no-en-tu-intuición\" class=\"headerLink\"\u003e\n    \u003ca href=\"#conf%c3%ada-en-el-checksum-no-en-tu-intuici%c3%b3n\" class=\"header-mark\" aria-label=\"Header mark for 'Confía en el checksum, no en tu intuición'\"\u003e\u003c/a\u003e9.4 Confía en el checksum, no en tu intuición\u003c/h3\u003e\u003cp\u003eEsta es la que me tatuaría si pudiera. Cuando estás haciendo reverse engineering de un protocolo binario y el algoritmo obvio no coincide con la captura, no te pongas creativo. No asumas que el vendor está usando una variante CRC conocida. Prueba las cosas tontas primero, luego prueba variantes ligeramente desviadas de las cosas tontas, luego siéntate con las matemáticas hasta que haga clic. El checksum del teclado Gigabyte es \u0026ldquo;hacer que el paquete totalice \u003ccode\u003e0xFF\u003c/code\u003e\u0026rdquo;, lo que nadie llamaría estado del arte, pero es lo que el firmware realmente computa, y en el reverse engineering, \u0026ldquo;lo que el firmware realmente computa\u0026rdquo; le gana a \u0026ldquo;lo que tú crees que debería computar\u0026rdquo; siempre.\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eMi teclado brilla ahora. Brilla cuando inicio sesión, porque \u003ccode\u003einstall.sh\u003c/code\u003e dejó un archivo autostart en \u003ccode\u003e~/.config/autostart/\u003c/code\u003e. Recuerda su último color después de que lo apago y enciendo, porque \u003ccode\u003ecli.py\u003c/code\u003e y \u003ccode\u003etray.py\u003c/code\u003e ambos escriben al mismo pequeño archivo JSON en \u003ccode\u003e~/.cache/\u003c/code\u003e. Funciona sin \u003ccode\u003esudo\u003c/code\u003e, gracias a una sola cláusula \u003ccode\u003eTAG+=\u0026quot;uaccess\u0026quot;\u003c/code\u003e en una regla udev que configuré una vez y nunca volveré a pensar en ella.\u003c/p\u003e\n\u003cp\u003eY en algún lugar debajo de todo eso, un paquete de 9 bytes — ocho bytes de intención, un byte de checksum que no es XOR — está siendo entregado desde Python a \u003ccode\u003efcntl.ioctl\u003c/code\u003e al driver hidraw de Linux a un controlador ITE-829X que finalmente, por primera vez desde que borré Windows, entiende lo que quiero.\u003c/p\u003e\n\u003cp\u003eQue era, desde siempre, simplemente que el teclado dijera algo de vuelta.\u003c/p\u003e\n",
        "language": "es"
    },
    {
        "title" : "Las Crónicas de Gocracker: Una microVM en Go, de Hack de Fin de Semana a Sandbox de Producción",
        "date_published" : "2026-05-19T00:00:00Z",
        "date_modified" : "2026-05-19T00:00:00Z",
        "id" : "https://misael.org/es/gocracker-part-1-foundation/",
        "url" : "https://misael.org/es/gocracker-part-1-foundation/",
        "summary": "Toda la historia de gocracker en una sola lectura: por qué Go, cómo funciona KVM en realidad, los bugs de concurrencia que casi la mataron, el trabajo de rendimiento que bajó el arranque en frío a menos de 200 ms, y la capa de producción encima que casi se mata silenciosamente.",
        "content_html" : "\u003ch2 id=\"firecracker-es-genial-escribí-uno-de-todos-modos\" class=\"headerLink\"\u003e\n    \u003ca href=\"#firecracker-es-genial-escrib%c3%ad-uno-de-todos-modos\" class=\"header-mark\" aria-label=\"Header mark for 'Firecracker es genial. Escribí uno de todos modos.'\"\u003e\u003c/a\u003e1 Firecracker es genial. Escribí uno de todos modos.\u003c/h2\u003e\u003cp\u003eAmo Firecracker. Le pondría un sticker pequeñito a mi laptop si AWS lo vendiera. La idea — arrancar una VM Linux real en milisegundos, quitar todo dispositivo que nadie necesita, blindar las syscalls, y listo — es una de las ideas más elegantes de ingeniería de sistemas de la última década. Convirtió \u0026ldquo;contenedor pero de verdad aislado\u0026rdquo; de meme a producto.\u003c/p\u003e\n\u003cp\u003eAsí que naturalmente, un fin de semana, decidí reemplazarla.\u003c/p\u003e\n\u003cp\u003eNo porque sea mala. Porque cada vez que quería correr \u003ccode\u003eubuntu:22.04\u003c/code\u003e como microVM había seis pasos manuales entre yo y un prompt: bajarme la imagen, extraer el rootfs, armar un disco ext4, generar un initrd, escribir un JSON de cuarenta líneas, crear un TAP, pelearme con iptables, \u003cem\u003ey entonces\u003c/em\u003e llamar a la API. Firecracker es un especialista de Rust. Arranca VMs maravillosamente y asume que el resto es tu problema. Ese es el diseño correcto para AWS, donde cada paso es otro servicio especialista. Para alguien corriendo cosas en una laptop, es una catástrofe de pestañas.\u003c/p\u003e\n\u003cp\u003eYo quería un generalista. \u003ccode\u003egocracker run --image ubuntu:22.04\u003c/code\u003e, y quería que ya hubiera hecho todo lo anterior antes de que yo terminara de presionar Enter. Así que escribí uno. En Go. Porque el lenguaje donde \u0026ldquo;baterías incluidas\u0026rdquo; es la cultura es el lugar obvio para construir una microVM con baterías incluidas.\u003c/p\u003e\n\u003cp\u003eSi Firecracker es CGI — una interfaz desnuda y principista que cualquier componente inteligente puede manejar — entonces gocracker es FastCGI: la misma interfaz con un proceso cómodo y de larga vida envuelto alrededor que asume que eres un desarrollador en una laptop y quieres avanzar con lo tuyo.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-1\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    FIRECRACKER                gocracker\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    (especialista en Rust)     (generalista en Go)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    solo un VMM                VMM + OCI + initrd + TAP + Compose\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    tú traes:                  tú traes:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - rootfs                   - un comando\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - kernel                   - un kernel\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - initrd\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - tap + NAT\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - JSON config\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      - 40 líneas de bash\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    hermoso.                   soy flojo y me gusta así.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003ch2 id=\"qué-es-kvm-en-realidad\" class=\"headerLink\"\u003e\n    \u003ca href=\"#qu%c3%a9-es-kvm-en-realidad\" class=\"header-mark\" aria-label=\"Header mark for 'Qué es KVM en realidad'\"\u003e\u003c/a\u003e2 Qué es KVM en realidad\u003c/h2\u003e\u003cp\u003eKVM — la Kernel-based Virtual Machine — es un dispositivo de caracteres, \u003ccode\u003e/dev/kvm\u003c/code\u003e, que expone la virtualización por hardware como ioctls. Eso es todo. Abres un archivo, llamas ioctls sobre él, y un CPU empieza a correr código por ti dentro de un sandbox de hardware.\u003c/p\u003e\n\u003cp\u003eMe gusta describir los ioctls de KVM como el ensamblador de las VMs. Bajo nivel, ortogonal, cada uno hace exactamente una cosa, y tú eres quien los compone en algo útil. No hay scheduler, no hay modelo de dispositivos. Hay un alfabeto pequeño de primitivas — crear la VM, crear el vCPU, mapear memoria, correr, obtener registros, asignar registros — y tú eres quien las convierte en una computadora.\u003c/p\u003e\n\u003cp\u003eEl corazón de cualquier VMM es un loop. Llamas \u003ccode\u003erun\u003c/code\u003e sobre el vCPU. El hilo del host se bloquea. El guest corre. Eventualmente el guest hace algo que necesita la atención del host — toca un registro MMIO, golpea un puerto de I/O, recibe una interrupción, se apaga — y KVM devuelve el control. Ese retorno se llama VMexit. Todo lo demás — dispositivos, bootloaders, motores de snapshots — es azúcar alrededor de este loop.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-2\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            HOST                              GUEST\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +---------------------+            +-------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | ioctl(KVM_RUN)      | ---------\u0026gt; | instr. del guest  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                     |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                     | \u0026lt;--------- | VMexit            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    | dispatch(exit_reason)            | (MMIO / IO / IRQ) |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   handle_mmio()     |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   handle_io()       |            |                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   inject_irq()      | ---------\u0026gt; | resume            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +---------------------+            +-------------------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eTodo el binding hacia KVM vive en un solo archivo, y el número central es exactamente cinco dígitos hex:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-3\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// internal/kvm/kvm.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"nx\"\u003ekvmRun\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mh\"\u003e0xAE80\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCuando userspace llama \u003ccode\u003eioctl(vcpu_fd, 0xAE80, 0)\u003c/code\u003e, el kernel le transfiere el control al CPU del guest. El hilo del host se bloquea. El guest corre. Ese es el corazón de cualquier VMM escrito jamás.\u003c/p\u003e\n\u003ch2 id=\"una-goroutine-por-vcpu-es-todo-el-modelo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#una-goroutine-por-vcpu-es-todo-el-modelo\" class=\"header-mark\" aria-label=\"Header mark for 'Una goroutine por vCPU es todo el modelo'\"\u003e\u003c/a\u003e3 Una goroutine por vCPU es todo el modelo\u003c/h2\u003e\u003cp\u003eAquí está la alegría de escribir un VMM en Go, y la cosa que hizo que el fin de semana se sintiera como un fin de semana. Un CPU virtual es un file descriptor que se bloquea hasta que el guest sale. Si quieres dos vCPUs, quieres dos hilos del host. Si quieres dieciséis, quieres dieciséis hilos.\u003c/p\u003e\n\u003cp\u003eGo tiene una palabra para \u0026ldquo;una cosa que parece un hilo y se bloquea en un syscall\u0026rdquo;. La salvedad molesta es que el scheduler de Go normalmente mueve goroutines entre hilos del SO cuando le da la gana, y a KVM eso \u003cem\u003ede verdad\u003c/em\u003e no le gusta. La solución son tres palabras:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-4\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eruntime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eLockOSThread\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEso es todo. Esa es la historia de multiproceso. Cada vCPU recibe una goroutine. Cada goroutine bloquea su hilo y corre el loop de exits. El runtime se encarga del resto. No hay pool de hilos que escribir. Ni siquiera una primitiva de sincronización — los canales que ya tienes en la caja de herramientas funcionan.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-5\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            goroutine por vCPU (LockOSThread)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +--------------------------------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |                                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   for {                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       err := ioctl(vcpu_fd, KVM_RUN, 0)          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       if err == EINTR || err == EAGAIN {         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |           continue          // transitorio       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       }                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       switch run.exit_reason {                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case IO:        handleIO(run.io)           |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case MMIO:      handleMMIO(run.mmio)       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case SHUTDOWN:  return                     |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case INTR:      continue                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       case HLT:       waitForIRQ(); continue     |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |       }                                          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    |   }                                              |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    +--------------------------------------------------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCuando un guest real arranca, este loop corre millones de veces por segundo. La mayoría de los exits son rápidos — un notify de una cola virtio, una escritura al UART, un tick del timer — y el loop vuelve a \u003ccode\u003eKVM_RUN\u003c/code\u003e antes de que nadie note. El arte está en hacer barato cada case del switch.\u003c/p\u003e\n\u003cp\u003eAl final del fin de semana tenía un prompt verde, una VM corriendo, y un arranque en frío bajo 400 ms. Le pondría un sticker de Rust a mi laptop también. Pero Go me dio goroutines que hicieron que \u0026ldquo;un hilo por vCPU\u0026rdquo; fuera una funcionalidad de tres palabras, un servidor HTTP en unas doscientas líneas, JSON config que Just Works, y el ecosistema de librerías OCI más maduro de cualquier lenguaje. Cross-compilar a ARM64 son dos variables de entorno. Las peleas que tuve con Go fueron reales pero acotadas; la conveniencia se compuso por meses.\u003c/p\u003e\n\u003ch2 id=\"las-siguientes-tres-semanas\" class=\"headerLink\"\u003e\n    \u003ca href=\"#las-siguientes-tres-semanas\" class=\"header-mark\" aria-label=\"Header mark for 'Las siguientes tres semanas'\"\u003e\u003c/a\u003e4 Las siguientes tres semanas\u003c/h2\u003e\u003cp\u003eLa VM arrancó. Un comando. La vida era hermosa. Y luego la misma propiedad de \u0026ldquo;goroutines baratas\u0026rdquo; que hizo agradable el primer fin de semana empezó a cobrar intereses.\u003c/p\u003e\n\u003cp\u003eCada bug empezó como un síntoma distinto y terminó en el mismo lugar: dos goroutines discutiendo sobre quién era dueño de un pedazo de estado del kernel. El jailer dejaba los zapatos en la puerta — bind mounts que sobrevivían a un sandbox caído, envenenando la siguiente VM. Un \u003ccode\u003eclose\u003c/code\u003e desnudo sobre un file descriptor de vsock compartido resultó ser una operación de refcount, no un mensaje de protocolo; el host se bloqueaba para siempre hasta que el usuario, de pura desesperación, presionaba una tecla, lo cual desbloqueaba una goroutine de fondo, soltaba la última referencia, y finalmente mandaba el paquete de shutdown. Un panic durante el cleanup dejaba la terminal en modo raw. Cada release del runtime de Go quería un syscall más que el filtro seccomp no conocía, y \u0026ldquo;Bad system call\u0026rdquo; se convirtió en la banda sonora de cada semana de release.\u003c/p\u003e\n\u003cp\u003eQuince goroutines bajo un gabán siguen siendo quince goroutines. La lección no fue tanto sobre un bug específico sino sobre la forma de todos: en una microVM, el kernel del host conserva estado sobre tu VM, y si no lo limpias en el camino feliz, nadie lo va a limpiar en el camino triste. Después del quinto race en tres días, el arreglo estructural no fue \u0026ldquo;ten más cuidado\u0026rdquo; — yo ya estaba teniendo cuidado — sino prender \u003ccode\u003e-race\u003c/code\u003e en CI y escribir pruebas que deliberadamente hacían competir a productor y consumidor, para que las race conditions aparecieran de forma reproducible. El race detector es la opción más importante que puedes prender en un proyecto Go. Es de diez a cien veces más lento, y vale cada ciclo.\u003c/p\u003e\n\u003cp\u003eCuando CI dejó de fallar de forma intermitente, finalmente tuve una VM en la que confiaba lo suficiente como para medirla.\u003c/p\u003e\n\u003ch2 id=\"estaba-mirando-la-caja-equivocada\" class=\"headerLink\"\u003e\n    \u003ca href=\"#estaba-mirando-la-caja-equivocada\" class=\"header-mark\" aria-label=\"Header mark for 'Estaba mirando la caja equivocada'\"\u003e\u003c/a\u003e5 Estaba mirando la caja equivocada\u003c/h2\u003e\u003cp\u003ePor unas dos semanas creí que gocracker tenía un problema 2× contra Firecracker. El campo \u003ccode\u003eduration\u003c/code\u003e que estaba imprimiendo pasaba de ~30 ms sin el jailer a ~55 ms con él. Dos veces peor. Fork-exec era el villano. El jailer era el villano. Siete REST PUTs eran el villano.\u003c/p\u003e\n\u003cp\u003eMira el \u003cem\u003ereloj de pared\u003c/em\u003e. Unos 860 ms vs unos 880 ms. Unos veinte milisegundos de diferencia, sobre una base de 860 ms. Eso no es 2×; es alrededor de 2%, y 2% es ruido. El \u0026ldquo;2×\u0026rdquo; estaba enteramente en un campo \u003ccode\u003eduration\u003c/code\u003e que medía cosas distintas en los dos caminos de código. El camino in-process medía \u003ccode\u003evmm.New\u003c/code\u003e puro. El camino de worker medía fork-exec, setup del jailer, chroot, siete REST PUTs, \u003cem\u003emás\u003c/em\u003e \u003ccode\u003evmm.New\u003c/code\u003e. Los dos paraban el reloj antes de que el kernel del guest hubiera impreso un solo byte. Ninguno medía tiempo hasta guest útil. Ninguno era comparable con el otro.\u003c/p\u003e\n\u003cp\u003eDividir la medición en cuatro fases honestas — orquestación, setup del VMM, arranque, primera salida del guest — hizo evaporar el 2×:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-6\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// pkg/vmm/timings.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// BootTimings es el desglose por fase de cuánto tomó traer a\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// la vida a una microVM.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - Orchestration:    trabajo en el host *antes* de arrancar el kernel\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - VMMSetup:         tiempo dentro de vmm.New() — KVM_CREATE_VM, memoria...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - Start:            KVM_RUN arranca en las goroutines de vCPU\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//   - GuestFirstOutput: primer byte que el guest imprime en la UART\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl jailer costaba unos 30 ms en orquestación, encima de unos ~300 ms de arranque del kernel del guest que ambos caminos compartían. Un impuesto honesto de orquestación de ~10%, no una penalización de 2×.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-7\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   antes del desglose                   después del desglose\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   (un solo número engañoso)         (cuatro números honestos)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   +-----------------------+         +--------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   | duration = ~55ms      |         | orchestration ~30ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   | (en runViaWorker esto |         | vmm_setup      ~8ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  incluye el jailer,   |         | start          ~2ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  fork, unos REST PUTs,|         | guest_first  ~320ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  después vmm.New,     |         | total        ~360ms|\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   |  después start)       |         +--------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   +-----------------------+            |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                        v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u0026#34;2x más lento\u0026#34;            \u0026#34;Estaba mirando la\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   caja equivocada.\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eY entonces algo más cayó del análisis: unos trescientos de esos milisegundos eran \u003cem\u003eLinux arrancando dentro de la VM\u003c/em\u003e. Si quería una VM más rápida, mi código no era el problema. Mi kernel sí.\u003c/p\u003e\n\u003cp\u003eDebuggear performance es auditar gastos. Te quedas viendo las líneas. Te quedas viendo hasta que encuentras la que dice \u0026ldquo;comida de negocios $480\u0026rdquo; y el restaurante resulta ser un Costco. La trampa nunca está donde la esperas.\u003c/p\u003e\n\u003ch2 id=\"el-kernel-era-el-problema\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-kernel-era-el-problema\" class=\"header-mark\" aria-label=\"Header mark for 'El kernel era el problema'\"\u003e\u003c/a\u003e6 El kernel era el problema\u003c/h2\u003e\u003cp\u003eForké el kernel del guest en dos perfiles: el genérico que envío por defecto y uno \u0026ldquo;minimal\u0026rdquo; que arranca cualquier cosa que una VM con virtio y nada más nunca va a necesitar. Se fue ACPI NUMA. Se fue hibernación. Se fue todo el subsistema de USB. Power management, profiling, SCSI, loop devices, XFS, NFS — todo fuera. Virtio quedó. ext4 quedó. kvm-clock quedó. El kernel se encogió un 12% y el arranque bajó un pedazo solo por correr menos initcalls.\u003c/p\u003e\n\u003cp\u003eDespués vino el cambio pequeño que importó más que cualquiera de los otros. Agregué un parámetro a la línea de comandos del kernel: \u003ccode\u003eloglevel=4\u003c/code\u003e. Le dice al kernel \u0026ldquo;solo imprime warnings y arriba en la consola; todo lo demás sigue yendo al ring buffer y se puede ver con dmesg\u0026rdquo;. El grueso del output de arranque dejó de ir a la UART emulada.\u003c/p\u003e\n\u003cp\u003eResulta que una UART virtualizada es cara por byte. Cada byte que el kernel escribe en la consola serial es un MMIO exit a userspace, que es un context switch, que son unos microsegundos perdidos. Multiplica por unos miles de bytes de arranque y el boot estaba dominado por \u003cem\u003eimprimir\u003c/em\u003e. Silenciar la consola le quitó unos 130 ms al arranque.\u003c/p\u003e\n\u003cp\u003eUna línea.\u003c/p\u003e\n\u003cp\u003eLas victorias chicas siguieron el mismo tema: dejar de pelearse con el kernel y pedirle que haga su trabajo. Cachear un probe de discard cuya respuesta solo depende del filesystem del host, no del guest. Rutear las interrupciones de x86 a través de \u003ccode\u003eeventfd + IRQFD\u003c/code\u003e en lugar de un ioctl por aserción, igual que ya hacía el backend de ARM64. Apagar el garbage collector de Go en el subproceso del VMM:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-8\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// cmd/gocracker-vmm/main.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;runtime/debug\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"nf\"\u003einit\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003edebug\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSetGCPercent\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// proceso corto-vivido; el SO recupera memoria\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl proceso no necesita un GC; corre felizmente hasta el final y el SO recupera la memoria. Unos milisegundos aquí, unos allá. Ninguna ingeniosa por sí sola. Acumulativas.\u003c/p\u003e\n\u003cp\u003eApilando las victorias como gráfico de barras a la escala de las mediciones reales:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-9\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e kernel estándar:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orq==][vmm][======guest_first_output: ~305ms======]    ~390ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ~70ms   ~15  ~305\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              esto es Linux arrancando.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e kernel mínimo:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orq==][vmm][=====guest_first_output: ~280ms=====]      ~365ms (-25)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              menos initcalls.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e mínimo + loglevel=4:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   [==orq==][vmm][guest_first_output: ~170ms]                 ~250ms (-115)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              el 80% del costo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                              era *imprimir*.\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eDespués de todo eso: arranque en frío entre 150 y 170 ms. Unos 45 ms detrás de Firecracker, bajando de bastante más. Un gap Go-vs-Rust medible en milisegundos, sobre un arranque dominado por un kernel Linux ajeno que yo no controlo. Ese es el lugar correcto donde terminar. Si alguien te dice que su microVM está unas decenas de milisegundos detrás de Firecracker, asientes con cortesía; si te dicen que está 2× detrás, tienes preguntas.\u003c/p\u003e\n\u003ch2 id=\"una-vez-que-el-arranque-en-frío-se-hizo-chico-el-camino-caliente-se-volvió-vergonzoso\" class=\"headerLink\"\u003e\n    \u003ca href=\"#una-vez-que-el-arranque-en-fr%c3%ado-se-hizo-chico-el-camino-caliente-se-volvi%c3%b3-vergonzoso\" class=\"header-mark\" aria-label=\"Header mark for 'Una vez que el arranque en frío se hizo chico, el camino caliente se volvió vergonzoso'\"\u003e\u003c/a\u003e7 Una vez que el arranque en frío se hizo chico, el camino caliente se volvió vergonzoso\u003c/h2\u003e\u003cp\u003eLa restauración de snapshots solía ser el camino rápido. 80 ms de restore encima de 400 ms de arranque en frío es un error de redondeo. 80 ms de restore encima de 170 ms de arranque en frío es la mitad del presupuesto.\u003c/p\u003e\n\u003cp\u003eEl restore viejo hacía la cosa obvia: reservar un mmap anónimo fresco de 128 MiB para la RAM del guest, leer el archivo de snapshot completo a un \u003ccode\u003e[]byte\u003c/code\u003e de Go, memcpy todo a su posición. Los pasos uno al tres tomaban unos 80 ms, exactamente como esperarías si alguna vez has tenido que memcpy 128 MiB en cada request.\u003c/p\u003e\n\u003cp\u003eLuego vino la pregunta: ¿y si simplemente no lo copio?\u003c/p\u003e\n\u003cp\u003eLinux tiene una bandera llamada \u003ccode\u003eMAP_PRIVATE\u003c/code\u003e. Cuando mapeas un archivo con ella, el kernel no hace I/O por adelantado. Arma una entrada de tabla de páginas que dice \u0026ldquo;si userspace toca esta página, falla al kernel, léela del archivo, mapéala. Si userspace \u003cem\u003eescribe\u003c/em\u003e en la página, falla, copy-on-write a una página anónima privada, y redirige el mapping a la copia\u0026rdquo;. El archivo en sí nunca se modifica.\u003c/p\u003e\n\u003cp\u003eLa analogía de Netflix es la que sigo usando. Netflix no descarga primero la película entera a tu dispositivo y luego la reproduce. Empieza a reproducirla de inmediato y pide cada minuto mientras lo ves. Si te adelantas, esas partes nunca se descargan. Pagas por minuto visto, no por película seleccionada. \u003ccode\u003eMAP_PRIVATE\u003c/code\u003e es ese patrón para la RAM del guest.\u003c/p\u003e\n\u003cp\u003eEl nuevo camino mapea el snapshot directamente sobre la región de memoria del guest:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-10\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emem\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMmap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ef\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eFd\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ememSize\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePROT_READ\u003c/span\u003e\u003cspan class=\"p\"\u003e|\u003c/span\u003e\u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ePROT_WRITE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMAP_PRIVATE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003e_\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eMadvise\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003emem\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003eunix\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMADV_HUGEPAGE\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLas páginas que el guest nunca toca, nunca se cargan. Las que lee pero no escribe quedan compartidas con el page cache. Las que escribe van a copias COW privadas y el archivo del snapshot queda intacto.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-11\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ANTES: copia eager\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------+    +----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |  mem.bin       |--\u0026gt;|   os.ReadFile  |---\u0026gt;| copy(ram, mem) |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |    128 MiB     |   |  lee 128 MiB   |    | memcpy 128 MiB |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------+    +----------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                      |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                                      v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                            ~80ms antes de este punto\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  DESPUÉS: mmap lazy (MAP_PRIVATE)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |  mem.bin       |\u0026lt;--| mmap(fd, PRIVATE)          |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |    128 MiB     |   | solo arma la page table    |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +----------------+   +----------------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        el guest toca la página N\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        minor fault (-\u0026gt; page cache)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                        el kernel la mapea sobre la marcha\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                   v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                         ~20ms a \u0026#34;corriendo\u0026#34;\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl baile completo del page fault por debajo se ve así:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-12\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  vCPU guest                  host kernel (KVM + mm)        snapshot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | read P  |---(EPT miss)---\u0026gt;| PTE not-present, PRIVATE     | en    |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; minor fault                | disco |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; lookup en page cache       |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 |    (o leer de disco)      \u0026lt;---+       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; instalar PTE de lectura    |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |\u0026lt;-----(resume)---|                               |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  después, el guest escribe en P:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | write P |---(EPT miss)---\u0026gt;| PTE de solo lectura           |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; COW fault                  |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; alocar página anon         |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; copiar desde page cache    |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 | -\u0026gt; instalar PTE de escritura  |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |                 |    (¡snapshot intacto!)       |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  |         |\u0026lt;-----(resume)---|                               |       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +---------+                                                 +-------+\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eCada paso ahí es lo que Linux ya hace para cualquier mmap respaldado por archivo. No tuve que escribir una sola línea de manejo de page faults. Solo dejar de pelearme con el kernel y pedirle que haga su trabajo.\u003c/p\u003e\n\u003cp\u003eSobre un snapshot de Alpine de 128 MiB, el restore bajó de ~80 ms a unos 20 ms. La resumición desde snapshot quedó de pronto varias veces más rápida que el arranque en frío. (Un caveat importante: no borres el archivo del snapshot mientras hay VMs corriendo sobre él. Pregúntame cómo lo sé.)\u003c/p\u003e\n\u003ch2 id=\"mise-en-place-para-vms\" class=\"headerLink\"\u003e\n    \u003ca href=\"#mise-en-place-para-vms\" class=\"header-mark\" aria-label=\"Header mark for 'Mise en place para VMs'\"\u003e\u003c/a\u003e8 Mise en place para VMs\u003c/h2\u003e\u003cp\u003eEntras a un restaurante decente al mediodía. Pides el bistec con papas. Llega en seis minutos. El bistec solo es una cocción de seis minutos, calculando generoso. Las papas son doce. La bearnesa son quince. ¿Cómo lo hizo la cocina en seis minutos?\u003c/p\u003e\n\u003cp\u003eMise en place. Las papas están pre-cocidas y escurridas antes de que llegaras. La bearnesa está emulsionada y mantenida tibia. El plato salió del calentador en el momento que el pedido llegó al pase. Lo único que la cocina hace \u003cem\u003edespués de tu pedido\u003c/em\u003e es el sellado final.\u003c/p\u003e\n\u003cp\u003eUna warm pool es mise en place para VMs. El proveedor de sandboxes líder en el benchmark público estaba alrededor de 100 ms — exactamente lo que esperarías de saltarte el restore por completo teniendo una VM ya corriendo, pausada, esperando que alguien diga ya. Si el líder gana pre-cocinando, deja de optimizar la estufa.\u003c/p\u003e\n\u003cp\u003eLa warm pool terminó siendo tres decisiones de diseño, cada una resultado de una discusión con un mal día hipotético.\u003c/p\u003e\n\u003cp\u003ePrimero, \u003ccode\u003eAcquire\u003c/code\u003e no bloquea. La tentación con APIs de pool es hacer que \u003ccode\u003eAcquire\u003c/code\u003e bloquee hasta que haya un worker disponible. Ese \u0026ldquo;siempre dale un worker al usuario\u0026rdquo; se siente seguro. No lo es. Si la pool está vacía, algo ya salió mal, y hacer esperar al usuario por un restore fresco es estrictamente peor que caer al camino de arranque en frío que ya funciona. La pool es de mejor esfuerzo. Un miss nunca debe hacer al usuario más lento que la línea base.\u003c/p\u003e\n\u003cp\u003eSegundo, liberar a un worker \u003cem\u003elo mata\u003c/em\u003e. Toda librería de pooling eventualmente quiere reciclar un worker de regreso a la pool. En un mundo multi-tenant, el worker que acaba de terminar una request tocó lo que el último tenant le pidió. Entregárselo al siguiente tenant es un hueco de aislamiento, y que nadie lo haya explotado todavía no es un argumento de seguridad. Cada \u003ccode\u003eAcquire\u003c/code\u003e devuelve un proceso que nunca ha servido una request. El refill ocurre en segundo plano, así que el siguiente que llama no paga nada. La pool siempre se está moviendo. Nunca se reutiliza.\u003c/p\u003e\n\u003cp\u003eTercero, el refill es asíncrono, con tope, y race-safe. Una ráfaga de pedidos de refill para el mismo template no debe estampedar en diez spawns paralelos; un spawn de refill que compite contra el shutdown debe limpiar después de sí mismo; y un reloj debe ser inyectable para que las pruebas de staleness sean determinísticas. Nada de esto es ingenioso. Son los invariantes que lamentas haberte saltado la primera vez que la pool corre en producción.\u003c/p\u003e\n\u003cp\u003eEl flujo completo con caché y pool conectados:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-13\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  llega una request\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  warmcache.Lookup(key)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         +-- miss --\u0026gt; arranque en frío (~250ms)  \u0026lt;-- línea base\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  hit, snapshotDir=S\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  pool.Acquire(key, S)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         +-- vacío --\u0026gt; restore_directo (~20ms)   \u0026lt;-- igual mejor\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  worker tibio listo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  worker.Resume (~3ms)  \u0026lt;-- camino más rápido\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  sirve la request\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  pool.Release(w)  --\u0026gt; worker.Close()\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  EnsureRefill en el fondo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  arrancar reemplazo (~20ms fuera del camino caliente)\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa superficie de la API de la pool es pequeña a propósito:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-14\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// pkg/warmpool/pool.go\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e \u003cspan class=\"kd\"\u003einterface\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eID\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003eClose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eAcquire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esnapshotDir\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kt\"\u003eerror\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eRelease\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ew\u003c/span\u003e \u003cspan class=\"nx\"\u003eWorker\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ep\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003ePool\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003eEnsureRefill\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003ekey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003esnapshotDir\u003c/span\u003e \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEn el camino caliente: el worker tibio ya tiene la RAM del guest mapeada, el estado del vCPU cargado, la VM pausada. \u003ccode\u003eAcquire\u003c/code\u003e devuelve. Un solo ioctl de resume lo voltea de pausado a corriendo. Tres milisegundos después, el guest ya ha dicho hola. Mise en place.\u003c/p\u003e\n\u003ch2 id=\"nueve-sandboxes-quemando-una-cpu-por-nada\" class=\"headerLink\"\u003e\n    \u003ca href=\"#nueve-sandboxes-quemando-una-cpu-por-nada\" class=\"header-mark\" aria-label=\"Header mark for 'Nueve sandboxes quemando una CPU por nada'\"\u003e\u003c/a\u003e9 Nueve sandboxes quemando una CPU por nada\u003c/h2\u003e\u003cp\u003eNueve sandboxes corriendo, pausados, ociosos. Sin tráfico. Sin sesiones de exec. Sin HTTP. Sentados en un prompt de shell dentro de una warm pool, esperando a que alguien les pida trabajar. \u003ccode\u003etop\u003c/code\u003e mostraba al host en 46% de un core.\u003c/p\u003e\n\u003cp\u003eCuarenta y seis por ciento para mantener vivos nueve guests Linux ociosos. Unos cinco por ciento de un core por VM ociosa. Una caja Linux física ociosa usa alrededor de 0.1% de un core en hardware moderno. Un guest virtualizado bien hecho debería ser \u003cem\u003emás barato\u003c/em\u003e, no cincuenta veces más caro.\u003c/p\u003e\n\u003cp\u003eAlgo estaba muy mal.\u003c/p\u003e\n\u003cp\u003eLa forma limpia de ver qué está haciendo realmente un hilo de vCPU es muestrearlo. Unos segundos de \u003ccode\u003eperf\u003c/code\u003e devolvieron un stack trace inequívoco: entrar a KVM, salir casi de inmediato, dormir un milisegundo, volver a entrar. Una y otra vez, mil veces por segundo, en cada hilo de vCPU, en paralelo. Nueve hilos haciendo esto a la vez eran exactamente el 370% de un core que el host reportaba.\u003c/p\u003e\n\u003cp\u003eLa causa era una cobertura. En lo profundo del loop del vCPU, el exit \u003ccode\u003eHLT\u003c/code\u003e estaba siendo \u0026ldquo;manejado\u0026rdquo; con un sleep de un milisegundo:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-15\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"nx\"\u003eKVM_EXIT_HLT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// El guest está ocioso. No spin; dale un respiro.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003eSleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etime\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eMillisecond\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEl sleep era razonable en un mundo donde el VMM tiene el controlador de interrupciones en userspace. gocracker no. gocracker usa IRQCHIP in-kernel — el default correcto para casi cualquier workload — donde KVM debería mantener el hilo \u003cem\u003edentro\u003c/em\u003e del ioctl hasta que la siguiente interrupción dispare, sin exit alguno. El sleep era código muerto que sobrevivió a un cambio de diseño que nadie cuestionó.\u003c/p\u003e\n\u003cp\u003eEl fix fue una eliminación:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-16\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e \u003cspan class=\"nx\"\u003eKVM_EXIT_HLT\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e// No-op. IRQCHIP in-kernel ya bloquea el vCPU hasta la siguiente\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"c1\"\u003e// interrupción. No hay trabajo productivo para userspace aquí.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEn la siguiente iteración del loop, el código llama de vuelta a KVM, y KVM — porque es dueño del IRQCHIP y sabe que no hay interrupción pendiente — bloquea el hilo dentro del kernel mientras el guest se mantenga ocioso.\u003c/p\u003e\n\u003cp\u003eMismo test de nueve sandboxes ociosos. \u003ccode\u003etop\u003c/code\u003e: 7%. No siete por ciento por VM. Siete por ciento \u003cem\u003epara toda la flota\u003c/em\u003e. De ~370% a ~7% eliminando una línea. Cincuenta veces menos.\u003c/p\u003e\n\u003cp\u003eEl patrón general vale la pena nombrarlo. El código que agregaste \u0026ldquo;por si acaso\u0026rdquo; suele ser el código que más vale la pena borrar, porque nadie lo cuestiona. Las partes de un sistema con las que peleas se revisan a muerte. Las partes de las que nadie se queja pueden pudrirse en paz. Cuando la podredumbre finalmente te cobra, te cobra cincuenta veces más que cualquier cosa en la que alguna vez pensaste.\u003c/p\u003e\n\u003ch2 id=\"una-microvm-rápida-es-una-caja-de-herramientas-no-un-producto\" class=\"headerLink\"\u003e\n    \u003ca href=\"#una-microvm-r%c3%a1pida-es-una-caja-de-herramientas-no-un-producto\" class=\"header-mark\" aria-label=\"Header mark for 'Una microVM rápida es una caja de herramientas, no un producto'\"\u003e\u003c/a\u003e10 Una microVM rápida es una caja de herramientas, no un producto\u003c/h2\u003e\u003cp\u003eCuando \u003ccode\u003eAcquire\u003c/code\u003e a primera instrucción del guest fueron tres milisegundos, estuve orgulloso de eso por una semana. Luego intenté construir algo \u003cem\u003econ\u003c/em\u003e ello.\u003c/p\u003e\n\u003cp\u003eLo que quería era lo que todo el mundo quiere estos días: un REST API donde un cliente dice \u0026ldquo;dame un sandbox de Python 3.12 con \u003ccode\u003enumpy\u003c/code\u003e y \u003ccode\u003epandas\u003c/code\u003e, déjame correr código ahí\u0026rdquo;, aparece un sandbox, tres segundos después le devuelven un stdout, y siguen con su vida. Un \u003ccode\u003egocracker run\u003c/code\u003e crudo no puede hacer nada de eso. Arranca una VM. Eso es todo. Si una microVM es un bloque de motor, lo que yo necesitaba era el resto del auto.\u003c/p\u003e\n\u003cp\u003eLa primera decisión fue la más importante: mantener a gocracker exactamente como era, y construir la capa de administración como cosa separada. gocracker se queda como el VMM de bajo nivel, el caché de snapshots, y la warm pool de workers. Habla bytes e ioctls. No tiene opiniones sobre clientes ni templates. \u003cem\u003esandboxd\u003c/em\u003e es un demonio nuevo que se sienta encima y es dueño de templates, leases, pools, y tokens de preview. El SDK solo le habla a sandboxd. sandboxd solo le habla a gocracker sobre un unix socket. El round-trip extra es una característica, no un bug.\u003c/p\u003e\n\u003cp\u003eAprendí el valor de ese split por \u003cem\u003eno\u003c/em\u003e hacerlo limpiamente primero, y luego pasar tres horas debuggeando una race condition que solo existía porque dos capas estaban compartiendo un puntero que no tenían por qué compartir. Cruzar una frontera de proceso te obliga a negociar. Compartir un puntero te deja hacer trampa. La frontera es el cinturón de seguridad.\u003c/p\u003e\n\u003cp\u003eEl split es un flujo limpio de tres capas:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-17\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eSDK\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ePython\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eGo\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e \u003cspan class=\"n\"\u003eTS\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003esobre\u003c/span\u003e \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esandboxd\u003c/span\u003e            \u003cspan class=\"o\"\u003e\u0026lt;--\u003c/span\u003e \u003cspan class=\"n\"\u003edemonio\u003c/span\u003e \u003cspan class=\"n\"\u003ede\u003c/span\u003e \u003cspan class=\"n\"\u003eruntime\u003c/span\u003e \u003cspan class=\"n\"\u003egestionado\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e             \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etemplates\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eleases\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epools\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etokens\u003c/span\u003e \u003cspan class=\"n\"\u003ede\u003c/span\u003e \u003cspan class=\"n\"\u003epreview\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eHTTP\u003c/span\u003e \u003cspan class=\"n\"\u003esobre\u003c/span\u003e \u003cspan class=\"n\"\u003eunix\u003c/span\u003e \u003cspan class=\"n\"\u003esocket\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003egocracker\u003c/span\u003e \u003cspan class=\"n\"\u003eserve\u003c/span\u003e     \u003cspan class=\"o\"\u003e\u0026lt;--\u003c/span\u003e \u003cspan class=\"n\"\u003eorquestador\u003c/span\u003e \u003cspan class=\"n\"\u003eVMM\u003c/span\u003e \u003cspan class=\"n\"\u003ede\u003c/span\u003e \u003cspan class=\"n\"\u003ebajo\u003c/span\u003e \u003cspan class=\"n\"\u003enivel\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e             \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eioctls\u003c/span\u003e \u003cspan class=\"n\"\u003eKVM\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esnapshots\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewarm\u003c/span\u003e \u003cspan class=\"n\"\u003epool\u003c/span\u003e \u003cspan class=\"n\"\u003ede\u003c/span\u003e \u003cspan class=\"n\"\u003eworkers\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e  \u003cspan class=\"n\"\u003eioctls\u003c/span\u003e \u003cspan class=\"n\"\u003eKVM\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003evsock\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"n\"\u003ev\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003eguest\u003c/span\u003e \u003cspan class=\"n\"\u003eVM\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e|\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         \u003cspan class=\"o\"\u003e+--\u003c/span\u003e \u003cspan class=\"n\"\u003eagente\u003c/span\u003e \u003cspan class=\"n\"\u003etoolbox\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eescucha\u003c/span\u003e \u003cspan class=\"n\"\u003een\u003c/span\u003e \u003cspan class=\"n\"\u003eun\u003c/span\u003e \u003cspan class=\"n\"\u003epuerto\u003c/span\u003e \u003cspan class=\"n\"\u003evsock\u003c/span\u003e \u003cspan class=\"n\"\u003edentro\u003c/span\u003e \u003cspan class=\"n\"\u003edel\u003c/span\u003e \u003cspan class=\"n\"\u003eguest\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eTres saltos. Dos demonios. El SDK nunca le habla a gocracker directamente — no sabe que gocracker existe. sandboxd es la única superficie pública de API; todo lo de abajo es detalle de implementación. Cruzar una frontera de proceso por un unix socket es barato (sub-milisegundo para JSONs chicos), y la separabilidad se paga sola la primera vez que quieres reiniciar sandboxd sin matar cien VMs vivas.\u003c/p\u003e\n\u003cp\u003eLos templates son la otra idea que carga peso. Un cliente no quiere \u0026ldquo;una VM Linux\u0026rdquo;. Quiere el entorno que usa para su agente de AI — una imagen base específica, unos paquetes apt, unos paquetes pip, un directorio de trabajo, unas variables de entorno. Un template captura esa mezcla, \u003cem\u003emás\u003c/em\u003e el snapshot que resulta de arrancar la spec una vez y dejar que llegue a un estado estable. Dos templates con specs idénticos comparten snapshot. Un segundo create con la misma spec es un no-op.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-18\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003etype\u003c/span\u003e \u003cspan class=\"nx\"\u003eTemplate\u003c/span\u003e \u003cspan class=\"kd\"\u003estruct\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eID\u003c/span\u003e           \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eName\u003c/span\u003e         \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eSnapshotDir\u003c/span\u003e  \u003cspan class=\"kt\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eSpecHash\u003c/span\u003e     \u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// huella canónica de imagen, kernel, mem, env...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003eContextHash\u003c/span\u003e  \u003cspan class=\"kt\"\u003estring\u003c/span\u003e  \u003cspan class=\"c1\"\u003e// tarball de contexto cuando usas Dockerfile\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003eWarmPolicy\u003c/span\u003e   \u003cspan class=\"nx\"\u003eWarmPolicy\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eEso suena obvio hasta que imaginas la vida de un SaaS real: la mayoría de los creates de template son reintentos idempotentes. Un deploy se vuelve a correr. Un job de CI reenvía. Un SDK hace un ensure-exists antes de un create. Si cada uno costara un \u003ccode\u003edocker build\u003c/code\u003e fresco, estarías enviando un producto de $40 al mes sobre una infra de $400 al mes. La identidad content-addressable en cada capa se compone: el warm cache era content-addressable, los templates son content-addressable encima, y los sandboxes son baratos porque los templates son baratos.\u003c/p\u003e\n\u003ch2 id=\"cinco-warm-ready-ninguno-lo-estaba\" class=\"headerLink\"\u003e\n    \u003ca href=\"#cinco-warm-ready-ninguno-lo-estaba\" class=\"header-mark\" aria-label=\"Header mark for 'Cinco warm ready. Ninguno lo estaba.'\"\u003e\u003c/a\u003e11 Cinco warm ready. Ninguno lo estaba.\u003c/h2\u003e\u003cp\u003eEstaba corriendo un load test contra un sandboxd recién reconstruido. Nada elaborado — crea un sandbox, exec \u003ccode\u003eecho hi\u003c/code\u003e, borra el sandbox, en un loop. La pool estaba configurada para tres hot-ready y tres paused-ready para un solo template. Cada create debería ser esencialmente instantáneo, porque la pool debería mantener seis sandboxes tibios vivos y yo solo necesitaba uno a la vez.\u003c/p\u003e\n\u003cp\u003eFuncionó por unos noventa segundos.\u003c/p\u003e\n\u003cp\u003eDespués cada create empezó a fallar. No lento. No con backpressure. \u003cem\u003eCada uno\u003c/em\u003e, con variaciones de \u0026ldquo;runtime returned 404: unknown vm\u0026rdquo;. El endpoint de status de la pool reportaba tres hot-ready, dos paused-ready, cero leased. Una pool perfectamente sana, según ella misma. Las VMs llevaban muertas minutos.\u003c/p\u003e\n\u003cp\u003eEse fue un martes divertido.\u003c/p\u003e\n\u003cp\u003eLa primera versión del reconciliador confiaba en su propio registro en memoria. Contaba entradas marcadas \u003ccode\u003ewarm_ready\u003c/code\u003e, comparaba el conteo con \u003ccode\u003eMinHot\u003c/code\u003e, y concluía: sano, no hay acción. Nada en el reconciliador estaba \u003cem\u003emirando\u003c/em\u003e. Una VM warm-ready murió silenciosamente — panic de vCPU, OOM-kill, guest atascado, error de tipeo en fstab cayendo a rescue mode en systemd, lo que sea — y sandboxd la siguió contando como viva. Los leases siguientes fallaban al attach con 404s, el handler marcaba la entrada como \u0026ldquo;rota\u0026rdquo; y caía al cold boot, pero las entradas rotas quedaban en memoria como \u003ccode\u003ewarm_leased\u003c/code\u003e hasta que una goroutine separada de cleanup las cosechaba. Mientras tanto la pool seguía reportando cinco warm, el reconciliador seguía sin tomar decisiones, y cada request cold-booteaba.\u003c/p\u003e\n\u003cp\u003eLa cascada no fue espectacular. Sin alarmas. Sin pager. El sistema se estaba degradando silenciosamente a peor-caso, un 404 a la vez, mientras reportaba verde con diligencia.\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003etext\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-19\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  vista de sandboxd            estado real del runtime\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +-----------------+          +----------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | warm_ready: 3   |          | VM #1: muerta        |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | warm_ready: 2   |          | VM #2: muerta        |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | leased:     0   |          | VM #3: viva pero OOM |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  | total:      5   |          | VM #4: desaparecida  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  +-----------------+          | VM #5: desaparecida  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         ^                     +----------------------+\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         | \u0026#34;sano, sin acción\u0026#34;               | intento de lease -\u0026gt; 404\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e         |                                  v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   tick del reconciliador        el handler marca rota,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   cuenta estado en memoria      cae al cold boot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   compara con MinHot                       |\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   no hace nada                             v\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                 el usuario ve un cold boot de 2s\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                                 en cada request\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003eLa degradación silenciosa es el peor modo. Una falla ruidosa te deja paginar sobre ella. Una falla silenciosa significa que el gráfico se ve verde mientras los clientes se van.\u003c/p\u003e\n\u003cp\u003eEl fix fue estructural y chico. El reconciliador ahora hace tres cosas en orden, y el orden carga peso:\u003c/p\u003e\n\u003cdiv class=\"code-block highlight is-open show-line-numbers  tw-group tw-my-2\"\u003e\n  \u003cdiv class=\"\n    code-block-title \n    \n    tw-flex \n    tw-flex-row \n    tw-justify-between \n    tw-w-full tw-bg-bgColor-secondary\n    \"\u003e      \n    \u003cbutton \n      class=\"\n        tw-select-none \n        tw-mx-2 \n        tw-block\n        group-[.is-open]:tw-rotate-90\n        tw-transition-[transform] \n        tw-duration-500 \n        tw-ease-in-out\n        print:!tw-hidden\"\n      disabled\n      aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 320 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n    \u003cdiv class=\"code-block-title-bar tw-w-full\"\u003e\n      \u003cp class=\"tw-select-none !tw-my-1\"\u003ego\u003c/p\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"tw-flex\"\u003e\n      \u003cbutton \n        class=\"\n          line-number-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.show-line-numbers]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle line numbers\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n\n      \u003cbutton \n        class=\"\n          wrap-code-button\n          tw-select-none \n          tw-mx-2 \n          tw-hidden \n          group-[.is-open]:tw-block \n          group-[.is-wrap]:tw-text-fgColor-link \n          print:!tw-hidden\" \n        title=\"Toggle code wrap\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n      \n      \u003cbutton \n        class=\"\n          copy-code-button\n          tw-select-none\n          tw-mx-2 \n          tw-hidden\n          group-[.is-open]:tw-block\n          hover:tw-text-fgColor-link \n          print:!tw-hidden\"\n        title=\"Copy code\"\u003e\n          \u003cspan class=\"copy-icon tw-block\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n          \u003cspan class=\"check-icon tw-hidden\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z\"/\u003e\u003c/svg\u003e\u003c/span\u003e\n      \u003c/button\u003e\n        \n      \u003cbutton \n        class=\"\n          tw-select-none \n          tw-mx-2 \n          tw-block \n          group-[.is-open]:tw-hidden \n          print:!tw-hidden\" \n        disabled\n        aria-hidden=\"true\"\u003e\u003csvg class=\"icon\"\n    xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"\u003e\u003c!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --\u003e\u003cpath d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"/\u003e\u003c/svg\u003e\u003c/button\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cpre style=\"counter-reset: codeblock;\" class=\"tw-block tw-m-0 tw-p-0\"\u003e\u003ccode \n    id=\"codeblock-id-20\" \n    class=\"\n      chroma \n      !tw-block \n      tw-p-0\n      tw-m-0\n      tw-transition-[max-height] \n      tw-duration-500 \n      tw-ease-in-out \n      group-[.is-closed]:!tw-max-h-0 \n      group-[.is-wrap]:tw-text-wrap\n      tw-overflow-y-hidden\n      tw-overflow-x-auto\n      tw-scrollbar-thin\n      \"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003efunc\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003em\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eManager\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"nf\"\u003ereconcileTemplate\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"nx\"\u003eTemplate\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ereapDead\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e              \u003cspan class=\"c1\"\u003e// sondear runtime, eliminar fantasmas\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003einv\u003c/span\u003e \u003cspan class=\"o\"\u003e:=\u003c/span\u003e \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003einventoryFor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eID\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e// contar desde estado honesto\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e\u003c/span\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003epruneExcess\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003einv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003em\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nf\"\u003ereplenishUpToMin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003etpl\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nx\"\u003einv\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003c/div\u003e\n\u003cp\u003ePrimero, sondear cada sandbox warm que el manager cree que tiene, y eliminar todo lo que el runtime ya no conoce — \u0026ldquo;inconcluso\u0026rdquo; cuenta como muerto, porque una pool de VMs quizá-vivas es peor que una pool con un agujero. Segundo, contar desde el inventario ahora honesto. Tercero, podar el exceso y rellenar hasta el mínimo. Antes del fix: cinco sandboxes fantasma, cada request cold-booteaba, la pool reportando salud con alegría. Después: creates instantáneos otra vez.\u003c/p\u003e\n\u003cp\u003eHe chocado contra exactamente este bug antes. Sospecho que tú también, si llevas suficiente tiempo construyendo backends. Cada vez viste un disfraz un poco distinto — un controlador de Kubernetes que confía en el estado cacheado de un Pod en lugar del reporte real del kubelet, un connection pool que marca un backend sano porque la última respuesta fue 200, sin notar que el socket lleva treinta segundos cerrado en silencio, un service registry cuyo hilo de heartbeat es independiente del hilo de trabajo, así que el servicio puede estar deadlockeado y seguir pingueando, un browser que cachea un registro DNS más allá de la realidad. El error subyacente es el mismo cada vez: confiar en una representación en memoria del mundo, cruzando una frontera de proceso, sin sondear. El estado en memoria y la realidad fuera de proceso siempre se desincronizan. La pregunta no es si lo vas a notar; es \u003cem\u003ecuándo\u003c/em\u003e, y cuánto daño visible al usuario se acumula en el ínterin.\u003c/p\u003e\n\u003cp\u003eDos guardias más entraron por la misma puerta. Un backoff por template para que un único template roto — digamos, uno cuyo snapshot está sutilmente corrupto — no pueda mantener al reconciliador clavado spawneando VMs fallidas cada tick, matando de hambre a los templates sanos. Y un presupuesto global de spawns en vuelo por host, porque diez templates queriendo rellenar tres VMs cada uno a la vez son treinta spawns paralelos, suficiente para hacer que cada spawn sea más lento de lo necesario, lo cual aprieta los timeouts, lo cual cascadea. Los topes por template no son suficientes. El número de cosas que pueden salir mal simultáneamente en N templates crece más rápido que lo que el tope por template retiene.\u003c/p\u003e\n\u003ch2 id=\"lo-que-la-base-me-enseñó\" class=\"headerLink\"\u003e\n    \u003ca href=\"#lo-que-la-base-me-ense%c3%b1%c3%b3\" class=\"header-mark\" aria-label=\"Header mark for 'Lo que la base me enseñó'\"\u003e\u003c/a\u003e12 Lo que la base me enseñó\u003c/h2\u003e\u003cp\u003eMirando el arco entero, unas cuantas cosas salen lo suficiente como para llevárselas hacia adelante.\u003c/p\u003e\n\u003cp\u003eEl estado del kernel del host sobrevive a tu proceso. Límpialo al arrancar tanto como al apagarte. \u003ccode\u003eclose(fd)\u003c/code\u003e es una operación de refcount, no un mensaje de protocolo — si necesitas que el peer sepa que te fuiste, tienes que decírselo. Todo camino de salida necesita restauración de terminal, porque defer es una sugerencia que las señales y los seccomp trips ignoran. El race detector en CI no es negociable para ningún proyecto Go que mantiene estado entre goroutines.\u003c/p\u003e\n\u003cp\u003eTu mayor costo casi seguro no es lo que tú escribiste. Linux arrancando dentro de la VM eran tres cuartos de un arranque en frío de cuatrocientos milisegundos. Nada de lo que yo escribí importaba hasta que me puse a reducir eso. Una UART virtualizada es cara por byte; silenciar el log del kernel en el camino de la consola fue la sola ganancia más grande de rendimiento del proyecto. \u003ccode\u003eMAP_PRIVATE\u003c/code\u003e es dinero gratis para restaurar snapshots. El garbage collector de Go es un impuesto que puedes elegir no pagar en subprocesos corto-vivos.\u003c/p\u003e\n\u003cp\u003eConfía en el kernel más que en tu instinto. IRQCHIP in-kernel ya resuelve la suspensión de vCPUs ociosos. El sleep defensivo encima era trabajo negativo. El código defensivo es un detector de mentiras para tus suposiciones que cambiaron desde entonces: revisita las coberturas cuando el sistema subyacente se mueve. Y a veces la mayor victoria es una eliminación.\u003c/p\u003e\n\u003cp\u003eUna vez que una warm pool se sienta encima de una microVM, las reglas cambian. La pool es de mejor esfuerzo — un miss nunca debe hacer al usuario más lento que la base. Mata workers al liberarlos; nunca le entregues a un tenant un proceso que ha tocado los datos de otro tenant. Los loops de reconciliador tienen que observar antes de actuar, porque lo único más peligroso que un caché equivocado es un caché que el sistema dejó de cuestionar. Y lo inconcluso siempre es muerto en una pool — el costo de tratar un sandbox quizá-vivo como muerto es un arranque en frío; el costo de tratar a uno muerto como vivo es la falla de lease que ve tu cliente.\u003c/p\u003e\n\u003cp\u003eNinguna de estas victorias es ingeniosa por sí sola. Cada una es algo que alguien más resolvió años atrás — mmap, copy-on-write, aislamiento por tenant, eventfd más IRQFD, mise en place como concepto, confiar en el scheduler del IRQCHIP in-kernel. Nada inventado aquí. Lo que pasó fue dejar de pelear con cada uno, uno a la vez. Así es como suceden arranques en frío visibles al usuario de ~3 ms. Te los ganas capa por capa. No hay un único cambio heroico. Hay una pila de pequeños cambios honestos, cada uno haciendo más barato escribir el siguiente.\u003c/p\u003e\n\u003cp\u003eLas partes interesantes de la máquina ya están. Lo que queda es mantenerlas honestas.\u003c/p\u003e\n",
        "language": "es"
    },
    {
        "title" : "Memoirs: Enseñando a agentes a recordar (sin perder la cabeza en la nube)",
        "date_published" : "2026-05-12T00:00:00Z",
        "date_modified" : "2026-05-12T00:00:00Z",
        "id" : "https://misael.org/es/memoirs-local-first-memory/",
        "url" : "https://misael.org/es/memoirs-local-first-memory/",
        "summary": "1 La amnesia de los mil tokensTrabajar con agentes conversacionales (Claude, Cursor, iteraciones CLI) tiene un peaje invisible: siempre parecen levantarse con la memoria borrada. Su contexto, por muy grande que sea hoy, parte de una hoja en blanco en cada nuevo proyecto. Si logras convencerlos de que mantengan una hebra, terminas con monstruosidades arquitectónicas que envían todo el historial del chat ruidosamente a la ventana de contexto. Pagar el costo de inferencia de 40K tokens, para que al final el agente ignore lo más importante oculto en el centro del documento (el fatídico efecto Lost in the Middle), no sólo es costoso, sino ineficiente.",
        "content_html" : "\u003ch2 id=\"la-amnesia-de-los-mil-tokens\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-amnesia-de-los-mil-tokens\" class=\"header-mark\" aria-label=\"Header mark for 'La amnesia de los mil tokens'\"\u003e\u003c/a\u003e1 La amnesia de los mil tokens\u003c/h2\u003e\u003cp\u003eTrabajar con agentes conversacionales (Claude, Cursor, iteraciones CLI) tiene un peaje invisible: siempre parecen levantarse con la memoria borrada. Su contexto, por muy grande que sea hoy, parte de una hoja en blanco en cada nuevo proyecto. Si logras convencerlos de que mantengan una hebra, terminas con monstruosidades arquitectónicas que envían todo el historial del chat ruidosamente a la ventana de contexto. Pagar el costo de inferencia de 40K tokens, para que al final el agente ignore lo más importante oculto en el centro del documento (el fatídico efecto \u003cem\u003eLost in the Middle\u003c/em\u003e), no sólo es costoso, sino ineficiente.\u003c/p\u003e\n\u003cp\u003eExisten soluciones corporativas pesadas y bases de datos vectoriales subidas a la nube con llamadas de red carísimas. Pero yo no pretendo exponer mis notas de arquitectura ni mis credenciales ofuscadas a una API externa que me responde en 2 segundos cuando la red va bien. Quería una solución \u003cem\u003elocal-first\u003c/em\u003e, que mantuviera la privacidad de las decisiones operativas, consolidando eficientemente mis variables de entorno, mis elecciones de frameworks y mi estilo.\u003c/p\u003e\n\u003cp\u003eAsí nació \u003ca href=\"https://github.com/misaelzapata/memoirs\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ememoirs\u003c/a\u003e. No un simple motor RAG (Retrieval-Augmented Generation). Eso hubiese sido fácil y aburrido. Quería un sistema real que olvidara orgánicamente y consolidara lo que importa.\u003c/p\u003e\n\u003ch2 id=\"la-anatomía-subyacente-6-capas-sobre-sqlite\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-anatom%c3%ada-subyacente-6-capas-sobre-sqlite\" class=\"header-mark\" aria-label=\"Header mark for 'La anatomía subyacente: 6 capas sobre SQLite'\"\u003e\u003c/a\u003e2 La anatomía subyacente: 6 capas sobre SQLite\u003c/h2\u003e\u003cp\u003eDecidí anclar la persistencia en algo brutalmente sólido y subestimadamente poderoso hoy en día: SQLite. Pero no un simple archivo \u003ccode\u003e.db\u003c/code\u003e. Un SQLite hipervitaminado con \u003ccode\u003esqlite-vec\u003c/code\u003e para búsqueda densa (vectores) y \u003ccode\u003eFTS5\u003c/code\u003e nativo (funciones léxicas BM25).\u003c/p\u003e\n\u003cp\u003eLa arquitectura se resolvió en 6 capas, porque un sistema de memoria no es sólo un almacén, es una planta de tratamiento de agua:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eRaw logs\u003c/strong\u003e: El tubo de desagüe que recibe todo el historial y los \u003cem\u003ediffs\u003c/em\u003e ruidosos.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eExtraction\u003c/strong\u003e: Un modelo LLM curador corriendo localmente (mi opción habitual actualmente es Qwen 2.5 3B, agilísimo) escoge candidatos (heurísticas, credenciales, preferencias, estilo).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eGraph\u003c/strong\u003e: El momento de relacionarlo usando principios de \u003cem\u003eZettelkasten\u003c/em\u003e, uniéndolo con el resto de nodos, encontrando conexiones semánticas compartidas.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eIndexado Dual\u003c/strong\u003e: Actualizaciones atómicas en paralelo (sqlite-vec y FTS5).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMemory Engine\u003c/strong\u003e: La curación donde las memorias ganan o pierden puntuación y rango válido (con soporte bi-temporal).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSurface\u003c/strong\u003e: La capa superficial por la cual se expone (vía HTTP, REST y sobre todo un MCP con 22 endpoints RPC para que un agente lo consulte limpiamente).\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"rrf-y-bm25-vectores-no-resuelven-todo\" class=\"headerLink\"\u003e\n    \u003ca href=\"#rrf-y-bm25-vectores-no-resuelven-todo\" class=\"header-mark\" aria-label=\"Header mark for 'RRF y BM25: Vectores no resuelven todo'\"\u003e\u003c/a\u003e3 RRF y BM25: Vectores no resuelven todo\u003c/h2\u003e\u003cp\u003eUna de las grandes crisis de desarrollo fue observar que los vectores de \u003ccode\u003esentence-transformers\u003c/code\u003e fallaban espectacularmente cuando le pedía a \u003ccode\u003ememoirs\u003c/code\u003e que recuperara identificadores específicos o nombres de herramientas oscuras. Los espacios vectoriales mapean semántica maravillosamente: saben que \u0026ldquo;coche\u0026rdquo; es cercano a \u0026ldquo;vehículo\u0026rdquo;. Pero cuando un modelo guarda la frase \u003cem\u003e\u0026ldquo;En el sistema viejo usaba psql-driver-v9\u0026rdquo;\u003c/em\u003e, el embedding semántico no le da prioridad al nombre exacto. Para un humano (o un agente), eso es determinante.\u003c/p\u003e\n\u003cp\u003eLa solución (mi momento de revelación en la búsqueda) fue combinar algo muy nuevo con algo muy clásico: Reciprocal Rank Fusion (RRF). Ejecuto al mismo tiempo una consulta semántica densa y una búsqueda pura de texto por índice invertido (BM25 vía FTS5). Si BM25 lo encuentra exactamente, y la búsqueda semántica lo entiende por contexto general, RRF estabiliza los puntajes y lo sube firmemente al puesto número uno de los resultados recuperados, bajando mis percentiles (p50) a latencias asombrosas (~3.9 ms), un desempeño letal para una base de datos local que supera por mucho a los servicios prefabricados en la nube.\u003c/p\u003e\n\u003ch2 id=\"la-asombrosa-curva-de-ebbinghaus-y-el-sueño-asíncrono\" class=\"headerLink\"\u003e\n    \u003ca href=\"#la-asombrosa-curva-de-ebbinghaus-y-el-sue%c3%b1o-as%c3%adncrono\" class=\"header-mark\" aria-label=\"Header mark for 'La asombrosa curva de Ebbinghaus y el sueño asíncrono'\"\u003e\u003c/a\u003e4 La asombrosa curva de Ebbinghaus y el sueño asíncrono\u003c/h2\u003e\u003cp\u003eUn sistema no tiene \u0026ldquo;memoria\u0026rdquo; a largo plazo si no es capaz de olvidar. Los agentes tienden a ahogarse en conocimiento rancio (\u0026ldquo;Ayer me peleé con las APIs de Docker\u0026rdquo; seguido de \u0026ldquo;Hoy desechamos Docker y estamos con llamadas OCI puras\u0026rdquo;). Si un motor de memoria preserva y emite al agente ambos hechos con la misma fuerza, el agente inevitablemente tomará la decisión de desarrollo equivocada.\u003c/p\u003e\n\u003cp\u003eTuve que meterme a los apuntes viejos e implementar funciones basadas en la Curva del Olvido de Hermann Ebbinghaus: $R(t) = e^{-\\Delta t_h / (S \\cdot 24)}$. Las nuevas introducciones van perdiendo su peso por el paso del tiempo asintóticamente. Pero su \u0026lsquo;Strength\u0026rsquo; (S) –su fortaleza original– se multiplica cada vez que el ecosistema consulta positivamente dicha memoria. Las variables o convicciones fundamentales terminan decayendo a un ritmo mucho más lento que las anécdotas de depuración circunstanciales de una sesión de programación solitaria.\u003c/p\u003e\n\u003cp\u003eClaro, este peso procesal y la propia consolidación y purga de conflictos semánticos es demasiado computo para ejecutar en tiempo real. Y eso exigió implementar un mecanismo que los humanos usan constantemente pero que el software casi nunca aprovecha: el \u003cem\u003esueño\u003c/em\u003e.\nEscribí un demonio (\u003ccode\u003esleep_consolidation.py\u003c/code\u003e) que detecta los momentos valle de CPU; solo cuando el desarrollador y los agentes dejan el teclado para ir a servirse café (literalmente \u0026ldquo;tiempos donde el lock está libre y el Idle es mayor a N minutos\u0026rdquo;). En esa ventana asíncrona ciega, el modelo local de Qwen o Phi despierta, repasa los perfiles almacenados recientemente, busca las contradicciones entre versiones y consolida/archiva la memoria obsoleta, compactando el grafo silenciosamente en segundo plano antes de la siguiente interacción.\u003c/p\u003e\n\u003cp\u003eAl final, lograr un ratio perfecto de MRR (Mean Reciprocal Rank) en bancos de prueba de memoria como LoCoMo, mientras el overhead en RAM del modelo apenas pasaba de 231 MB frente a gigantes como LlamaIndex o Mem0, me confirmó el objetivo. Resulta que las mejores características para agentes no se escriben enviando millones de tokens a proveedores remotos; se escriben entendiendo muy bien índices básicos y imponiendo una disciplina de recursos implacable en tu propia máquina.\u003c/p\u003e\n",
        "language": "es"
    },
    {
        "title" : "node-vmm: La ilusión de un proceso, el aislamiento de una máquina virtual",
        "date_published" : "2026-05-05T00:00:00Z",
        "date_modified" : "2026-05-05T00:00:00Z",
        "id" : "https://misael.org/es/node-vmm-instant-vms/",
        "url" : "https://misael.org/es/node-vmm-instant-vms/",
        "summary": "1 El engaño de la \u0026ldquo;rapidez\u0026rdquo; en los contenedoresHay una mentira piadosa en la industria del desarrollo moderno que todos aceptamos porque es conveniente: fingimos que levantar un contenedor de Docker es rápido y ligero. Y lo es, si lo comparas con aprovisionar hardware desnudo en 2005. Pero a medida que mis herramientas necesitaban aislar cargas de trabajo cada vez con más frecuencia (y de forma más dinámica), empecé a sentir la fricción de depender de un motor externo gigante que actúa como intermediario.",
        "content_html" : "\u003ch2 id=\"el-engaño-de-la-rapidez-en-los-contenedores\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-enga%c3%b1o-de-la-rapidez-en-los-contenedores\" class=\"header-mark\" aria-label=\"Header mark for 'El engaño de la \u0026amp;ldquo;rapidez\u0026amp;rdquo; en los contenedores'\"\u003e\u003c/a\u003e1 El engaño de la \u0026ldquo;rapidez\u0026rdquo; en los contenedores\u003c/h2\u003e\u003cp\u003eHay una mentira piadosa en la industria del desarrollo moderno que todos aceptamos porque es conveniente: fingimos que levantar un contenedor de Docker es rápido y ligero. Y lo es, si lo comparas con aprovisionar hardware desnudo en 2005. Pero a medida que mis herramientas necesitaban aislar cargas de trabajo cada vez con más frecuencia (y de forma más dinámica), empecé a sentir la fricción de depender de un motor externo gigante que actúa como intermediario.\u003c/p\u003e\n\u003cp\u003eYo quería algo que se sintiera ergonómicamente como llamar a un \u003ccode\u003echild_process.spawn()\u003c/code\u003e en Node.js, pero que me entregara el aislamiento de hardware de una máquina virtual completa. Nada de contenedores con namespaces compartidos; quería un kernel propio (o al menos un micro-kernel) ejecutándose aislado. Y más importante aún: quería un \u0026ldquo;pause y resume\u0026rdquo; que se ejecutara tan rápido que una API HTTP dentro de la VM pudiera responder al cliente sintiéndose como si sólo hubiera habido latencia de red, sin tiempos de \u0026ldquo;descongelamiento\u0026rdquo;.\u003c/p\u003e\n\u003cp\u003eAquí nació \u003ca href=\"https://github.com/misaelzapata/node-vmm\" target=\"_blank\" rel=\"noopener noreferrer\"\u003enode-vmm\u003c/a\u003e. Y como suele ocurrir con estas cosas, la idea era sencilla hasta que me encontré de frente con la realidad de los hipervisores nativos.\u003c/p\u003e\n\u003ch2 id=\"ignorando-a-qemu-por-el-camino-difícil\" class=\"headerLink\"\u003e\n    \u003ca href=\"#ignorando-a-qemu-por-el-camino-dif%c3%adcil\" class=\"header-mark\" aria-label=\"Header mark for 'Ignorando a QEMU por el camino difícil'\"\u003e\u003c/a\u003e2 Ignorando a QEMU por el camino difícil\u003c/h2\u003e\u003cp\u003eCasi cualquier proyecto que necesita virtualización cruzada acude a QEMU instintivamente. Es la navaja suiza. Pero QEMU es \u003cem\u003egrande\u003c/em\u003e. Envolver QEMU desde Node hubiera resuelto el problema de la compatibilidad multiplataforma en una semana, pero habría matado el objetivo de la latencia y la ligereza.\u003c/p\u003e\n\u003cp\u003eDecidí construirlo interactuando directamente con las APIs de los hipervisores nativos de cada sistema operativo, usando C++ atado directamente a Node a través de N-API. El resultado fue una arquitectura dividida en tres mundos completamente distintos que no comparten ni una sola línea de código en su capa inferior:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eLinux\u003c/strong\u003e: Comunicación directa mediante llamadas \u003ccode\u003eioctl\u003c/code\u003e a KVM. Es un solo archivo de una belleza cruda (\u003ccode\u003enative/kvm/backend.cc\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWindows\u003c/strong\u003e: The Windows Hypervisor Platform (WHP). Aquí el dolor fue real. Requirió emular desde cero dispositivos como el APIC, timers, y puertos UART porque WHP te da un procesador virtual desnudo y te dice \u0026ldquo;buena suerte ensamblando la placa base\u0026rdquo;.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003emacOS / Apple Silicon\u003c/strong\u003e: El Hypervisor.framework (HVF). Pasé noches enteras dándome cuenta de que la forma más limpia aquí no era fingir ser un x86, sino usar una silueta de máquina ARM64 (basada en virt) para mantener todo veloz y nativo.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePoder abstraer estas bestias debajo de un simple \u003ccode\u003einterface NativeRunConfig\u003c/code\u003e en TypeScript no fue trivial. Hubo momentos donde el diseño de las interrupciones Mmio (Memory-Mapped I/O) para Virtio no cuadraba. En KVM la zancada de memoria (stride) para los dispositivos era limpia (0x1000). En Windows necesité empacarlos más ajustados (0x200) para hacerlos funcionar sin solapar las tablas de ACPI. Al final, logramos que la abstracción ocultara el abismo arquitectónico a cualquier desarrollador que importe la librería.\u003c/p\u003e\n\u003ch2 id=\"despidiendo-al-intermediario-oci-sin-docker-engine\" class=\"headerLink\"\u003e\n    \u003ca href=\"#despidiendo-al-intermediario-oci-sin-docker-engine\" class=\"header-mark\" aria-label=\"Header mark for 'Despidiendo al intermediario: OCI sin Docker Engine'\"\u003e\u003c/a\u003e3 Despidiendo al intermediario: OCI sin Docker Engine\u003c/h2\u003e\u003cp\u003eToda herramienta moderna necesita ejecutar imágenes. El impulso natural era hacer un puente que hablara con el socket local de Docker. Pero eso rompía mi regla de no depender de motores pesados.\u003c/p\u003e\n\u003cp\u003eEl resultado es \u003ccode\u003eoci.ts\u003c/code\u003e, un cliente de registros OCI (Open Container Initiative) escrito enteramente en TypeScript. Descifra los manifiestos, negocia los tokens y descarga los blobs tar.gz capa por capa, inyectándolos directamente en un rootfs ext4 que la VM puede montar al vuelo. Poder iniciar \u003ccode\u003enode:22-alpine\u003c/code\u003e arrancando un rootfs con la imagen decodificada localmente, sin la sobrecarga del demonio dockerd, cambia completamente el paradigma de inmediatez. En las arquitecturas donde \u003ccode\u003emkfs.ext4\u003c/code\u003e no está presente por defecto, nos apoyamos en WSL2 o Homebrew, degradando con gracia pero sin romper el flujo.\u003c/p\u003e\n\u003ch2 id=\"el-aha-moment-sharedarraybuffer-para-ignorar-la-gravedad\" class=\"headerLink\"\u003e\n    \u003ca href=\"#el-aha-moment-sharedarraybuffer-para-ignorar-la-gravedad\" class=\"header-mark\" aria-label=\"Header mark for 'El \u0026amp;ldquo;Aha! moment\u0026amp;rdquo;: SharedArrayBuffer para ignorar la gravedad'\"\u003e\u003c/a\u003e4 El \u0026ldquo;Aha! moment\u0026rdquo;: SharedArrayBuffer para ignorar la gravedad\u003c/h2\u003e\u003cp\u003eLograr tiempos de boot fríos de 1-3 segundos estaba bien. Pero mi verdadera obsesión era hacer que procesos pausados se reanudaran en latencias de petición de red (sub-100 ms).\u003c/p\u003e\n\u003cp\u003eNormalmente, el \u0026ldquo;pause/resume\u0026rdquo; en máquinas virtuales se logra congelando la CPU, serializando la RAM e interrupciones en el disco, y restaurándolas al despertar. Eso es lentísimo para mi propósito. Mi otra opción era mandar mensajes entre el hilo principal de Node JS y el Worker thread que manejaba el hipervisor. ¿El problema? El puente \u003cem\u003eMessage Passing\u003c/em\u003e de Node en el event loop introduce parpadeos (jitter) de latencia y bloqueos.\u003c/p\u003e\n\u003cp\u003eLa iluminación llegó recordando cómo renderizan los videojuegos modernos: \u003cstrong\u003eSharedArrayBuffer\u003c/strong\u003e combinado con Atomics.\nImplementé un búfer diminuto estructurado (con slots para \u003ccode\u003eCONTROL_COMMAND\u003c/code\u003e, \u003ccode\u003eCONTROL_STATE\u003c/code\u003e y la consola) que el hilo principal (TypeScript) y el hilo Worker (C++) pueden consultar de manera atómica sin \u003cem\u003elocks\u003c/em\u003e costosos ni serialización de mensajes a través del V8.\u003c/p\u003e\n\u003cp\u003eCuando quiero pausar una VM, el hilo de TS escribe un \u003ccode\u003e1\u003c/code\u003e de forma atómica en el buffer. El Worker de KVM/WHP, en uno de sus microscópicos VM-exits, revisa ese byte de memoria compartida y simplemente detiene la ejecución del vCPU sin tirar abajo la infraestructura de memoria de la máquina. LaVM no está en el disco, sigue viva en el hipervisor pero \u0026ldquo;dormida\u0026rdquo; sin gastar ciclos.\u003c/p\u003e\n\u003cp\u003eLos servidores Fastify o Express reanudan resolviendo un \u003ccode\u003eGET /\u003c/code\u003e pendiente en apenas \u003cstrong\u003e5 a 50 milisegundos\u003c/strong\u003e. Es un truco modesto comparado con un hypervisor comercial, pero el impacto ergonómico que tiene para levantar ambientes aislados es descomunal.\u003c/p\u003e\n\u003cp\u003eEl proyecto aún no está terminado —el restore de RAM complejo (snapshot frío) es algo que sigo persiguiendo utilizando \u003cem\u003edirty-page tracking\u003c/em\u003e, que he dejado preparado en los cimientos del código—. Pero hasta ahora, he conseguido exactamente lo que buscaba: la firmeza inquebrantable de una máquina virtual real escondida en el bolsillo de algo que luce, opera y muere tan fácilmente como un proceso más de mi terminal.\u003c/p\u003e\n",
        "language": "es"
    },
    ]
}
