AWS publicó en septiembre de 2026 un emulador local de sus propias APIs. Es un binario en Rust que se instala desde un paquete Debian o RPM, se apunta AWS_ENDPOINT_URL hacia él y se corren las pruebas de integración sin cuenta de AWS. Quien haya usado esa categoría de herramienta conoce el modo de fallo: todo verde en local, todo roto en la nube real.
Lo que hace distinto a este proyecto es que AWS publica la evidencia. Cada versión debería traer un scores.json.gz con el registro crudo de aprobados y fallidos de una suite de conformidad pública, y el repositorio lleva cada hueco como un archivo markdown en dos directorios: issues/things-that-do-not-work/ e issues/things-that-now-work/.
Esto no es una reseña basada en una demo. Descargué el registro adjunto a v1.0.322, descargué el manifiesto de la suite de evaluación, cloné el repositorio de forma dispersa para contar los archivos de operación, ejecuté el paquete Debian real en una máquina Debian de 2 núcleos y apunté boto3 contra él. Todo lo que sigue sale de archivos propios de AWS o de mediciones hechas en esta máquina.
El resumen corto: el emulador funciona mejor de lo que sugiere su cifra de titular, el 56,46 % es lo menos preocupante, y la clase de fallo que sí importa es mucho más pequeña y mucho más específica que «la mitad de las pruebas fallan».
Qué hay realmente dentro
El archivo AGENTS.md de la raíz es más preciso sobre la arquitectura que el README. Describe un espacio de trabajo generado por algo llamado EmulatorCore v0.1.0, con un crate de Cargo por servicio de AWS y un archivo por operación bajo src/ops/. El modelo Smithy viaja con cada crate y gobierna el protocolo de red; vendor/ contiene lo que AGENTS.md llama un marco «inmutable para agentes» que no se debe bifurcar.
Medido sobre un clon disperso:
| Medición | Valor |
|---|---|
Crates de servicio en services/ | 436 |
Modelos Smithy (smithy-model/*.json) | 437 |
Archivos de operación (*/src/ops/*.rs) | 40.130 |
Archivos marcados //! SCAFFOLDED, NOT IMPLEMENTED: | 8.261 (20,6 %) |
| Líneas de Rust totales | 15.911.659 |
| Líneas de código frente a pruebas | 7.805.009 / 8.104.470 (1,04:1) |
Tamaño de services/ en disco | 944 MB (repositorio 1,35 GB) |
Esa proporción de 1,04:1 entre pruebas y código, sobre 15,9 millones de líneas, no es lo que parece un prototipo. Tampoco es lo que parece un producto terminado, porque 8.261 de las 40.130 operaciones —una de cada cinco— son esqueletos que responden HTTP 501 por construcción. El propio repositorio lo dice en el comentario de cabecera, una línea encima de la marca de censo que el código documenta como la forma de listarlas:
# el censo del propio repositorio, citado de la cabecera del esqueleto
grep -rl '^//! SCAFFOLDED, NOT IMPLEMENTED:' */src/ops/ | wc -l
# 8261
Una quinta parte de la superficie de API es honesta sobre su ausencia. Es una decisión de diseño respetable: un 501 es una respuesta mucho mejor que un éxito inventado. El registro de issues/ lo confirma: un archivo abierto describe un intento anterior de simular una respuesta de aprendizaje automático con una tabla de unos 60 términos y un hash reescalado a [0.90, 1.00), y sentencia: «Do not re-introduce either» —no vuelvas a introducir ninguna de las dos cosas.
El registro publicado, leído directamente
El scores.json.gz de v1.0.322 se descomprime en 16,7 MB de JSON: un objeto con commit, schemaVersion, scoredAt y un array units. Cada unidad trae un outcome, un kind (behavioral o wire), los servicios que toca y —cuando falla— un bloque detail con el estado esperado, el obtenido, el paso que rompió y la operación.
| Resultado | Unidades | Porcentaje |
|---|---|---|
| pass | 24.243 | 56,46 % |
| fail | 18.614 | 43,35 % |
| skipped | 80 | 0,19 % |
| total | 42.937 |
Por tipo: 32.402 de comportamiento y 10.535 de cable. Los servicios puntuados son 350.
Por servicio la dispersión es enorme, y no sigue el patrón que la mayoría adivinaría:
| Servicio (nombre en el registro) | Unidades | Pasan | Tasa |
|---|---|---|---|
| CloudFormation | 4.206 | 795 | 18,9 % |
Lambda (AWSGirApiService) | 3.006 | 1.561 | 51,9 % |
| S3 | 2.814 | 1.763 | 62,7 % |
| EC2 | 2.470 | 958 | 38,8 % |
| DynamoDB | 1.694 | 1.153 | 68,1 % |
| SQS | 1.523 | 1.032 | 67,8 % |
| IAM | 1.223 | 709 | 58,0 % |
| CloudWatch Logs | 1.223 | 701 | 57,3 % |
| Step Functions | 982 | 687 | 70,0 % |
| RDS | 813 | 321 | 39,5 % |
| SSM | 748 | 178 | 23,8 % |
| Auto Scaling | 529 | 105 | 19,8 % |
| Athena | 465 | 92 | 19,8 % |
| IoT | 468 | 47 | 10,0 % |
CloudFormation —el servicio con el que empieza casi cualquier canal de CI— es el peor servicio grande del emulador, con 18,9 % sobre la mayor población de pruebas de todo el conjunto. IoT está en 10,0 %. En cambio DynamoDB y SQS, los que suelen suponerse más difíciles de falsificar, son sus mejores notas.
Los fallos no se reparten igual según la intención
El manifest.jsonl de la suite de evaluación (29.676 comprobaciones, 18,2 MB) etiqueta cada comprobación. Cruzarlo con el registro por check_id —coinciden 29.255— convierte el 56,46 % global en algo mucho más útil:
| Etiqueta | Unidades | Pasan | Tasa |
|---|---|---|---|
axis:happy-path | 15.877 | 10.366 | 65,3 % |
plane:control | 19.300 | 12.973 | 67,2 % |
axis:rejection | 12.542 | 7.606 | 60,6 % |
axis:negative-delivery | 836 | 400 | 47,8 % |
timing:real-time | 892 | 382 | 42,8 % |
dp:endpoint | 1.434 | 361 | 25,2 % |
dp:native | 156 | 0 | 0,0 % |
Fíjate en la última fila. De las 156 unidades etiquetadas como plano de datos nativo, no pasa ninguna; 133 fallan y 23 se omiten. Es un emulador de plano de control con las virtudes de un plano de control: crear, describir, listar, etiquetar, borrar. Cuando una prueba se adentra en un protocolo nativo de datos, la respuesta no es «casi correcta», es nada.
La diferencia entre camino feliz y rechazo (65,3 % frente a 60,6 %) es más estrecha de lo que esperaba, y apunta a lo mismo desde otro ángulo: el emulador es casi igual de fiable rechazando peticiones válidas que aceptando las válidas. Encaja con un código donde una quinta parte de las operaciones no está implementada: un rechazo 501 parece un error de validación si solo miras la clase del código de estado.
La cifra que sí debería preocuparte: 306
Las tasas agregadas esconden el modo de fallo que de verdad duele. Por cada unidad fallida el registro guarda el estado expected y el actual. Filtrando la dirección peligrosa —AWS espera un 4xx de cliente y el emulador devolvió un 2xx de éxito— quedan 306 unidades. De ellas, 248 volvieron como un simple HTTP 200.
Son los casos en los que tu CI demuestra algo falso. La prueba afirma que el SDK lanza una excepción con una entrada inválida, el emulador devuelve éxito, la aserción nunca se dispara y la suite queda verde.
| Servicio | Unidades donde AWS dice 4xx y el emulador dice 2xx |
|---|---|
| CloudWatch Logs | 19 |
| CloudFront | 16 |
| S3 | 16 |
| OpenSearch | 15 |
| Cognito User Pools | 10 |
Las operaciones se concentran igual: CreateDistribution (11), CreateDomain (9) y AssociateSourceToS3TableIntegration (9). Dos entradas concretas, citadas del registro:
BackplaneControlService/GetMethod: esperado404, obtenido200.ApiGatewayV2/CreateApi: esperado400, obtenido201.
La imagen espejo importa también, y es nueve veces mayor: 2.755 unidades donde AWS espera un 2xx y el emulador devolvió un 4xx. Ese es el fallo que vas a vivir a diario —una petición válida rechazada, una prueba que no hay manera de poner en verde, una tarde perdida en un workaround—. Es caro, no peligroso, porque falla en cerrado.
Qué son las 80 unidades omitidas
Las unidades omitidas se pasan por alto con facilidad y este conjunto es demasiado elocuente. Las 80 llevan cause: runner-tool-missing y sus identificadores empiezan por _smoke.:
_smoke.js-sdk-v3-drop-in_smoke.js-sdk-v3-batch-copy-and-query-operations_smoke.opentofu-provider-connects_smoke.opentofu-provider-assume-role-resolves-the-role-session
Las pruebas de humo que demostrarían la compatibilidad directa con el SDK y la conexión del proveedor de OpenTofu no se ejecutaron en el entorno que produjo las puntuaciones publicadas. La promesa central del producto —«apuntas el endpoint al emulador y tu código no cambia»— está respaldada, en el registro, por pruebas omitidas en lugar de superadas. No es un escándalo: es una distancia entre la afirmación y la evidencia, y es exactamente la distancia que un lector debe cerrar por su cuenta.
Cobertura: 11.082 de 40.130
El manifiesto nombra las operaciones que ejercita cada comprobación. Deduplicando entre las 29.676 comprobaciones salen 11.082 pares servicio:Operación distintos. Contra las 40.130 operaciones del repositorio, eso es el 27,6 % de la superficie con al menos una prueba de fidelidad detrás.
La documentación dice qué hacer con el otro 72,4 %, y lo dice con una franqueza poco habitual: «If no fidelity test covers a behavior, the emulator might already handle it correctly, but no fidelity test confirms it yet» —si ninguna prueba cubre un comportamiento, puede que el emulador ya lo haga bien, pero ninguna prueba lo confirma.
Hay dos asimetrías menores en ese cruce que conviene registrar porque nadie las publica:
- 421 comprobaciones del manifiesto no tienen unidad puntuada en el registro de
v1.0.322: existen en la suite pero no produjeron resultado en esta versión. - 67 de los 436 crates no tienen servicio puntuado correspondiente (emparejados por el nombre del directorio, así que es aproximado en los márgenes). Para esos servicios no hay evidencia publicada en ninguna dirección.
El registro de huecos crece, no se reduce
issues/things-that-do-not-work/ contiene 6.544 archivos markdown. issues/things-that-now-work/ contiene 12.708. Cada archivo lleva front matter estructurada, lo que hace cuantificable el atraso en lugar de anecdótico:
| Severidad de huecos abiertos | Archivos |
|---|---|
| HIGH | 5.028 |
| MEDIUM | 1.234 |
| CRITICAL | 159 |
| LOW | 123 |
Los archivos se nombran con marca de tiempo UTC, así que se puede contar lo abierto y lo cerrado por día. Ahí está la tendencia más reveladora:
| Periodo | Abiertos | Resueltos | Neto |
|---|---|---|---|
| 2026-10-21 → 10-30 (10 días) | 1.974 | 12.009 | −10.035 |
| 2026-10-31 → 11-10 (11 días) | 4.569 | 692 | +3.877 |
En los primeros diez días las resoluciones superaron a las aperturas por un orden de magnitud. Después la dirección se invirtió, y las aperturas han superado a las resoluciones todos los días durante once días consecutivos. El peor día es el 2026-11-09: 1.011 huecos abiertos frente a 10 resueltos.
Hay dos lecturas honestas y los datos no eligen entre ellas. O la auditoría automatizada que genera estos archivos se volvió mucho más agresiva —verosímil en un código escrito por agentes contra una especificación— o el proyecto acumula comportamiento roto conocido más rápido de lo que lo arregla. Lo que no es ambiguo es la aritmética: el atraso abierto creció 3.877 archivos en once días, y 159 de los abiertos están marcados como CRITICAL.
Solo 277 de los 6.544 archivos abiertos llevan Issue-Gap-Class; 6.267 no tienen etiqueta. El lado resuelto está mucho mejor etiquetado (4.579 de 12.708). El registro es más legible sobre lo arreglado que sobre lo roto.
Doce versiones en cinco días y 206 regresiones
Entre el 2026-11-07 a las 09:02 y el 2026-11-11 a las 00:32 el proyecto publicó 12 versiones, de v1.0.250 a v1.0.322. Los cuerpos de versión son listas largas de entradas fix: <slug>: el de v1.0.322 son 13.099 bytes de ellas y no contiene ninguna cifra de fidelidad.
Solo 5 de las 12 versiones adjuntan scores.json.gz; el activo aparece por primera vez en v1.0.290. Hay una distancia real entre lo que el README te dice que hagas —«check the fidelity scores published with each release»— y lo que la página de versiones te da en la mayoría de los casos.
Las cinco versiones puntuadas sí permiten ver moverse la tasa:
| Versión | Unidades | Pasan | Tasa |
|---|---|---|---|
| v1.0.290 | 42.634 | 23.774 | 55,76 % |
| v1.0.291 | 42.634 | 23.796 | 55,81 % |
| v1.0.292 | 42.631 | 23.926 | 56,12 % |
| v1.0.293 | 42.631 | 23.942 | 56,16 % |
| v1.0.322 | 42.937 | 24.243 | 56,46 % |
Entre las dos últimas hay 482 unidades que pasaron de fallo a éxito. También hay 206 unidades que pasaron de éxito a fallo, junto a 400 identificadores nuevos y 97 eliminados. Parte de esas 206 serán cambios de expectativa y no regresiones de código —el conjunto de unidades también se movió, así que desde fuera no se pueden separar—. Pero «mejora continuamente, con actualizaciones varias veces al día» (frase de la propia documentación) corta en las dos direcciones cuando fijas una dependencia de CI: mejoras y regresiones viajan por el mismo canal, a la misma velocidad.
Lo que medí ejecutándolo de verdad
Extraje el paquete Debian sin root (dpkg-deb -x), arranqué el binario y apunté boto3 1.44.1 a https://127.0.0.1:9297 con el CA del propio emulador.
Primero la comprobación de realidad: el binario informa Version: 1.0.322 y Commit: 7753983c6e495ec3612695b1336bf4936fc7b7fd, exactamente el commit con el que se generó el archivo de puntuaciones. El registro y el artefacto que ejecuté son lo mismo, algo que en la mayoría de proyectos no se puede dar por supuesto.
Lo que funcionó, a la primera y sin ajustes:
| Prueba | Resultado |
|---|---|
| S3 crear/poner/leer, listar | 200, cuerpo intacto |
| S3 clave inexistente | NoSuchKey 404 |
S3 GetObject con SSECustomerAlgorithm y sin clave | InvalidArgument 400, mensaje correcto |
| S3 bucket sin política | NoSuchBucketPolicy 404 |
| DynamoDB crear tabla → escribir → leer | CREATING → ACTIVE en 3,0 s, luego funciona |
| Stack de CloudFormation con SQS + S3 + SSM | CREATE_COMPLETE en 1 s, los tres recursos existían de verdad, la salida Ref resolvió |
| Lambda crear → invocar (zip real) | Pending → Active → 200, carga {"ok": true} |
| Envío SQS de exactamente 1.048.576 bytes | aceptado |
| Envío SQS de 1.048.577 bytes | InvalidParameterValue: «Message must be shorter than 1048576 bytes» |
La fila de CloudFormation merece énfasis, porque el cuadro anterior dice 18,9 %. El stack que creé aprovisionó una cola, un bucket y un parámetro, informó los tres recursos como CREATE_COMPLETE, emitió los eventos en el orden correcto y resolvió una salida. El camino feliz no es donde viven el 81,1 % de fallos.
Y ahora la parte que hice mal, que es la más útil de este artículo.
Salí a buscar trampas con 13 pruebas de ruta negativa: mensajes sobredimensionados, nombres de bucket inválidos, tipos de recurso desconocidos en CloudFormation, Ref a identificadores inexistentes, plantillas que no son JSON, tipos de clave equivocados, elementos de 500 KB en DynamoDB y más. Tres salieron «aceptado donde AWS rechaza» y tenía tres hallazgos listos.
Los tres eran error mío:
- Mensaje SQS sobredimensionado. Estaba seguro de que el límite era 256 KiB. El emulador aceptó 262.145 bytes. Luego leí la referencia actual de
SendMessage: el máximo hoy es 1 MiB. El emulador tenía razón y yo trabajaba con un límite que AWS ya había cambiado. - Zip en línea de Lambda.
create_functioncon un cuerpo que no era zip dejó la función enPendingyInvokefalló conInvalidZipFileException. Eso es lo que hace AWS real con una carga que no es un zip. Con un zip de verdad pasó aActivey devolvió el valor del handler. - Nombre de parámetro SSM sin barra inicial. El emulador aceptó
no-slash-name. La documentación dePutParameterexige la barra inicial para parámetros en una jerarquía; un nombre plano es válido.
Después empujé el límite de SQS para encontrar el borde real, y el emulador lo aplicó al byte, con el texto de mensaje de AWS, en 1.048.576 frente a 1.048.577.
Esa es la lección real de este código, y es mejor que «la mitad de las pruebas fallan». Trece pruebas negativas dirigidas no encontraron ninguna divergencia confirmada, mientras la propia suite de AWS conoce 18.614 unidades fallidas. Inspeccionar a mano no te dice si este emulador es seguro para tu stack, en ninguna de las dos direcciones. Tres hallazgos seguros se disolvieron al contacto con la documentación vigente, y cada uno habría sido un párrafo equivocado en una reseña menos cuidadosa. El registro existe precisamente porque la intuición sobre AWS está desactualizada, es asimétrica y equivocarse sale caro.
El límite que AWS declara sobre sí misma
Dos documentos de este proyecto son más sinceros que el material de marketing, y conviene leerlos antes de adoptarlo.
La página de seguridad dice en negrita: «Don’t use AWS Emulator to test whether your application is secure. Passing tests in the emulator don’t show that your permissions are correct or safe» —no uses el emulador para comprobar si tu aplicación es segura; que las pruebas pasen no demuestra que tus permisos sean correctos. Después enumera qué no probar —políticas y límites de permisos de IAM, políticas basadas en recursos, políticas de claves de KMS, acceso entre cuentas o regiones, «cualquier otro control de seguridad»— y explica el mecanismo: «The emulator never denies a request signed with its own key». No comprueba políticas de claves de KMS ni políticas de bucket de S3, así que «a request that AWS denies might succeed in the emulator. A request that AWS allows might fail».
Otras dos que se pasan por alto y salen caras después:
- «When the emulator runs code for you, such as function code, it doesn’t isolate that code from your machine. The code runs with your user’s permissions and can read and change your files». Tus pruebas de Lambda corren con los privilegios de tu shell, sin aislamiento.
- «Ports for resources with their own endpoint don’t check request signatures», y con
--auth offel emulador «serves any request, signed or not».
Y la página de limitaciones descarta el uso que mucha gente intentará a continuación: todo el estado vive en memoria y «the emulator deletes all resources when it stops», el comportamiento se verifica contra AWS «in us-west-2 only» y «we haven’t tested the emulator for long-running use or load testing». Es una herramienta para ejecuciones de prueba, no una nube local.
Cómo decidir, servicio por servicio
La documentación apunta a una skill para agentes de IA. Se puede hacer a mano en cinco pasos, y los números de arriba son los que vas a leer.
- Enumera las operaciones que llama tu flujo, no los servicios: las operaciones.
- Comprueba cobertura en
manifest.jsonldeaws/aws-emulator-evaluation-suite. Si tu operación no está entre las 11.082 nombradas, no tienes evidencia y hay que probar contra AWS real. - Lee el array
unitsdescores.json.gzde tu versión, filtrado por tu servicio. Lee las unidades por operación, no la media del servicio:AmazonS3está en 62,7 % global, pero esa cifra no dice nada sobre tus seis llamadas. - Filtra la dirección peligrosa:
expected.status >= 400y200 <= actual.status < 300. Si alguna unidad que cubre tu operación está ahí, tus pruebas de ruta de error no significan nada contra el emulador. - Busca
^//! SCAFFOLDED, NOT IMPLEMENTED:en las operaciones de las que dependes. Si está, la operación responde 501 y ninguna prueba lo va a cambiar.
Después fija la versión por commit, no por «latest»: 206 unidades se movieron en la dirección equivocada en una ventana de cinco días.
Preguntas frecuentes
¿Esto es LocalStack? No. Es otra implementación publicada por AWS, en Rust, un crate por servicio, licencia CC0-1.0, instalada como paquete nativo. También es mucho más joven: el repositorio se creó el 2026-09-11.
¿Necesita cuenta o credenciales de AWS?
No. Al arrancar genera su propia clave de acceso y la escribe en ~/.aws/emulator/<puerto>/emulator_credentials, con el CA al lado, y rechaza peticiones sin firmar. Cada ejecución tiene claves nuevas.
¿Por qué la tasa es solo del 56 % si el camino feliz funciona? Porque una quinta parte de las operaciones son esqueletos 501 y la suite prueba toda la superficie, incluidos protocolos nativos de plano de datos donde no pasa ninguna de 156 unidades. Los fallos se concentran en los servicios con mayor superficie incompleta: CloudFormation, EC2, SSM, Auto Scaling, Athena, IoT.
¿Sirve en CI? Sí, con dos condiciones: solo afirmas sobre operaciones cuyas unidades de fidelidad pasan, y mantienes una ejecución aparte contra AWS real para validación y comportamiento de seguridad. La documentación excluye explícitamente las pruebas de seguridad.
¿Y CloudFormation en concreto? Tiene la mayor población de pruebas (4.206 unidades) y la peor tasa entre los servicios grandes (18,9 %), además de 689 archivos abiertos que lo mencionan. Mi stack de tres recursos funcionó de principio a fin, así que «CloudFormation está en 18,9 %» no significa que sea inutilizable: significa que 3.391 comportamientos concretos divergen y conviene leerlos antes de fiarse de una prueba de plantillas.
¿Las 15,9 millones de líneas están escritas a mano?
No. AGENTS.md dice que el proyecto lo genera EmulatorCore v0.1.0 con esqueletos por operación, y 8.261 archivos de operación son esqueletos sin modificar con la marca de censo intacta. Toma el conteo de líneas como el tamaño de la superficie generada, no como prueba de esfuerzo.
Veredicto
Juzgado como emulador, esto es mejor que su nota. S3, DynamoDB, SQS, SSM, Lambda y un stack de CloudFormation de tres recursos se comportaron correctamente en una primera sesión, hasta el texto del mensaje de error y un límite de tamaño exacto al byte. Salí a buscar defectos con trece pruebas y no encontré ninguno que pudiera defender.
Juzgado como dependencia de CI, los números dicen algo más estrecho y más útil. El 27,6 % de las 40.130 operaciones tiene alguna evidencia de fidelidad. 306 unidades confirman que el emulador devuelve éxito donde AWS devuelve error: justo el modo de fallo que convierte un CI verde en un incidente de producción. Y una quinta parte de la superficie responderá 501 antes de que tu prueba llegue a afirmar nada.
La forma correcta de usarlo es la que el propio proyecto documenta, algo poco habitual en esta categoría: lee el registro primero, fija la versión, deja las aserciones de ruta de error y de seguridad contra AWS real, y trata el emulador como un bucle rápido para CRUD de plano de control, no como un sustituto de la nube.
Si un emulador puede decirte exactamente cuáles 18.614 comportamientos hace mal, lo mínimo es leer la lista. Esa lista es el producto; el binario es solo la parte que compila.
Para una segunda opinión sobre cuánto revelan los archivos de autodeclaración de un proyecto, mira la auditoría de OpenMausBot, donde un sandbox que solo se enciende en la nube resultó estar apagado por diseño, y la auditoría de agent-desktop, donde 44 comandos informan un error de permisos que nunca comprobaron.