What's Changed
New Contributors
[1.5.12] — 2026-09-15
Added
- EKS — Pod Identity associations — the five operations did not exist, so a controller that reads pod identity to find its role, the AWS Load Balancer Controller among them, could not run against MiniStack. Create, Describe, List, Update and Delete serve the records, one per namespace and service account. Deleting a cluster removes them with it. Reported by @josephaw1022.
- EKS —
aws eks update-kubeconfigworks against MiniStack — k3s could not authenticate the IAM exec tokenaws eks get-tokenproduces, so the only way in was to copy the admin kubeconfig out of the container. MiniStack now answers a TokenReview webhook:AUTH=falseaccepts local bearer tokens,AUTH=trueverifies the presigned token and then requires a creator or Access Entry grant. The five general-purpose access policies grant their published Kubernetes permissions at cluster or namespace scope. Contributed by @jgrumboe. Reported by @StraggleCraft.
Changed
- SES — one v2 implementation, returning the v1
MessageIdshape —ses.pycarried an unreachable copy of the v2 endpoints that had drifted fromses_v2.py. The duplicate is gone and every v2 request is served by one implementation, which surfaced a divergence it had hidden:SendEmailanswered an id prefixedministack-, where the v1 paths return<uuid>@email.amazonses.com. Both now return the shared shape. Contributed by @jgrumboe. - Persistence — every service module declares the same state contract — a module could expose
get_state, arestore_state, both or neither, so persistence was wired per service and nothing could be derived from it. Every module inministack/services/now exposes the sameget_state/load_persisted_state/resettrio, stateless ones included, and a registry-driven test refuses a service that does not. Contributed by @jgrumboe.
Fixed
- S3 — a notification configuration whose destination does not exist is refused — the configuration was stored and the failed test delivery swallowed, so a queue or topic that was not there looked like a successful setup and no event ever arrived. AWS verifies an SQS or SNS destination by sending it a test notification and fails the whole PUT when it does not arrive; a missing one is now
InvalidArgumentwith nothing stored. Reported by @ortizgui. - ECS — a task walks the whole lifecycle — a Docker-backed task went straight from
PENDINGtoRUNNINGtoSTOPPED, so a consumer waiting on any other state waited forever. It now reports the states AWS documents, in order:PROVISIONING,PENDING,ACTIVATING, thenDEACTIVATING,STOPPING,DEPROVISIONINGandSTOPPED. Every starting state counts against a service's desired capacity, whilependingCountcountsPENDINGalone as the API model defines it. Reported by @iot-rocket. - ECS — a task's
versioncounts its state changes — the counter was minted at1and never moved, so a consumer could not tell a staleDescribeTaskscopy from the current one. It now moves on each change the record reports, matching a real Fargate task:1atPROVISIONINGthrough6atSTOPPED. Contributed by @iot-rocket. - ECS — only an
awsvpctask reports an elastic network interface — every task got anElasticNetworkInterfaceattachment once its container was up, whatever its network mode, while the Task reference describesattachmentsas the adapter a task has "if the task uses theawsvpcnetwork mode". Other modes now report an empty list, and anawsvpcattachment names the subnet the request placed the task in. Contributed by @iot-rocket. - ECS — a task no longer reports
attachmentsStatus— the task record carried the member next to its attachment, and there is no such member on theTaskshape: the API model has it onClusteronly. A client reading the raw wire saw an invented field. Contributed by @iot-rocket. - ECS — the task metadata endpoint reports the status the task is in —
${ECS_CONTAINER_METADATA_URI_V4}servedDesiredStatusandKnownStatusas the literalRUNNING, captured at registration, so a container reading its own metadata was told the task was running whileDescribeTasksreported otherwise. Both now follow the task record, withKnownStatusper container as AWS reports it. Contributed by @iot-rocket. - Cognito — a hosted-UI request that invokes a Lambda trigger no longer deadlocks —
/saml2/idpresponse,/oauth2/idpresponseand/oauth2/tokenran on the event loop, so a trigger calling back into MiniStack queued behind its own request and timed out as a400. All three now run off the loop, and a replayed authorization code getsinvalid_grantinstead of a500. Contributed by @kjdev. - SQS — every batch action validates the request before its entries — the ten-entry limit reached
SendMessageBatchonly, and three more request-level errors the model declares on all three batch actions were never raised.DeleteMessageBatchandChangeMessageVisibilityBatchnow refuse an empty batch, more than ten entries, repeated ids and malformed ids, failing the whole request rather than landing inFailed. Contributed by @CaptainAni187. - Persistence — the state map is derived from
SERVICE_REGISTRY— it was a hand-maintained dict alongside the registry, andcloudcontrol,config,curandorganizationswere never added to it, so everything they held was dropped on every warm boot. The save map and the reset set now come from the registry, including the modules a service reaches through its own dispatcher, and a test refuses any service that would be saved with no way to restore it. Contributed by @jgrumboe. - IAM — a resource-scoped policy matches, instead of denying — under
AUTH=truethe resource ARN resolved to*for whole families of request, so onlyResource: "*"matched and everything narrower was denied. IoT jobs, provisioning templates, both IoT data planes, multi-level MQTT topics and API Gateway invokes now resolve to their real ARNs. Contributed by @iot-rocket. - CloudFormation —
AWS::IAM::Policyis provisioned inline, not as a managed policy — the document was stored as an account-global managed policy and attached, where CloudFormation embeds it on each Role, User and Group it names. CDK derivesPolicyNamefrom the construct path, so stacks sharing a path collapsed onto one record and each deploy took the grants from the ones before it.ListRolePoliciesandGetAccountAuthorizationDetailsnow report it and the IAM evaluator finds it. Contributed by @iot-rocket. - Lambda — SQS event source batches are dispatched concurrently — one thread polled every mapping and waited for each invoke, so a slow handler held messages on unrelated queues, and
ScalingConfig.MaximumConcurrencywas stored but ignored. Batches now run off the poll thread, up toMaximumConcurrencyper mapping, with a FIFO event source held to one at a time so message groups keep their order. Contributed by @ThailerL.
Internal
- Test harness — the suite runs on a non-default
GATEWAY_PORT— eight tests compared a value a service had built fromGATEWAY_PORTagainst a hard-coded4566, so the suite passed only on the default port, which is exactly the port a contributor moves off to avoid resetting a shared server. Contributed by @iot-rocket.