v0.0.1-alpha.37
Pre-releaseOvercast v0.0.1-alpha.37
Docker Images
Full image with the web management console:
- Pull:
ghcr.io/neaox/overcast:0.0.1-alpha.37 - Moving tag:
ghcr.io/neaox/overcast:alpha - Registry: overcast package
Headless slim image for CI pipelines:
- Pull:
ghcr.io/neaox/overcast-slim:0.0.1-alpha.37 - Moving tag:
ghcr.io/neaox/overcast-slim:alpha - Registry: overcast-slim package
docker run --rm -p 4566:4566 -p 4567:4567 ghcr.io/neaox/overcast:0.0.1-alpha.37
docker run --rm -p 4566:4566 ghcr.io/neaox/overcast-slim:0.0.1-alpha.37Native Binaries
Download a binary for your platform from the assets below and verify it with SHA256SUMS.
| Asset | SHA256 |
|---|---|
overcast-darwin-amd64 |
6a9bb5e5293b2e0a1c9576a2eea9dbff9d1063e8c42c8ac20066c3d18f6185eb |
overcast-darwin-arm64 |
e1577756fb25fe0ef6a43d4da0ba11fef4a132a91c0823ce6f7400b069927a5c |
overcast-linux-amd64 |
91108ec5f743e46ad11d330cfeb49331ee40b3eb51ceef22f23ada342bfdb584 |
overcast-linux-arm64 |
ab4e2e8cac426bbdeb8c4c879de0aa9eaf7e82a9694d39d0a84a2ccec2367447 |
overcast-windows-amd64.exe |
ba2998f0ebaaf21fdb8a41fdd6a25541d3f77bf2fa9979d8d9f2e2f4a1892ea3 |
overcastd-darwin-amd64 |
4822ffaf5deacf352e1e72236f4384d680802dffe507eb1b1d5868bbb732a24d |
overcastd-darwin-arm64 |
ae8a07a486ec0a16644ec57269d4f2c47344eba42039cadc88920bf5f41ef103 |
overcastd-linux-amd64 |
493429a1968d5a3492d66b4a26eecd7f5acf929a530864381467f5a29fed75ac |
overcastd-linux-arm64 |
62989659ded2dac954244aa4f6db578381f103b14f43f41b09b2af965e146a77 |
overcastd-windows-amd64.exe |
95e546c5a02f26f47779377b53eade1e2bf00263822e1be12c6ec2bc32ad0b43 |
Release Notes
Added
-
[athena]
CreateWorkGrouppersistsConfigurationandGetWorkGroupreturns it; it was previously discarded -
[backup/cloudwatch-logs] tag operations for the resources that had none. Backup gains
TagResource/ListTags/UntagResource(POST /tags/{ResourceArn},GET /tags/{ResourceArn},POST /untag/{ResourceArn}), with tags stored inline on the vault or plan record so they die with it, and the CloudFormation provisioner forwardsBackupVaultTags/BackupPlanTagsand reconciles a tag-only stack update in place. CloudWatch Logs gains the modernTagResource/UntagResource/ListTagsForResourcetrio for log groups, resolving a resource ARN to the log group name and sharing the existingTagLogGroup/UntagLogGroup/ListTagsLogGroupspelling's validation and storage rather than duplicating it -
[ci] a
Lockfile freshnesscheck fails a pull request whoseweb/pnpm-lock.yamlwas generated against amainthat has since changed its own copy, and a sweep on every lockfile push tomainre-judges the open pull requests so a stale one is blocked before it merges rather than after — the case that kept every PR red for 28 minutes on 2026-08-22. It is not a merge queue: two lockfile PRs auto-merging in the same instant can still race -
[ci] a pruned Smithy shape snapshot under
models/aws/shapes/, regenerated and digest-verified alongside the AWS operation manifest -
[cloudformation]
ContinueUpdateRollbackis now supported, so a stack inUPDATE_ROLLBACK_FAILEDis recoverable rather than a stack you have to delete and redeploy. That state is reached when an update fails and the automatic rollback fails too — usually because both were blocked by the same thing outside the stack, a host port already bound or a resource something else is still holding — and it has no last known stable state, so every laterUpdateStack,ExecuteChangeSetand change set has nowhere to start from; previously the operation returned 501, and the only way forward wasdelete-stackand a deploy from scratch. Clearing the blocker and calling it now resumes the rollback: the deletes that failed are retried, what the failed attempt half-created is retired, and the stack reachesUPDATE_ROLLBACK_COMPLETEand is updatable again. A retry that meets the same blocker lands back inUPDATE_ROLLBACK_FAILEDand can be tried again. Nested stacks are continued before their parent, so a wedged child is recovered rather than reported rolled back from above.ResourcesToSkipis honoured for a resource that cannot be cleaned up at all, AWS'sNestedStackName.ResourceLogicalIDform included: the resource is left physically untouched, reportedUPDATE_COMPLETE, and the rest of the stack rolls back around it. Any other stack status gets AWS'sStack:<arn> is in <status> state and can not be updated., and everyResourcesToSkipmember is validated before the operation is accepted -
[cloudformation]
AWS::CloudTrail::Trail,AWS::Transfer::Server,AWS::Transfer::User,AWS::IAM::ManagedPolicyandAWS::IAM::InstanceProfilenow merge a stack's own tags onto the resources they create, and reconcile a stack-tag-only change on update, matching every other propagating resource type -
[cloudformation/web] a deployed stack now says which resources are stubs or backed by an inert/stub-tier service, on the same
ResourceStatusReasonCloudFormation already exposes and in the console's stack view, instead of only a server log line nobody sees -
[cloudwatch] CloudWatch answers Smithy RPC v2 CBOR. The pinned model declares
rpcv2Cborfor CloudWatch — the only dispatched service it declares it for — and a newer AWS SDK major negotiates it for a service that declares it, but Overcast claimed the protocol nowhere: its Smithy RPC registration carried no protocol service, so all fifteen dispatched operations answered501 Not ImplementedoverPOST /service/GraniteServiceVersion20100801/operation/<Op>while the identical call over the JSON or Query protocol worked. All fifteen now answer over CBOR, from the same implementation the other two protocols use — the request and response shapes and every validation rule are shared, so a call returns the same result whichever protocol an SDK picks, andPutMetricAlarmstill marks the alarms Overcast will not evaluate. The router's protocol-symmetry gate consequently exercises CloudWatch over CBOR for real, which it could not before: an operation added to the Query and JSON tables without a CBOR binding now fails the build instead of quietly becoming a fourth protocol gap -
[cognito]
LambdaConfigtriggers (PreSignUp,PostConfirmation,PreTokenGeneration,PostAuthentication,CustomMessage) are now invoked duringSignUp,ConfirmSignUp,AdminCreateUser,AdminConfirmSignUp,ResendConfirmationCode,ForgotPassword,ConfirmForgotPassword,InitiateAuth,AdminInitiateAuthandRespondToAuthChallengeon the classicX-Amz-TargetAPI, through the same synchronous Lambda invoker Secrets Manager rotation, API Gateway and AppSync already use. A trigger that errors or is unreachable fails the call withUserLambdaValidationExceptioninstead of being silently skipped, exceptPostConfirmation, which AWS documents as fire-and-forget since the account is already confirmed by the time it runs.CUSTOM_AUTH'sDefineAuthChallenge/CreateAuthChallenge/VerifyAuthChallengeResponseremain tracked separately (#88, #94, #101);PreAuthentication,UserMigrationand the Smithy RPC v2 duplicate dispatch path's non-token triggers are not yet wired -
[compat] four new registry groups.
rds-clusterscovers the Aurora DB cluster lifecycle — create, describe, modify, stop, start, delete — andrds-cluster-memberscovers a cluster with an instance in it:DBClusterMembers, writer election, the settings a member inherits from its cluster, and the membership pruningDeleteDBInstancedoes. Both run in the Go, Python, Node and Java SDK suites and the CLI suite, over ground nothing outside the emulator's own Go tests had ever looked at, and they are what found the cluster response-envelope and membership fixes below.eventbridge-patterns(TestEventPattern,TestEventPatternNoMatch) andlambda-invoke-error(a handler that throws must come back as HTTP 200 withFunctionErrorset) are implemented in all seven SDK/CLI suites;eventbridge-patternsis the dotnet-sdk and rust-sdk suites' first EventBridge coverage -
[config] the startup log names the resolved bind address(es), says that
OVERCAST_LISTENis what changes them, and says why the default was chosen — containerised or native -
[config] Overcast warns once at startup when
OVERCAST_HOSTNAME's subdomains do not resolve on this host — a resolver limitation (Windows does not resolve*.localhostthe way Linux does) that otherwise surfaces far from its cause: virtual-hosted-style S3 addressing breaks silently, andcdk deployasset publishing fails with a baregetaddrinfo ENOTFOUNDon a bucket-shaped hostname. The probe is two DNS lookups run off the startup critical path — the bare hostname, then a synthetic subdomain of it — and only warns, never fails, since path-style S3 addressing is unaffected; the message also flags the case where a custom hostname is resolved only through a hosts file, where the warning is an expected false positive rather than a bug -
[config] LocalStack's environment variables are accepted as compatibility aliases for their Overcast equivalents.
LOCALSTACK_HOSTmaps toOVERCAST_HOSTNAME— LocalStack's documentedhostname[:port]format is parsed and a port part must matchOVERCAST_PORT— andEDGE_PORT,GATEWAY_LISTEN,DEFAULT_REGION,DATA_DIR,DEBUG,PERSISTENCE,HOSTNAME_EXTERNALandLAMBDA_RUNTIME_ENVIRONMENT_TIMEOUTmap to the matchingOVERCAST_*/LAMBDA_*setting, completing the drop-in-replacement audit.SERVICES,LOCALSTACK_API_KEYandLOCALSTACK_AUTH_TOKENare recognised and logged once at startup as inert rather than silently ignored. Every alias shares one mechanism and one conflict rule: a value that disagrees with an explicitOVERCAST_*setting fails startup naming both, rather than silently preferring one.docs/migration-from-localstack.mdrecords which further variables were deliberately not aliased and why —LAMBDA_DOCKER_NETWORK,LAMBDA_KEEPALIVE_MS, the CORS variables,EAGER_SERVICE_LOADING,SNAPSHOT_*— rather than half-mapping them -
[devex] Overcast warns, once per session, about four more environment mistakes that read exactly like emulator bugs: Docker unreachable for one or more container-backed services (naming which, and why); a request addressed to a real AWS hostname answered by Overcast instead (a stray hosts-file or DNS redirect); the API's published port differing from its listen port in a way URL rewriting cannot fix (a value compared rather than dialled); and memory-only storage about to silently ignore a database that already has data in it. Each fires as one actionable WARN at the moment the symptom appears, never speculatively on a healthy setup — see the new Troubleshooting section in
docs/README.md -
[ecs] a service's
deploymentConfigurationnow decides how many tasks its rollout runs at once.maximumPercentandminimumHealthyPercentwere stored and echoed back byDescribeServicesbut never acted on, so every rolling update was a start-then-stop whatever the service asked for — and that is the ordering a shared Docker host cannot always take. A task whose port mapping carries ahostPortpublishes it on the one host, so while both deployments are alive they contend for it and the replacement cannot start; on AWS eachawsvpctask has its own ENI and two tasks never collide. The settings now resolve againstdesiredCountas AWS resolves them —maximumPercenta ceiling on tasks that have not stopped, across both deployments, rounded down;minimumHealthyPercenta floor on tasks reportingRUNNING, rounded up — and the superseded tasks are retired before the replacements are placed when the floor allows it.maximumPercent: 100withminimumHealthyPercent: 0(CDK'smaxHealthyPercent/minHealthyPercent) therefore deploys such a service cleanly, at the cost of a moment of downtime, which is the same trade AWS offers. The defaults are unchanged at AWS's200/100, so a service that sets neither deploys exactly as before.100/100cannot make progress at any desired count — nothing may be retired and nothing more placed — and AWS stalls there silently; Overcast stalls too but says once why, with a routine service event CloudFormation will not mistake for a stack failure -
[eventbridge]
TestEventPatternis implemented. It evaluates an event against a pattern with the same matcherPutEventsuses to select rules, so a pattern that passes here is one a rule would fire on; an unparseable pattern is the documentedInvalidEventPatternExceptionrather than a silentResult: false. It used to return 501 -
[eventbridge/ec2/ecs/stepfunctions] state changes now publish AWS-shaped events onto the default bus, so a rule matching one actually fires.
EC2 Instance State-change Notificationcomes fromRunInstances,StartInstances,StopInstances,TerminateInstancesand the scheduler callbacks that fast-forward pending/stopping;ECS Task State ChangefromRunTask,StopTaskand container-exit-driven stops; andStep Functions Execution Status Changefrom a standard workflow's initial RUNNING record, its SUCCEEDED/FAILED/TIMED_OUT/ABORTED terminal write, andStopExecution's direct ABORTED write — EXPRESS workflows are excluded, matching AWS -
[inert]
internal/inert/conformanceturns the inert contract into executable, table-driven conformance tests for future Tier 1 services, with a naive-stub meta-test that pins the exact set of clauses a sloppy hand-rolled emulator violates -
[lambda] extensions now start for container-image functions too. External extensions in
/opt/extensionsused to be launched by a shell shim that was only injected into zip-packaged containers; the in-container init launches them for every function, whatever it is packaged as, and tags each one's output with the extension's own name — so a Logs API subscriber receives them asextensionrecords without the[overcast-extension:<name>]prefix Overcast used to write into the line -
[lambda] the
platform.initStart,platform.initRuntimeDoneandplatform.initReportTelemetry API records, with real timings. The in-container init is PID 1 in the execution environment and the proxy in front of the Runtime API, so it observes both ends of the INIT phase — the runtime being spawned, and the runtime's firstGET /next— and reports them on the same sequence-ordered stream as the container's output. UnderLogFormat: JSONthey reach CloudWatch in the position AWS puts them in:initStart→ the INIT phase's own output →initRuntimeDone→initReport→START, filtered bySystemLogLevelat AWS's documented levels (all three areDEBUGfor a successful on-demand cold start,INFOfor the report of a provisioned environment,WARNwhen the phase failed). Extensions subscribed to the Telemetry/Logs API receive them whatever the log format is, including the ones published before the extension's own process existed. Text-format log streams are unchanged, as they are on AWS. -
[metrics/lambda/sqs/sns/dynamodb/apigateway/cloudwatch] emulated services now publish the AWS-native CloudWatch metrics real AWS publishes, so an alarm configured against one evaluates and fires end to end instead of silently never triggering. Lambda records
Invocations,Errors,Duration,ThrottlesandConcurrentExecutionsonce per invocation at the shared outcome boundary every path — sync, async, function URLs, event source mappings — already funnels through, plus the asynchronous set:AsyncEventsReceivedfor every accepted Event invocation,AsyncEventAgeper retry attempt,AsyncEventsDroppedwhen an event outlives itsMaximumEventAgeInSeconds, andDeadLetterErrors/DestinationDeliveryFailureswhen a failed invocation's dead-letter or destination delivery itself fails. SQS recordsNumberOfMessagesSent/NumberOfMessagesReceived/NumberOfMessagesDeleted/NumberOfEmptyReceives/SentMessageSizeper operation, plus a once-a-minute gauge sampler forApproximateNumberOfMessagesVisible/NotVisible/DelayedandApproximateAgeOfOldestMessagepublished for every active queue even with no traffic, as AWS does. SNS recordsNumberOfMessagesPublished/PublishSizeonPublish/PublishBatchandNumberOfNotificationsDelivered/NumberOfNotificationsFailedper subscription delivery attempt. DynamoDB recordsSuccessfulRequestLatency,ConsumedReadCapacityUnits/ConsumedWriteCapacityUnits(withGlobalSecondaryIndexNamewhere applicable) andUserErrors/SystemErrorsacrossPutItem/GetItem/DeleteItem/UpdateItem/BatchGetItem/BatchWriteItem/Query/Scan/TransactWriteItems/TransactGetItems. API Gateway recordsCount/4XXError/5XXError/Latency/IntegrationLatencyonce per dispatched request, for both REST (v1,ApiName+Stage+Method+Resource) and HTTP (v2,ApiId+Stage+HttpMethod+RouteKey) APIs. All of it routes through one read-through and alarm evaluator, soGetMetricStatistics,GetMetricDataandListMetricsreturn the automatic series alongside customPutMetricDatadata. The store is a sharded in-memory hot path with dedicated SQLite tables where available, keeping a 1-minute tier for 24 hours alongside 300-second (7-day) and 3600-second (30-day) rollups, so a 6h/24h/7d/30d chart has real data rather than only the last hour — and the whole substrate now survives a restart onOVERCAST_STATE=waland every-tags nosqlitebuild, the one gap every earlier phase disclosed (OVERCAST_STATE=memoryis unaffected: there is nothing for it to restart into).OVERCAST_SERVICE_METRICS=auto|enabled|disabledcontrols collection, defaultauto -
[organizations] policies are stored for real —
CreatePolicy,DescribePolicy,UpdatePolicy,DeletePolicyandListPolicies, withTagResource,UntagResourceandListTagsForResourceover the same tags. A policy's ID and ARN are derived from its name and stable across restarts,ListPoliciesfilters by the required type and rejects an invalidNextTokenrather than restarting at page one, and the modeled errors are returned by name. Nothing a policy describes takes effect:AttachPolicystill returns 501, so no policy is ever attached or enforced. Tagging accepts policy IDs only — a root, OU or account ID returnsTargetNotFoundException, since none of those are stored yet -
[route53] Overcast's own DNS resolver now answers real queries from a hosted zone's records instead of storing them inertly. A/AAAA/CNAME (chased across zones, up to 8 hops)/MX/TXT/NS/SOA are served, including the apex NS/SOA every zone gets on creation; one-label wildcards (
*.example.com) match exactly as AWS scopes them; TTLs come from the record's own stored value. ALIAS records resolve to Overcast's own reachable address, since that is the only correct answer for an alias pointed at an emulated ELB/CloudFront/S3-website/API Gateway hostname in this environment. NODATA and NXDOMAIN are distinguished correctly, both carrying the zone's SOA for negative caching. A hosted zone's records always take precedence over Overcast's own split-horizon emulator hostnames when both would claim a name. Private zones answer for every container (no VPC-association filtering, a documented deliberate simplification); public zones are still never advertised to the internet. Seedocs/services/route53.mdfor the full behaviour and documented divergences -
[web] list and tabbed pages that already had a filter box now keep it in the URL (
?q=) — AppSync, Cognito, KMS, SSM, Step Functions, and IAM/EventBridge, which also gained a deep-linkable?tab=— so a filtered or tabbed view is shareable and survives a reload, and Back/Forward works; typing debounces into one history-replacing navigation instead of one per keystroke, following the same contract Request Traces' filters already used.ResourceTable/QueryListStatealso gainedisFiltered/onClearFilter, so the empty state distinguishes "nothing exists yet" (create action) from "nothing matches the filter" (clear-filter action) instead of each page hand-writing that ternary, and a load error still always wins over both -
[web] a copy control on the identifiers a detail page shows — ARNs, ids, domain names, lock tokens — named after the field it copies
-
[web/lambda/sqs/sns/dynamodb] Lambda function, SQS queue, SNS topic and DynamoDB table pages have a Monitor view charting the metrics the emulator now records automatically, over 1h/6h/24h/7d/30d ranges: Lambda's Invocations, Errors, Duration (average and maximum), Throttles and Concurrent Executions; SQS's messages sent, received, deleted and empty-receives plus the queue-depth and oldest-message-age gauges; SNS's messages published, publish size and notification deliveries/failures; and DynamoDB's consumed read and write capacity. Each reads through a narrow per-service allowlist endpoint —
GET /_overcast/lambda/functions/{name}/metrics,/_overcast/sqs/queues/{name}/metrics,/_overcast/sns/topics/{topicName}/metrics,/_overcast/dynamodb/tables/{name}/metrics, never the CloudWatch protocol — and the matching BFF proxy under/api/
Changed
-
BREAKING [lambda]
LAMBDA_PROACTIVE_INITnow defaults totrue: after a settled function has trigger evidence (an invocation this process, a function URL, or an event source mapping), one execution environment is created in the background ~10 s after its last identity-changing write, so the next request lands warm instead of paying a cold start. This is the flip deferred from the cold-start plan after a release of opt-in soak surfaced no regressions. Proactive creation reserves capacity through the same admission budgets (instance count, memory including the high-water margin) a real invocation would, non-blockingly, so it can never queue ahead of real traffic; it never runs for functions with provisioned concurrency, an existing environment, or reserved concurrency of zero
migration: setLAMBDA_PROACTIVE_INIT=falseto opt back out and restore the previous on-demand-only behaviour -
BREAKING [networking/ec2] the control plane is now created
--internalwhenever Overcast can tell it is safe — Overcast is itself containerised, or is running on a native Linux Docker daemon — closing the one documented egress path a gateway-less VPC's containers had: every VPC-placed resource without an internet gateway now genuinely has no route out, matching a private subnet with no NAT gateway. A native Overcast on a Docker Desktop host is unaffected — the probe declines there and the control plane stays an ordinary bridge; a containerised Overcast qualifies wherever it runs, Docker Desktop included
migration: a Lambda function or ECS task in a VPC with no internet gateway that reached the real internet through this leak (a package fetch, an external HTTP call at cold start) now times out the way it would on AWS — attach an internet gateway, or setPubliclyAccessible(RDS) /assignPublicIp: ENABLED(ECS) perdocs/networking.md, to restore reach -
BREAKING [rds] the Aurora writer endpoint is now
{cluster}.cluster.{region}.rds.{base}, not{cluster}.cluster-rw.….cluster-rwwas a label AWS has never used; dropping the account-specific hash from AWS's own{cluster}.cluster-{hash}.…, as every Overcast endpoint name does, leaves a barecluster. The reader endpoint is unchanged
migration: nothing to do if you read the endpoint fromDescribeDBClustersor CloudFormation — the new name arrives on the next deploy. If you hard-coded or reconstructed…cluster-rw.…anywhere (task definition, secret,.env), replace it with the value the API returns; the old spelling keeps resolving for an existing cluster and stops once that cluster is recreated -
[cloudformation]
AWS::IAM::RoleandAWS::IAM::Usernow also merge the stack's own tags, instead of only their own resource-levelTags -
[dev]
scripts/docker-go.sh,scripts/docker-go.ps1andscripts/go.sh's Docker fallback take their default image from.devcontainer/Dockerfile'sFROMline (golang:1.26-bookwormtoday) instead of a hardcodedgolang:1.24-bookworm, which had stopped working oncego.modmoved to Go 1.25 (go: go.mod requires go >= 1.25.0 (running go 1.24.13; GOTOOLCHAIN=local));OVERCAST_GO_IMAGEstill overrides -
[docs] the Tier 0 / Tier 1 (inert) / Tier 2 operation-level vocabulary is documented in
CONTRIBUTING.mdalongside the existing service-level tiers, and the inert-tier rollout plan's status header, capability census and backfill table now match the code after phases I0–I2 landed — including the two premises (#1369 classification, #1373 error selection) that failed re-measurement and gate phase I3 -
[ec2] all 69 of EC2's registered typed operations now answer from the typed operation registry, which the service had registered but never dispatched to. Each shares one body with the legacy handler rather than carrying a second implementation that had fallen behind it — the typed twins of the twenty-one
Describe*operations decoded no filters at all, so every one of them would have answered with the whole region had it ever been reached. Twelve mutations whose typed body validated a parameter and returned success without touching the store (TerminateInstances,StartInstances,StopInstances,DeleteTags,DeleteVpcEndpoints,ModifyInstanceAttribute, the fourAuthorize/Revoke``SecurityGroup*,ModifySubnetAttribute,ModifyVpcAttribute) now perform the write, and five creates that dropped part of their input (RunInstances' security groups and tags,CreateVpc's DNS-support default,CreateSubnet/CreateSecurityGroup's lifecycle events,CreateTags) now carry all of it.DeleteVpc/DeleteSubnet/DeleteSecurityGrouprun their dependency checks on both dispatch paths, so aDependencyViolationanswer is the same whichever one served the request. Two differential tests — one for reads, one for mutations, seeded on two independent handlers since a mutation cannot be replayed against the same store — hold every routed operation to the same bytes and the same store effect as its legacy twin. No response changes for an existing caller: legacy answered these calls before and answers the identical bytes now -
[ecs] a task's
awslogsoutput is followed with reconnect, exact de-duplication of the lines a timestamp-based resume replays, and batched CloudWatch writes, so a broken daemon connection no longer silently ends a task's logs; event timestamps are the container's own, not the host's arrival time -
[lambda] a function's
X-Amz-Log-Resulttail now holds exactly that invocation's own lines, and its CloudWatch stream is ordered START / handler output / END / REPORT exactly, on every invoke rather than only on tail-requesting ones. The function's stdout and stderr are now owned by an init process inside the container — the same process model AWS uses, where RAPID is the parent of the runtime and serves the Runtime API on127.0.0.1:9001. Because the init reads every line itself, it knows which invocation was in flight when the line was written, and it drains both pipes before it forwards the runtime's response; Overcast is told where an invocation's output ends instead of inferring it from Docker's log stream going quiet.docker logson a function container is unchanged — the init tees every line to the container's own stdout and stderr byte for byte — with the init's own[overcast-init]diagnostics interleaved on stderr, which never reach CloudWatch, the tail or the Telemetry API. Inside the containerAWS_LAMBDA_RUNTIME_APInow carries the AWS value127.0.0.1:9001, which is the init's proxy; the per-execution-environment endpoint on the host is passed separately asOVERCAST_RUNTIME_APIand is read only by the init. Nothing outside the container changes: the Runtime API, the extensions API, the Telemetry/Logs API,LAMBDA_RUNTIME_API_PORTand every response shape are as they were. The init is delivered in a named volume seeded once per Overcast process rather than copied into each container, so a cold start is within ~10 ms of what it was; its CPU comes out of the function's allocation, as AWS's own init does, which is visible as occasional ~80 ms stalls when a 128 MB function is driven with no think time at all (see docs/services/lambda.md) -
[lambda] platform records reach Telemetry/Logs API subscribers as the event AWS documents — the event's own
type(platform.start,platform.report, …) and an objectrecord— instead of a"platform"-typed event whose record was the log line as a string. CloudWatch output andX-Amz-Log-Resulttails are unchanged in both log formats. -
[server] cleartext HTTP/2 (h2c) is served through the standard library's
http.Server.Protocolsinstead of the deprecatedx/net/http2/h2cwrapper; prior-knowledge HTTP/2 and HTTP/1.1 behave as before, and the rarely usedUpgrade: h2chandshake now continues on HTTP/1.1 -
[web/ci] the console's server-side TypeScript types are generated from the Go response structs by
cmd/tsgenintoweb/src/types/api.gen.ts, andmake check-tsfails CI when the two drift — a Go field rename can no longer ship a console readingundefined. Health, metrics, debug metrics, advisories, the event stream and diagnostics provenance came first; the Map page's topology graph, the Inbox's captured messages and the request-tracing payloads (trace detail, list, count and search) have since joined them, and their hand-written mirrors are gone.trace.Entry/Hopnow marshal through package-level wire structs thatcmd/tsgenrenders — the generator will not render a type that has its ownMarshalJSON, so a struct can no longer be described as a shape it never sends -
[web] every colour in the web UI now resolves through a design-system token. Service identity, map node and startup-phase colours move onto the ten-slot categorical ramp (
--cat-1..10); state colours collapse onto--success/--warning/--danger/--accent; the debug JSON viewer adopts the shared syntax tokens. Each token carries its own light and dark value, so the 429 raw Tailwind hue classes that rendered one fixed colour on both themes are gone, and a test fails the build on the next one -
[web] the SES identity and API Gateway API-key delete confirmations now use the shared dialog, adding the "esc to cancel" keyboard hint
-
[web/iam] the Groups tab's expanded member list renders in its own panel below the group table instead of as an extra row inserted under the clicked group — the shared list-table kernel (
ResourceTable) has no row-expansion concept, and this is the same below-the-table pattern API Gateway's usage-plan key list already used -
[web] resource list tables sort by clicking a column header, and hide columns from a Columns menu. The wave covers CloudFront's six lists (Distributions, Continuous Deployment Policies, Field-Level Encryption Configs and Profiles, Key Groups, Realtime Log Configs); the Lambda function and layer lists, the ECS cluster list, the Auto Scaling group list and the Applications list — by name and by the counts and timestamps each page carries (memory, timeout, running tasks, capacity, latest version, created); DynamoDB, RDS, ElastiCache, EFS, S3 and ECR; API Gateway's REST and HTTP API lists, the WAF Web ACL list and the Cognito user pool list; CloudFormation, CloudWatch Logs, SSM, Step Functions and Secrets Manager; and SQS, SNS, Kinesis, MSK and Pipes — name on every page, plus message counts and visibility timeout on Queues, shards and retention on Streams, brokers on Clusters and creation time on Pipes. The sort is deep-linkable as
?sort=name/?sort=-name(or?sort=id,?sort=-created) on the KMS, SSM, AppSync, Step Functions, CloudFront, DynamoDB, RDS, ElastiCache, EFS, S3, ECR, WAF and Cognito pages and on the CloudFormation Stacks and CloudWatch Log Groups index pages, alongside the filter'sq— so a sorted view survives a reload and can be shared. Distributions, the ECS cluster and Auto Scaling group lists, RDS, ElastiCache, EFS, Log Groups and Queues also gain the Columns menu for their wider tables. Moving the API Gateway, WAF and Cognito lists ontoResourceTableis what gives them a real loading skeleton, a load-error state and a delete confirmation identical to every other list in the UI -
[web] the tables inside detail pages sort the same way, and now show the app's skeleton rows while loading and its standard empty state when there is nothing to list, instead of a bare spinner or a "None defined." line that meant in-flight, empty and failed alike. Covered: the Lambda layer, ECS cluster, Applications, EC2 instance and EC2 VPC pages (versions, tasks, services, container instances, subnets, route tables, endpoints, security groups and tags); a distribution's Origins, Origin Groups and Invalidations; the ECR images table and DynamoDB's secondary-index tables; API Gateway's routes, integrations, stages, deployments and authorizers, AppSync's data sources, resolvers, functions, API keys and schema types, and Cognito's users and group members; a stack's parameters, outputs, tags and resources, a log group's streams, an SSM parameter's version history, a state machine's executions and a secret's rotation versions — the last three now newest-first rather than trusting the order the API returned them in; and the SQS queue, SNS topic, Kinesis stream, Pipes pipe and EventBridge bus sub-tables, where the bus page's rule list also tells "nothing here yet" apart from "nothing matches your filter". The REST API's resource tree and the Cognito user's attribute grid stay hand-rolled: the first expands a row into per-method rows, the second is an editable name/value grid rather than a resource list
-
[web] the last hand-built attribute grids — Applications, CloudFront's distribution configuration and monitoring panels, the ECS primary-deployment summary, WAF's Web ACL detail and Cognito's pool, app-client and user rows — render through the shared definition table, so every detail page's field labels, value typography and unset-field em dashes match
Fixed
-
BREAKING [acm/apigateway/appregistry/appsync/cloudformation/cloudfront/cognito/dynamodb/ecr/efs/glue/iam/kinesis/kms/msk/secretsmanager/stepfunctions] every one of the seventeen services that had a tag-accepting operation reaching AWS's own tag constraints in name only now routes it through the shared
serviceutil.ValidateTags— a reservedaws:key prefix, an over-length key or value, or more than 50 tags on one resource is refused instead of silently stored, on everyTagResource-style operation and everyCreate*'s inlineTagsmember these services carry. Three of the seventeen (Cognito, Glue, Step Functions) were already fixed and needed no change
migration: fix any tags on these services that use a reservedaws:key prefix, exceed the 128/256-character key/value length limit, or exceed 50 tags on one resource — Overcast previously accepted and silently stored these; AWS itself has always rejected them -
BREAKING [backup]
BackupVaultTags/CreateBackupVaultandBackupPlanTags/CreateBackupPlanare now validated and stored instead of being silently accepted and dropped, so a vault or plan tagged with a reservedaws:key prefix, an over-length key or value, or more than 50 tags is refused instead of succeeding with the tags discarded
migration: fix anyBackupVaultTags/BackupPlanTagsthat use a reservedaws:key prefix, exceed the 128/256-character key/value length limit, or exceed 50 tags on one resource — Overcast previously accepted and silently dropped these; AWS itself has always rejected them -
BREAKING [cloudformation/ecr] ECR repository templates now get their
ImageTagMutability,ImageScanningConfiguration,EncryptionConfigurationandTags. The repository handler's create sent onlyrepositoryNameand appliedRepositoryPolicyText/LifecyclePolicyas follow-ups, dropping the rest even though the ECR service already stored and echoedImageTagMutabilityon both wire paths — a CDK repository withimageTagMutability: TagMutability.IMMUTABLEprovisioned as mutable.ImageTagMutabilityandImageScanningConfigurationnow reachCreateRepositoryon create and are reconciled through two new operations,PutImageTagMutabilityandPutImageScanningConfiguration, on update;TagsreachesCreateRepositoryand is reconciled throughTagResource/UntagResource, the same as the Lambda, LogGroup, Secrets Manager and SQS handlers.EncryptionConfigurationis forwarded on create and, since real ECR has no API to change it later, a template that changes it now replaces the repository instead of an update silently keeping the old encryption.EmptyOnDeleteis honoured on delete:forceis only passed toDeleteRepositorywhen the template saysEmptyOnDelete: true, matching real CloudFormation's default of failing to delete a repository that still holds images
migration: a stack relying on the old always-force delete must setEmptyOnDelete: trueon the repository to keep deleting it while it still holds images -
BREAKING [cloudformation/elasticache]
AWS::ElastiCache::CacheClustertakes its cluster name fromClusterName, the resource's own property. It readCacheClusterId— what the API calls the parameter, and not a property the resource has at all — so a template that named its cluster got one named after the stack instead, and every endpoint the stack advertised belonged to a cluster nobody had asked for. An unnamed cluster now gets CloudFormation's generated name rather than<stack>-cache, which carried neither the logical ID nor the random suffix: two unnamed clusters in one stack were handed the same ID, and the second create failed on a name the template never mentions. The generated name is lowercased and capped at 50 characters, which is what ElastiCache accepts in a cluster ID
migration:CacheClusterIdon this resource was never valid CloudFormation and is now ignored. Spell itClusterNameto keep the cluster's name; leave it and the next change to that resource replaces the cluster with the generated one. Stacks already deployed keep the physical ID they were given until something replaces the resource -
BREAKING [cloudformation/elasticache]
Fn::GetAtton a cache cluster resolves the endpoint pair the engine has —RedisEndpoint.Address/.Portfor Redis and Valkey,ConfigurationEndpoint.Address/.Portfor Memcached, as AWS populates them. Only theConfigurationEndpointpair was exported, whatever the engine, and aGetAtton an attribute a handler does not export does not fail: it falls back to the physical ID. CDK'sattrRedisEndpointAddresstherefore handed out a bare cluster ID as a hostname, which no container advertises and nothing can resolve, so an ECS task withREDIS_HOSTbaked in at deploy time died ongetaddrinforather than reaching the cache. The pair the engine does not have resolves empty, so a template reading the wrong one gets the same nothing it would get on AWS
migration: a Redis or Valkey cluster'sConfigurationEndpoint.Addressis now empty rather than carrying the endpoint, which is what AWS answers for it. ReadRedisEndpoint.Address— CDK'sattrRedisEndpointAddress— as code written against AWS already does -
BREAKING [cloudformation] a
DELETE_COMPLETEstack is no longer resolvable by name onDescribeStacks,DescribeStackEvents,ListStackResources,DescribeStackResources,GetTemplateandGetTemplateSummary— each now answersValidationError: Stack with id <name> does not exist, matching AWS's rule that a deleted stack is addressable only by its unique stack ID once its name is free to reuse. The stack ID (ARN) captured at create still resolves the deleted stack's final state and events, as does a stack mid-DELETE_IN_PROGRESSlooked up by name; only the terminal record excludes the name path. Mutations already treatedDELETE_COMPLETEas nonexistent; this closes the one read-side divergence that was deliberately left open
migration: a client that polledDescribeStacksby name to observe a delete finishing (rather than by the stack IDCreateStackreturned) now gets aValidationErrorinstead of aDELETE_COMPLETEbody once the delete completes — the same as the AWS SDK'sstack-delete-completewaiter, which already treats that error as the terminal success case. Poll by the stack's ID, or treat the does-not-exist error as "deleted" the way the waiter does -
BREAKING [cloudformation/cloudtrail]
AWS::CloudTrail::Trailnow reads the trail name fromTrailName, the real resource schema's property, instead ofName
migration: renameNametoTrailNamein any template using this resource; a template still settingNamegets an auto-generated trail name instead of a validation error, since Overcast has no per-property unrecognised-property diagnostic -
BREAKING [cloudformation/kms]
AWS::KMS::Keyused to provision successfully and quietly keep the AWS default whenever a template setOriginto anything other thanAWS_KMS,MultiRegion: true, orEnableKeyRotation: true— the emulator models none of import/CloudHSM origins, the replica-key relationship multi-Region implies, or rotation state.CreateKeynow answersValidationExceptionfor the first two (and both are replacement-required on update);EnableKeyRotation: true(CDK'senableKeyRotation, a common setting) is now dispatched to the KMS service'sEnableKeyRotationoperation, which does not exist, so the resource fails loudly instead of silently reporting rotation off forever — the same "dispatch to the gap" treatmentAWS::IAM::User'sLoginProfilegets
migration: dropOrigin/MultiRegionfrom anyAWS::KMS::Keytemplate that relied on them being ignored, and dropEnableKeyRotation: true(enableKeyRotation: truein CDK) until key rotation is emulated -
BREAKING [cloudformation/athena]
AWS::Athena::WorkGroupnow reads the workgroup configuration fromWorkGroupConfiguration, the real resource schema's property, instead ofConfiguration(theCreateWorkGroupAPI member it maps onto); a template's result location, bytes cutoff and the rest were silently dropped before
migration: renameConfigurationtoWorkGroupConfigurationin any template using this resource; a template still settingConfigurationgets a workgroup with no configuration instead of a validation error, since Overcast has no per-property unrecognised-property diagnostic -
BREAKING [config] native
overcast servenow defaults to binding127.0.0.1instead of0.0.0.0; the containerised default (Docker image,OVERCAST_DATA_DIR_SOURCE=image) is unchanged at0.0.0.0. An explicitOVERCAST_LISTENalways wins over either default
migration: native users reaching Overcast from another machine, a VM, or a phone on the same network must setOVERCAST_LISTEN=0.0.0.0to restore the old reach -
BREAKING [config]
OVERCAST_HOSTis renamed toOVERCAST_LISTEN(LocalStack'sGATEWAY_LISTENidiom) and removed rather than kept as an alias — a leftoverOVERCAST_HOSTnow fails at startup namingOVERCAST_LISTENas the replacement, instead of being silently ignored
migration: renameOVERCAST_HOSTtoOVERCAST_LISTENwherever it is set (env,.envfiles,docker-compose.yml, CI config) — the value format is unchanged, the same comma-separated address list -
BREAKING [ec2]
DeleteSecurityGroup,DeleteSubnetandDeleteVpcnow answerDependencyViolationwhile a dependent (instance, ENI, subnet, gateway, endpoint, peering connection) remains, matching AWS instead of deleting unconditionally; the VPC default security group answersCannotDelete
migration: clear dependents before deleting a security group, subnet or VPC — a template or script that relied on the unconditional delete now sees the same refusal real AWS gives, surfaced by CloudFormation teardown asDELETE_FAILEDinstead of a silent dangling reference -
BREAKING [pipes]
CreatePipe/UpdatePipenow refuses a stream source'sDeadLetterConfignaming a destination that is not an SQS queue or an SNS topic, instead of storing it and only failing once a batch exhausts its retries
migration: pointDeadLetterConfig.Arnat an SQS queue or SNS topic, or remove it -
BREAKING [rds]
CreateDBClusterandCreateDBInstanceenforce the identifier shape AWS documents — 1 to 63 letters, numbers or hyphens, starting with a letter, no trailing or doubled hyphen — and store identifiers lowercase, so a describe finds what a create made whatever case it was sent in
migration: an identifier real RDS would have refused is now refused here too; rename it to match the documented shape. A mixed-case identifier keeps working and comes back lowercased, as it does on AWS -
BREAKING [rds] a
DBSubnetGroupNamenaming no existing subnet group is refused byCreateDBClusterandCreateDBInstance, andDBSubnetGroupNotFoundFaultcarries AWS's documented 404 rather than a 400
migration: create the subnet group before the database that references it, as AWS requires. Code branching on the 400 status rather than the error code should read the code, which has not changed -
BREAKING [rds] cluster
BackupRetentionPerioddefaults to 1 and is held to AWS's documented 1–35 on create and modify;Portis held to 1150–65535 on both
migration: a cluster created withoutBackupRetentionPeriodnow reports 1 rather than 0.BackupRetentionPeriod=0was never a value AWS accepts for a cluster and is now refused; send 1 to mean the minimum -
BREAKING [router/appregistry/efs/eks/scheduler] AppRegistry, EFS, EKS and Scheduler no longer answer
POST /for anX-Amz-Targetheader (AppRegistry.,EFS.,EKS.,Scheduler.) or a Smithy RPC v2 label (/service/<name>/operation/<op>) — a wire the pinned models never gave these restJson1 services and no real SDK ever sends. Both doors now answer the same honest "unknown operation"/"unsupported protocol" error any nonsense request gets, never a 200
migration: address these four services only at their modeled REST bindings, the paths already documented underdocs/services/, the same way any AWS SDK or theawsCLI reaches them today -
[appsync] AppSync's tag validation no longer rejects a digit in a tag key — a locally re-implemented validator predating the shared one was more restrictive than AWS's own documented tag-key pattern and is retired in favour of it
-
[athena/cognito/ec2/ecs/elasticache/eventbridge/firehose/rds/ssm/stepfunctions] create-time tags are no longer dropped. Ten services accepted a
Tagsmember on create and silently discarded it, so a resource had to be tagged again afterwards to be tagged at all:CreateWorkGroup(Athena),CreateUserPool(Cognito, whose request modeled noUserPoolTagsmember at all),CreateCluster/CreateService(ECS),CreateReplicationGroup(ElastiCache),CreateEventBus/PutRule(EventBridge, wherePutRulemerges with whatever the rule already carries, as AWS documents),CreateDeliveryStream(Firehose),CreateDBInstance/CreateDBCluster/CreateDBSubnetGroup(RDS),PutParameter(SSM) andCreateStateMachine(Step Functions, whose request modeled no tags member either). EC2'sTagSpecification.Nwas honoured only byRunInstances,CreateNatGatewayandCreateVpnGateway, and now also byCreateVpc,CreateSubnet,CreateSecurityGroup,CreateInternetGateway,CreateRouteTable,CreateVpcEndpoint,CreateVpcPeeringConnection,CreateNetworkInterfaceandCreateKeyPair— the endpoint and peering-connection responses previously carried no tag set at all and now do. Tags are validated and stored where the tag operations already look, soListTagsForResourcesees them without a follow-up call; SSM applies them only when the call creates a new parameter, matching AWS's rule thatTagscannot update an existing one. ECS'sRegisterTaskDefinitionand ElastiCache'sCreateCacheCluster/CreateServerlessCachealready stored theirs and needed no change, and ElastiCache's independent CBOR typed path is fixed alongside its Query/XML handler. Everywhere else the legacy handler now delegates to its typed counterpart instead of duplicating it, which also fixed a pre-existing Step Functions bug: the typed path minted state machine ARNs with the account default region rather than the request's actual region -
[cloudformation] API Gateway templates now get the stage variables, authorizers and models they declare.
AWS::ApiGateway::Stage'sVariablesandAWS::ApiGatewayV2::Stage'sStageVariablesused to be parsed into nothing on create — the service already readstage.Variablesinto a Lambda proxy integration'sstageVariablesat execution time and already accepted the field on update, but create never sent it, so every integration behind a CDK-declared stage variable saw an empty map. Both now reach the stage on create, and update patches per-key changes in place.AWS::ApiGateway::Method'sAuthorizerIdandAWS::ApiGatewayV2::Route'sAuthorizerIdround-trip the same way — a method or route wired to aCfnAuthorizerno longer loses the association.Method.RequestParametersandMethodResponses(viaPutMethodResponse) are forwarded on create, andAWS::ApiGateway::RestApi'sPolicy,Tags,BinaryMediaTypesandDisableExecuteApiEndpointround-trip on create (the two scalars also on update) —GetRestApipreviously never surfacedPolicyback either, even thoughCreateRestApialready stored it.AWS::ApiGateway::Authorizer,AWS::ApiGateway::ModelandAWS::ApiGateway::RequestValidatorare provisioned resource types now instead of falling through to the unknown-type stub; the apigateway service already implemented all three, nothing in CloudFormation's handler registry ever dispatched to them.RestApi.Body/BodyS3LocationandApiGatewayV2::Api.Body(OpenAPI import) fail the resource loudly instead of silently provisioning a routeless API, since neither is implemented. Remaining gaps —Method.RequestModels/RequestValidatorId/OperationName/AuthorizationScopes,Stage.MethodSettings/TracingEnabled/cache and logging settings,RestApi.MinimumCompressionSize/ApiKeySourceType, and the equivalentApiGatewayV2::Integration/Route/Stage/Apiproperties — are documented in docs/services/cloudformation.md rather than wired or failed, since the service has no field for any of them yet -
[cloudformation] a cancelled stack update keeps the resource records it already applied. The shutdown exit in the update walk discarded the accumulated records on
failStack, so the persistedUPDATE_FAILEDstack described a pre-update state that no longer matched the service-side resources — and a retried update replayed resources that had already changed. The branch now retains the records the same way theDisableRollbackfailure branches do: applied resources keep their attempted state, untouched ones keep their prior record, and a subsequent update picks up where the cancelled one stopped -
[cloudformation] a custom resource's properties now reject a secure dynamic reference (
{{resolve:secretsmanager:...}},{{resolve:ssm-secure:...}}) instead of resolving it into the Lambda's request — AWS documents secure values as unsupported in custom resources, for the same reasonAWS::CloudFormation::Initand EC2UserDatacarry the restriction: a custom resource's Lambda may log its own event payload. A plain{{resolve:ssm:...}}reference still resolves.OldResourcePropertiesis now actually threaded through to the Lambda (it was previously discarded and effectively sent empty), in the same form asResourceProperties, so an update whose properties did not change no longer looks like every property changed -
[cloudformation] a teardown now tells the truth about what it did and did not delete. Around fifty resource handlers discarded the result of their delete and reported success unconditionally, so a rollback recorded
DELETE_COMPLETEand the stack reachedROLLBACK_COMPLETEwith the resource still standing; they now report the failure, and the stack reachesROLLBACK_FAILEDwith the resource left in its list, which is the signal an operator needs and what real CloudFormation does. That was only safe once absence stopped counting as failure: around sixty-five handlers — API Gateway, AppRegistry, AppSync, EC2, ECS, EFS, EKS, EventBridge, KMS, Lambda, MSK, Pipes, Route 53, Step Functions, S3 and WAFv2 among them — returned their delete error unfiltered, so a rollback failed over a resource nobody needed deleted, and they now recognise the absent-resource answer their service gives. The two together letDeleteStackstop swallowing genuine delete failures: a resource still standing leaves the stackDELETE_FAILEDwith the resource listed, as on AWS, so a retry knows what is out there, while a resource that is merely already gone can never wedge a stack. EC2'sDependencyViolationroutes through the same deletion-blocked sentinel as IAM'sDeleteConflict, RDS's deletion protection and a non-empty S3 bucket —DeleteInternetGateway, the main-table case ofDeleteRouteTableandDeleteVpnGatewayall refuse while something is still attached, and that refusal is now marked as the lasting condition it is rather than reported as an ordinary, possibly transient, delete failure -
[cloudformation] rollback honours
DeletionPolicy: Retain, andRetainExceptOnCreateis honoured onCreateStack,UpdateStackandExecuteChangeSetrather than only onRollbackStack. An update rollback no longer deletes the resources that update created regardless of the policy, so a failed update no longer destroys a resource the template asked to keep; the replacement a failed update created is still deleted, since the original is what the stack rolls back to.RetainExceptOnCreateon the operation opts back into deleting a retained resource, so a create rollback that asks for it deletes what create made — including resources markedDeletionPolicy: Retain— instead of orphaning them for the next deploy to collide with -
[cloudformation] ELBv2 templates now get the target-group health check,
TargetTypeandSchemethey declare.AWS::ElasticLoadBalancingV2::TargetGroup'sTargetTypewas stored and echoed by the elbv2 service (instancevsipvslambdamatters for the Fargate path specifically, since anawsvpcservice registersiptargets) butCreateTargetGroup's Query request never carried it, so every target group came back as theinstancedefault regardless of what the template asked for. The wholeHealthCheck*family (Protocol,Port,Path,Enabled,IntervalSeconds,TimeoutSeconds,HealthyThresholdCount,UnhealthyThresholdCount),Matcher,ProtocolVersionandIpAddressTypehad the same gap — parsed out of the template and dropped before the request left the provisioner. All of it now reachesCreateTargetGroup, defaults the way the real API documents when the template leaves it unset, and reconciles through a newModifyTargetGroupon update;TargetType,Protocol,PortandVpcIdstill force replacement, matching AWS.TargetGroupAttributes(e.g.deregistration_delay.timeout_seconds) reaches a target group through two new operations,ModifyTargetGroupAttributesandDescribeTargetGroupAttributes— the realCreateTargetGrouphas no field for attributes either, so the provisioner makes the follow-up call CDK's synthesized template implies.AWS::ElasticLoadBalancingV2::LoadBalancer'sSchemehad the matching gap: stored and echoed by the service, never sent byCreateLoadBalancer, sointernalvsinternet-facingnever round-tripped from a template;IpAddressTypehad the same fix.Tagson bothCreateTargetGroupandCreateLoadBalancerare threaded directly, as the real API accepts them inline on create.AWS::ElasticLoadBalancingV2::Listener'sDefaultActionsnow forwards each action'sRedirectConfigandFixedResponseConfigwholesale instead of onlyTypeandTargetGroupArn— a CDK HTTP→HTTPS redirect listener no longer loses its target on the way to the emulator, though the proxy still only forwards traffic and does not act on a redirect or fixed-response action's data-plane behaviour. Health checks and the two listener action configs are round-trip-faithful, not enforced:DescribeTargetHealthstill always reports "healthy". Remaining gaps — weightedForwardConfig,Certificates/SslPolicy/AlpnPolicy/MutualAuthentication, the Cognito/OIDC auth actions,SubnetMappings, andAWS::ElasticLoadBalancingV2::ListenerRule(no handler registered at all) — are documented in docs/services/elb.md rather than wired or failed, since the service has no field for any of them yet -
[cloudformation] IAM templates now get the permissions they declare.
AWS::IAM::Roleused to send exactly three parameters toCreateRoleand drop everything else, so a CDK role built withaddToPolicy(...)oraddManagedPolicy(...)provisioned successfully with no permissions at all. The role handler now dispatchesAttachRolePolicyperManagedPolicyArnsentry andPutRolePolicyperPoliciesentry, passesPath,Description,MaxSessionDuration,PermissionsBoundaryandTagsthrough toCreateRole, and reconciles adds and removes on update — compensating already-applied mutations when a later one fails, so a rejected update leaves the role as it was.AWS::IAM::Usergains the same treatment forGroups,ManagedPolicyArns,Policies,PermissionsBoundaryandTags;AWS::IAM::ManagedPolicyhonours itsDescriptionand attaches to the principals itsRoles/Users/Groupslists name;AWS::IAM::Policywrites its document onto groups and users on create, not just roles; andAWS::IAM::Groupis now a real resource rather than an unknown-type stub. Every create-side attachment has a matching detach in the same handler's teardown, so a stack that creates the attachment can also delete itself now that IAM enforcesDeleteConflict— out-of-band relationships are never swept, and a refused delete restores the detachments it made. ALoginProfilefails the resource loudly instead of being silently dropped, since IAM does not emulate login profiles -
[cloudformation]
Refof aCommaDelimitedList(orList<...>) parameter now resolves to a list, as on AWS, with an empty value resolving to the empty list. CDK's bootstrap template depends on this: the CLI passes the execution policy ARN as aCommaDelimitedListwhoseReffeeds the toolkit role'sManagedPolicyArnsdirectly -
[cloudformation]
AWS::DynamoDB::Tablenow appliesTagson create and reconciles additions, changes and removals throughTagResource/UntagResourceon update, and reconcilesGlobalSecondaryIndexesadd, remove and throughput changes throughGlobalSecondaryIndexUpdateson update instead of forwarding indexes at create only.AWS::DynamoDB::GlobalTableis no longer a no-op stub: Overcast emulates a single region, so it provisions theReplicasentry naming the deploying stack's own region as a real table, forcingNEW_AND_OLD_IMAGESstreaming and defaulting to on-demand billing, and fails the stack loudly rather than completing silently when noReplicasentry names that region orPROVISIONEDbilling is requested -
[cloudformation]
AWS::Kinesis::Streamno longer dropsRetentionPeriodHours,Tags,StreamEncryptionorStreamModeDetails.RetentionPeriodHoursthreads throughIncreaseStreamRetentionPeriod/DecreaseStreamRetentionPeriodon create and reconciles on update, so a CDK stream withretentionPeriod: Duration.days(7)no longer reports Kinesis' 24-hour default;Tagsreconcile throughAddTagsToStream/RemoveTagsFromStream; andStreamModeDetailsandStreamEncryptionare new Kinesis fields, threaded at create, reconciled through the newUpdateStreamMode/StartStreamEncryption/StopStreamEncryptionoperations, and round-tripping throughDescribeStream/DescribeStreamSummary.ShardCountchanges still force replacement, sinceUpdateShardCountis not implemented (#1096); an unnamed stream now gets a generated per-instance name so that replacement does not collide with the still-live original -
[cloudformation]
AWS::SSM::Parameterno longer dropsDescription,Tags,Tier,DataType,AllowedPatternorPoliciesbetween CDK and the emulator. The handler's create and update used to send onlyName,TypeandValuetoPutParameter; they now thread every scalar through and reconcileTagsthrough SSM's ownAddTagsToResource/RemoveTagsFromResourceon create and update.Tier,DataTypeandPolicieswere dropped on the SSM service side too:PutParameternow stores them andGetParameter/DescribeParameters/GetParameterHistoryecho the stored value instead of a hardcodedStandard/text/[] -
[cloudformation] SQS queue templates now get their
TagsandRedriveAllowPolicy. The queue-attribute mapping forwarded twelve queue attributes andRedrivePolicybut silently dropped both — the service already supported each (TagQueue/ListQueueTags/UntagQueue, andRedriveAllowPolicyas a validated queue attribute), CloudFormation just never called them.Tagsnow reachesCreateQueueon create and is reconciled throughTagQueue/UntagQueueon update — added, changed and removed keys, plus stack-level propagated tags, the same as the Lambda, LogGroup and Secrets Manager handlers.RedriveAllowPolicyis forwarded alongsideRedrivePolicyon create and update and round-trips throughGetQueueAttributes; itsredrivePermissionrestriction is validated for shape but, as before, not enforced againstStartMessageMoveTaskor automatic DLQ redrive.AWS::SQS::QueuePolicyremains a stub, tracked separately -
[cloudformation] a generated physical ID no longer contains two consecutive hyphens when truncation lands on a separator, which RDS and several other services refuse
-
[cloudformation]
AWS::Scheduler::Scheduletemplates no longer loseDescription,ScheduleExpressionTimezone,StartDate,EndDateorKmsKeyArn.CreateSchedulesentName,GroupName,ScheduleExpression,State,FlexibleTimeWindowandTargetwhile the scheduler service already stored and returned every one of the five —ScheduleExpressionTimezoneandStartDate/EndDateare also honoured by the firing dispatcher — so a CDK schedule with a non-UTC timezone or a future start date deployed clean and fired on the wrong clock. Update also always forced full replacement; it now callsUpdateSchedulein place, and onlyName/GroupNamechanges still replace, matching the resource's create-only properties on real AWS.AWS::Scheduler::ScheduleGroup(Name,Tags) had no gap -
[cloudformation]
AWS::Logs::LogGroup'sRetentionInDaysis coerced from a JSON string to a number before it reaches the Logs service, matching real CloudFormation's own type coercion. A String-typedReforParameterrenders the property as"7"rather than7— CloudFormation accepts either form, but the emulator's Logs handler decodes a strict int and the raw string failed there instead of being accepted, on bothCreateStackand an in-placeUpdateStack. A value that still is not a valid retention number, string or not, continues to fail with the Logs service's ownInvalidParameterExceptionrather than a decode error -
[cloudformation]
AWS::EC2::VPC'sEnableDnsSupport/EnableDnsHostnamesandAWS::EC2::Subnet'sMapPublicIpOnLaunchare threaded through to the emulator instead of being parsed into nothing. A CDKVpcconstruct sets both DNS attributes on every VPC it synthesizes, andMapPublicIpOnLaunchon every public subnet — none of the three reached the emulator'sModifyVpcAttribute/ModifySubnetAttributecalls, and even a direct call was metadata-only:DescribeVpcAttributealways answeredtrueregardless of what was set, andDescribeSubnetsalways answeredmapPublicIpOnLaunch=false. All three are now real store fields:CreateVpcdefaults toEnableDnsSupport=true/EnableDnsHostnames=false(matching AWS), the seeded default VPC and its default subnets get the account-default-VPC values (both DNS attributes andMapPublicIpOnLaunchalltrue),ModifyVpcAttribute/ModifySubnetAttributepersist whatever is set, andDescribeVpcAttribute/DescribeSubnetsreport the stored value. The provisioner issues the follow-upModify*Attributecall on create, and bothAWS::EC2::VPCandAWS::EC2::Subnetgained update support so a template that only changes these properties (or tags) updates in place instead of replacing the resource —CidrBlock/InstanceTenancyon a VPC andVpcId/CidrBlock/AvailabilityZoneon a subnet still force replacement, matching real CloudFormation.AWS::EC2::EIPalso stops hardcodingDomain: "vpc"and reads it (andTags) from the template;DescribeAddressesnow returns the tags it is given. What reads these values downstream — ECS/RDS/Lambda-VPC routing behaviour keyed offEnableDnsHostnamesand the rest — is separate and tracked in #1100; this closes the storage, round-trip and template-threading gap, not the data-plane consumption of it. The other catalogued gaps remain:AWS::EC2::Subnet'sIpv6CidrBlock/AssignIpv6AddressOnCreation/EnableDns64/PrivateDnsNameOptionsOnLaunch/OutpostArn,AWS::EC2::VPC'sIpv4IpamPoolId/Ipv4NetmaskLength,AWS::EC2::NatGateway'sConnectivityType/PrivateIpAddress, andAWS::EC2::Route's IPv6/ENI/TGW/peering/carrier-gateway destinations -
[cloudformation] ElastiCache replication groups now get the engine they declared.
AWS::ElastiCache::ReplicationGroup'sCreateReplicationGrouprequest droppedEngineandEngineVersionentirely — the service already read both and already treated anEnginechange as replacement on update, but create never sent them, so the engine image fell through to itsredis:7default regardless of the template. A replication group declared for Valkey or Memcached, or pinned to a specific Redis version, silently started the wrong Docker container behind an endpoint that looked healthy.SnapshotRetentionLimitandPrimaryClusterIdare forwarded on create too, andDescribeReplicationGroupsnow echoesEngine, matching AWS.AWS::ElastiCache::CacheCluster'sPreferredAvailabilityZoneandCacheParameterGroupName— both already stored fields — and inlineTagsnow round-trip from create as well. Remaining gaps on both resource types —Port, security-group and encryption properties,LogDeliveryConfigurations, replication-group node-group sizing, and a handful of others the emulator has no field for yet — are documented in docs/services/elasticache.md rather than silently accepted -
[cloudformation/secretsmanager/stepfunctions] a filter value outside the enum AWS models is now rejected instead of silently matching nothing.
ListStacksrejects aStackStatusFiltervalue outside AWS's fullStackStatusenum withValidationError— the complete 23-value enum is modelled, including theIMPORT_*states Overcast itself never produces, so a caller legitimately filtering on one of those is still accepted.ListSecretsandBatchGetSecretValuereject aFiltersentry whoseKeyis outside AWS's enum withInvalidParameterException, andListExecutionsrejects astatusFiltervalue outside theExecutionStatusenum withValidationException— each of them one of the operation's own declared errors -
[cloudformation] a stack-tag-only update now reconciles the tags it should.
AWS::DynamoDB::Table,AWS::SSM::ParameterandAWS::StepFunctions::StateMachineleft their tags unreconciled when only the stack's tags changed and the resource's own template properties did not, even though each resource's own tag-reconciliation code was already correct.AWS::Transfer::Serverno longer forces replacement when only itsTagschange either; it reconciles them throughTagResource/UntagResourcein place, the same as every other resource type that reconciles tags without replacing -
[cloudformation] Cognito user pool and app client templates now get their
AliasAttributes,DeviceConfiguration,AccountRecoverySetting,SmsConfiguration,SmsAuthenticationMessage,SmsVerificationMessage,EmailVerificationMessage,EmailVerificationSubject,LambdaConfig,PreventUserExistenceErrors,ReadAttributesandWriteAttributes. The user pool handler forwarded only a core subset ofAWS::Cognito::UserPoolproperties and had no update handler at all, so any template change replaced the pool instead of reconciling it; the app client handler had the same update gap. The Cognito service now stores and returns all of the properties above; the CloudFormation handlers thread them on create and reconcile the mutable ones on update throughUpdateUserPool/UpdateUserPoolClient, replacing the pool only whenAliasAttributesorUsernameAttributeschange, since neither is a member of real Cognito'sUpdateUserPoolRequest.LambdaConfiground-trips throughDescribeUserPool, so a pool's configured triggers are visible — and, as of this release, invoked -
[cloudformation]
AWS::KMS::Keystack deletion carries the template'sPendingWindowInDaysthrough toScheduleKeyDeletioninstead of the hardcoded 7 days it used before, falling back to KMS's own documented 30-day default when the property is absent.Tagsround-trips too:CreateKeyaccepts and stores them, merged with any stack-level tags, where it previously dropped the property entirely, and update reconciles changes throughTagResource/UntagResource, the same add/remove diff every other tag-aware handler here uses.KeyPolicyandEnabledwere already threaded on create;AWS::KMS::Aliashas no properties beyondAliasName/TargetKeyIdand had no gap -
[cloudformation]
AWS::StepFunctions::StateMachinetemplates no longer loseDefinitionSubstitutions,LoggingConfiguration,TracingConfigurationorTags. The handler's create sent onlyname,definition,roleArnandtype, so a CDK state machine built fromDefinitionBody.fromString/fromFilewithdefinitionSubstitutionsprovisioned with its ASL${key}placeholders unresolved — inert today, but a state machine executed under the ASL interpreter would fail at the first task state on the literal placeholder string.DefinitionSubstitutionsis now applied on both create and update: a matched${key}is replaced with its value before the definition is stored, and an unmatched placeholder is left verbatim rather than failing the deploy, since CloudFormation does not validate substitution completeness against the definition.LoggingConfigurationandTracingConfigurationare threaded on create as well as update, andTagsare applied throughTagResourceon create and reconciled on update. AStateMachineTypechange on update was silently ignored instead of forcing replacement — AWS documents itUpdate requires: Replacementand does not support changing it in place — so it now returns the same replacement-required pathStateMachineNamealready took -
[cloudwatch]
GetMetricDatanow answers over the JSON protocol (X-Amz-Target: GraniteServiceVersion20100801.GetMetricData), the pinned model's primary protocol for the operation — previously only the Query protocol reached it, so an AWS SDK speaking JSON gotUnknownOperationException -
[docker/ecs] a container-backed launch no longer fails outright on a transient registry error. The shared image puller retries a rate limit ("too many requests"), a registry 5xx, and a deadline, timeout or reset while a pull was in flight, within one bounded 3-attempt backoff — at most 4 attempts, about 2.5 s in total — closing the window where ECS's
RunTaskand every other container-backed create reported failure for a blip that would have cleared a moment later. A caller's own context ending, whether shutdown or its own deadline, still stops the pull immediately rather than retrying -
[docs] the published documentation now says what the emulator actually does. The CloudFormation resource-type coverage docs state the real number — 132 registered types at the time of writing, 136 after this release's additions, not "~50" — and
docs/cdk.mdlists every handler. The LocalStack migration guide's known-gaps table, stale in every row (SigV4 validation, CloudWatch metrics, Firehose, Route 53 and ElastiCache are all implemented), is replaced by a pointer to the generated service index. The configuration reference gains the ~30 environment variables it omitted, including theOVERCAST_MCP_REMOTE_EXPOSURE/OVERCAST_MCP_AUTH_TOKENremote-access controls.OVERCAST_SIGV4_VALIDATE's startup log and configuration-reference row claimed SigV4 validation was "not yet implemented — all requests are accepted" while the middleware was validating signatures and answering a bad one with 403InvalidSignatureException, actively misdirecting an operator away from the real cause of signature failures; both now describe what the flag does, including where signing secrets come from (IAM user access keys and STS session credentials, with the local-devtestfallback), and validation behaviour itself is unchanged.docs/operation-manifest.mdno longer silently drops S3, Lambda, API Gateway, CloudFront, Pipes, CloudWatch and seven other REST/legacy-dispatch services: it lists every excluded service by name with a derived reason, and explains how its typed-dispatch counts differ from the service index's capability-registry counts. The web console section no longer describes a separate BFF server process that could fail to start — the console is served in-process by the Go binary, andOVERCAST_UI_PORT=0/--ui-port 0is how to turn it off. A code comment that wrongly equatedOVERCAST_HOSTwith LocalStack'sLOCALSTACK_HOSTis corrected: the true analogue isOVERCAST_HOSTNAME -
[dynamodb] table responses carry the identity and the shape AWS documents.
CreateTable,DescribeTable,UpdateTableandDeleteTablenow includeTableDescription/Table.TableId— a UUID minted once atCreateTabletime and persisted with the table, so it stays stable across reads and restarts — and tooling that keys off a table's identity rather than its name or ARN (CloudFormation'sFn::GetAtt TableId, for one) no longer gets an empty string with no indication anything was missing.DeleteTable's response returns the deleted table's description underTableDescription, withTableStatusreported asDELETING, matching the other three and AWS's documented shape; it previously reused theDescribeTable-shapedTablewrapper, which strict SDK decoders would silently drop since the member name did not match -
[ec2] the typed dispatch path answers as the legacy one did. Typed-operation errors render EC2's
<Errors><Error>envelope rather than the generic Query one, restoring the wrapper deleted when typed dispatch was switched off, andAllocateAddress's typed body no longer ignoresDomainand always allocates avpc-domain address — found by the new differential test rather than by a caller, since the typed path had never been reachable -
[ec2]
CreateSecurityGroupreturns thetagSetits result shape has always carried. Every other EC2 create nests its tag set inside the resource element it answers with; this one puts it at the top level next togroupId, and Overcast omitted it, so a caller that tagged a group at create had to issue aDescribeSecurityGroupsto read back what it had just set. -
[ec2]
AllocateAddresshonours create-timeTagSpecification.Non both dispatch paths. Neither ever parsed it — the call succeeded and the Elastic IP came back untagged, invisible toDescribeTagsand to thetagSetDescribeAddressesrenders — and a tag the model refuses, such as a reservedaws:key, now fails the allocation instead of allocating and dropping the tag. Per the AWS model,AllocateAddressResultitself carries no tag set; the tags surface through the describes. -
[ecr]
CreateRepositorynow marks its response withx-overcast-emulation-limitationwhenever therepositoryUriit just minted does not name a registry proven to be listening. No ECR registry container running at all, and one whose container started but the daemon-reachability probe never confirmed it answers, both used to be aWarnlog line only —CreateRepositorystill returned 200 with arepositoryUrithat a laterdocker pushanswers with405 Method Not Allowed, or that an ECS or Lambda image pull fails against, with nothing tying that failure back to the repository that caused it. Both wire paths (the AWS JSON 1.1 handler and the RPC v2 CBOR typed operation) mark the same sentence; inside a CloudFormation deploy it becomes the resource'sResourceStatusReason, next to theAWS::ECR::Repositoryit is about. Any typed operation's response type can now opt into this channel without new plumbing per service -
[ecs] containers in an
awsvpctask now share one network namespace, so127.0.0.1reaches every container in the task as it does on Fargate — the ECS sidecar pattern (nginx to php-fpm, an application to its X-Ray daemon) works against the AWS-correct task definition, and the addressDescribeTasksreports is the one every container answers on -
[ecs]
StopTaskover RPC v2 CBOR records the stop before it kills the containers, as the JSON path already did. Killing them first let the Docker die event file the task asEssentialContainerExitedinstead ofUserInitiated, charge its deployment a failed task, and schedule a service replacement nobody asked for. The two paths now share one implementation, so the CBOR one also takes a stopped task out of its service's target groups -
[ecs] a task that fails to start part-way tears down the containers it had already started, instead of leaving them holding ports and memory until shutdown
-
[iam]
UpdateRoleis now supported (Description,MaxSessionDuration; an empty description clears it, an omitted one is left alone),CreateRole/GetRoleround-tripDescriptionandMaxSessionDuration,CreatePolicy/GetPolicyround-tripDescription, andCreatePolicyVersionwithSetAsDefault=truereplaces the operative document and bumpsDefaultVersionId— superseded versions are not retained -
[inithooks] a hook script's timeout on Windows now kills its whole process tree, not just the shell.
TerminateProcesspreviously reached only the shell, so a backgrounded descendant (sleep 5 &, say) was orphaned and kept running, still holding whatever handle it had inherited — including, undergo test, the test binary's own stdout pipe, which reds the pre-push gate after every test has passed. Unix already killed the whole process group on timeout; Windows now does too, through a Job Object withJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE -
[lambda] a
LogType: Tailinvocation no longer loses the handler's output when the Docker daemon is busy. The tail used to be assembled from two sources that shared no clock and no sequence — the Runtime API response on one channel, the container's bytes read back off the daemon on another — so "this invocation's output has all arrived" could only be inferred from silence plus a bound, and a daemon under load could beat any bound chosen. Widening that bound is what the interim fixes did, and each closed a real case: a cold start whose log stream took a daemon round trip to connect, a warm invocation whose already-open reader was slow to deliver under contention, and a wait whose bound ran from the moment it began rather than from the last observed pipeline event (connect answered, reader parked, bytes read, line appended), with one absolute 1 s ceiling so a wedged pipeline could never hold up the response. The tail is now assembled from a numbered stream the container itself produces, and the invocation's response names the last line that belongs to it — which retires the guesswork rather than tuning it, and with it the whole class of defect -
[lambda/ecs] a line a function or task printed is no longer dropped when the daemon wrote it out of timestamp order — which it does whenever stdout and stderr are written close together, since each stream has its own copier: a Python handler's first
printregularly lost to asys.stderrwrite that landed first, and the line went missing from both CloudWatch Logs and theX-Amz-Log-Resulttail. The follower's reconnect de-duplication treated "older than the newest line seen" as "already delivered" on every connection; it now does so only for the replay a reconnect asks for, up to the watermark it resumed from. A task'sawslogsoutput has followed the same path since the shared follower landed and is fixed with it — and after a reconnect, the lines a traceback prints within one nanosecond are no longer dropped past the first -
[lambda/pipes] a partial-batch failure report is honoured rather than refused. A Lambda event source mapping accepts
FunctionResponseTypes: ["ReportBatchItemFailures"]— validated against AWS's one-member enum instead of being stored unread — and a function that answers withbatchItemFailureshas exactly the records it named retried: an SQS mapping deletes only the succeeded messages and leaves the reported ones to become visible again, and a DynamoDB Streams mapping retries from the earliest reported sequence number, as AWS checkpoints an ordered stream. AWS's edge cases come with it: an empty or absent list is a complete success, and a response that cannot be read (invalid JSON, a missing, empty or non-stringitemIdentifier, an identifier naming a record that was not in the batch) is a complete failure that acknowledges nothing and logs why. A pipe's Lambda target reports the same way for SQS, Kinesis and DynamoDB Streams sources — an SQS source deletes only the messages the target did not name, a Kinesis source rewinds its shard cursor to just before the earliest reported record, and a DynamoDB Streams source retries from that record onwards — but a pipe has no opt-in flag, so a response that never mentions the member is left alone and a target returning an arbitrary payload behaves as before. A Step Functions target and a Lambda enrichment still do not report, and both are documented as such -
[lambda] the layer version metadata endpoint behind the Lambda layer detail page was missing from the Go BFF entirely and 404d in every build, not only under
pnpm dev; it is proxied like every other Lambda route now -
[lambda] a handler error reported through the Runtime API now comes back as
FunctionError: Unhandled, as AWS reports it for an exception thrown by any current runtime. It used to beHandledunless the runtime reportedRuntime.ExitError— a value no modern runtime produces for a thrown error, and one that broke SDK callers branching on the documented value. The new compatInvokeWithErrortest is what surfaced it -
[lambda] an execution environment survives exactly as long as its function does. Deleting a function now reaches the containers it is still starting: an environment between
docker createand its firstGET /nextbelonged to nothing the pool could see — not the warm set, not the reservation, not the instance tracker — soDeleteFunctionwalked past it and the container went on running with no function to serve, until the invocation that started it eventually gave up. Every creation is now registered for exactly that window, so deleting the function aborts it and an environment that comes up anyway is destroyed rather than handed out; a create abandoned mid-flight, whose container the daemon finishes after the answer is lost, is removed by the name the cold start gave it. Conversely, a function deleted and created again under the same name now keeps the environments built for it: deletion marks the name so that an invocation still in flight destroys its environment on release instead of pooling it for a function that no longer exists, but the mark was only lifted by the next invocation — so provisioned concurrency configured on the recreated function had every environment it built destroyed on release, leaving the reservation unfilled until something invoked the function, and proactive initialization lost its environment the same way. Configuring a reservation or initializing ahead of traffic now counts as proof the function is in use, as an invocation always did -
[lambda] a cold start no longer has its CPU cut by another environment of the same function finishing INIT. The INIT burst — the ~2 CPUs a container gets while it initializes, before dropping to its memory's proportional share — was tracked per function rather than per execution environment, so when a function had two environments initializing at once (two event source mappings on one stream deliver to one function at the same instant, and every concurrent invocation cold starts its own), the second displaced the first. The environment that finished INIT threw the throttle-down at whichever container had registered last, cutting a container still in INIT to a fraction of a core — a one-second init becoming half a minute on a 128 MB function — while the environment that had actually finished kept burst CPU for the rest of its life. Both now belong to the container that reported INIT complete
-
[lambda] bind-mount hot reload now picks up an edit to a file the runtime has already loaded, which is the gesture the feature exists for. A hot-reload function's execution environment was keyed on the mounted directory's own mtime, and a directory's mtime moves only when an entry is created, deleted or renamed inside it — so editing
index.jsin place left the identity untouched, the warm container was reused, and Node'srequirecache (or Python'ssys.modules) kept serving the module that container had loaded. Saving under a new name, deleting a file, orcdk watchall worked, which is why this went unnoticed. The identity is now a fingerprint of the whole mounted tree — every entry's path, and every file's size and modification time — so an in-place edit retires the warm environment through the same pathUpdateFunctionCodeuses and the next invoke cold starts against the edited source. On a Windows host the identity never moved at all, for either reason: the tree was being read through the Docker-form path (/c/Users/you/app), which names nothing on Windows, so both this check and the TypeScript-source warning silently did nothing there. The fingerprint is bounded so that recomputing it before each invocation does not become the cost of invoking, and docs/services/lambda.md states each bound exactly:node_modules,.git,__pycache__,.venv,.mypy_cacheand.pytest_cacheare fingerprinted by name and not descended into (which is what keeps a 30,000-file dependency tree at the same 1–2 ms as the source beside it); the walk stops at 20,000 entries and 24 directory levels; and a read costing over 25 ms is rate-limited to 20× what it cost, capped at 2 s, so a tree behind a slow Docker Desktop file share costs a bounded fraction of the invoke path rather than half a second on every invocation, while everything under the budget — a few thousand files on a local disk — is re-read before every invocation and is exact. Two boundaries are honest rather than fixed: a same-length edit made within a filesystem's 1–2 s timestamp granularity of the previous one may be seen a change late, and Overcast running in a container can only fingerprint a source directory that is mounted into Overcast itself — it now logs a warning naming the path when it cannot read one, instead of reloading nothing and saying nothing. -
[lifecycle/eks/stepfunctions] three shutdown races of the same shape are fenced.
lifecycle.Scheduler.After(the readiness health-check retries MSK, RDS, ElastiCache and EFS run) is now a defined no-op onceStophas run, so a still-running health check stops rescheduling instead of racing shutdown; EKS'sCreateClusterlive-mode bootstrap calledliveWg.Add(1)without first checking the stopping flag under its lifecycle mutex, so a create racingStopcould leave a k3s container starting after shutdown, and a create that lands afterStophas begun is now refused, leaving the cluster recordFAILEDwith a reason instead of a half-started container; and Step Functions'startExecution— the single choke pointStartExecution,StartSyncExecutionand a nestedstates:startExecutionall funnel through — registered its run and calledwg.Addwith nothing synchronising either againstStop'swg.Wait, so astoppingflag guarded by the same lock now fences the reservation:Stopsets it before it ever waits, and an execution arriving concurrently either reserves its slot strictly before that happens or gets a definedServiceUnavailableExceptionwith no goroutine launched and nowgtouched, its already-persisted RUNNING record unwound to ABORTED rather than left stuck. All three could surface under-raceon unrelated PRs -
[pipes] a Kinesis source's
MaximumRetryAttemptsandDeadLetterConfigare honoured, matching the retry-then-dead-letter behaviour DynamoDB Streams sources already had — a batch that exhausts its retries now reaches the configured SQS queue or SNS topic instead of being retried on every poll tick forever -
[protocol] a request member typed as an open document (CloudWatch's
PutMetricAlarmMetricsandEvaluationCriteriaare the ones in reach today) decodes from CBOR the same way it decodes from JSON. CBOR maps landing in such a member used to decode with untyped keys, which the emulator's own JSON persistence cannot serialise, so the call failed with a bare500 InternalErroron the CBOR door and succeeded on every other. A CBOR map with non-string keys is now rejected at the decode instead, which is the honest answer — no Smithy structure or document has them -
[rds] Aurora cluster endpoints now resolve from other containers, and carry a port the caller can dial.
EndpointandReaderEndpointwere registered nowhere in Docker's DNS, so a name ending in a split-horizon domain fell through to public DNS and answered loopback — an ECS task or Lambda readingcluster.clusterEndpoint.hostnamefrom a CDK stack dialled itself and got a refused connection, and only the per-instance endpoint worked. The writer member's engine container now carries both cluster names alongside its own, andDescribeDBClustersrenders the address and port from that instance on the same per-caller rulesDescribeDBInstanceshas always used, so a host caller is given the published port instead of the engine port nothing binds on the host. The reader endpoint resolves to the writer: Overcast's cluster members do not share storage, so spreading reads over the replicas would answer from an empty database — see docs/services/rds.md -
[rds] deleting an Aurora DB instance now removes it from its cluster, and deleting the writer promotes a survivor.
DBClusterMemberswas only ever appended to, soDeleteDBInstancedropped the instance record and left the membership entry behind:DescribeDBClusterswent on reporting a member that no longer existed, and if the deleted member was the writer the cluster was left with no writer at all — which took its endpoints with it, since the cluster's address, port and DNS all answer from whichever member holds that role. A surviving member is now promoted the way AWS chooses one during a failover, by promotion tier, and the cluster endpoint names move onto the new writer's engine container. Moving them detaches and re-attaches that container, so connections held open to it over that network drop, exactly as a failover on AWS drops them. A cluster whose last member is deleted keeps its AWS-shaped endpoint names and its own port, unchanged -
[rds]
StopDBClusterandStartDBClusteranswer in their own response envelopes instead ofCreateDBClusterResponse, so an AWS SDK can deserialize them. The state change was always correct, so nothing that read the stored record noticed -
[router/backup/apigateway] a
/tags/{resourceArn}request whose ARN belongs to a service whose tag operations are not implemented answers the generated 501 with thex-emulator-unsupportedmarker. API Gateway's ARN-keyed tag store was the dispatcher's fallback for every ARN no other service claimed, soaws backup list-tagsread HTTP 200{"tags":{}}from a service the caller never addressed — and Backup implements no tag operations at all. The store still answers its own ARNs and AppRegistry'sservicecatalogones, whose SDK shares the endpoint -
[router] a request signed for one service (OpenSearch or MSK, say) that lands on a path modeled for a different real AWS service now gets that service's own scoped-credential 403 —
InvalidSignatureExceptionnaming the expected signing name, matching what AWS answers when a credential scope names the wrong endpoint — instead of falling into S3's bucket/object wildcard and returning a nonsensicalNoSuchBucket/NoSuchKey. Unsigned traffic, S3's own traffic, and a scope that is not a real AWS signing name are unaffected -
[s3/sns] S3 bucket notifications now deliver to SNS topics.
TopicConfigurationswas accepted, stored and returned, but the notification dispatcher never read it, so a configured S3 → SNS destination silently never fired — a success response, a correct-looking round trip, and no delivery. Matching events now publish to the named topic through SNS's own fan-out, so the whole S3 → SNS → SQS/Lambda/email path works; event-type selection and prefix/suffix filters share the same matcher as the SQS and Lambda destinations. The envelope matches real S3: the{"Records":[…]}JSON travels as the SNS notification'sMessagestring with subject "Amazon S3 Notification", so a queue subscribed to the topic receives the standard SNS envelope whoseMessageparses back into the S3 event -
[scheduler]
KmsKeyArnis now accepted onCreateSchedule/UpdateScheduleand round-trips throughGetSchedule, matchingFlexibleTimeWindowandScheduleExpressionTimezone: stored and returned, but nothing is actually encrypted with it -
[sns]
Publishmatches AWS's documented behaviour on several fronts reviewed against the API reference. It accepts exactly one ofTopicArn,PhoneNumberorTargetArnas the destination, whereTopicArnwas previously required even for aPhoneNumber/TargetArncall;PhoneNumberdelivers directly to the Inbox as SMS with no topic involved;TargetArn(mobile platform endpoints) returns an explicit400 InvalidParameterin place of a misleading "missing TopicArn" error;MessageStructure=jsonis implemented, selecting each subscriber's own protocol-keyed payload (falling back todefault) for the envelope, raw delivery, email and SMS bodies alike;Message(256 KB) andSubject(under 100 characters, no line breaks or control characters) are validated on bothPublishand eachPublishBatchentry; and a topic whose name ends in.fifogets itsMessageGroupId/MessageDeduplicationId(orContentBasedDeduplication) parameters checked onPublish— a call that was never valid against real AWS now fails the same way, while full FIFO ordering, deduplication andSequenceNumbergeneration remain unimplemented and documented as such -
[sns] a subscription whose delivery dependency was never wired into this instance (SQS enqueuer, Lambda invoker, SMTP mailer, SMS sender, or outbound webhook/push capture) no longer vanishes silently on
Publish— it now fails the same way any other delivery failure does:NumberOfNotificationsFailedis recorded, the subscription's dead-letter queue receives it when aRedrivePolicyis set, and a one-time warning per topic and protocol is logged naming the missing wiring -
[sqs]
DeleteQueue,PurgeQueue,SetQueueAttributes,TagQueueandUntagQueueno longer emit an empty<{Action}Result></{Action}Result>wrapper on the legacy Query/XML protocol. These five operations have no modeled output at all, and real AWS's Query/XML response for them goes straight from the opening<{Action}Response>element to<ResponseMetadata>with no result element in between. The JSON protocol was already correct ({}with no wrapper) and is unaffected -
[state]
HybridStore.Set/Delete/DeletePrefixno longer pin TierCached writes (S3 objects, SQS messages, Lambda deployment packages, CloudWatch Logs events, …) permanently in memory; they now live only in the pending overlay until flushed, then in SQLite -
[stepfunctions]
UpdateStateMachineis implemented — the CloudFormation update path called it unconditionally whenever a mutable property was present, which is effectively always sinceDefinitionStringis rarely omitted, so every in-place stack update to a state machine's mutable properties was quietly falling back to full replacement (AWSStepFunctions.UpdateStateMachinehad no handler).DescribeStateMachinenow echoesloggingConfiguration/tracingConfiguration, defaulting to AWS's documentedOFF/disabled shape when unset, on both the legacy JSON and typed CBOR paths -
[sts]
AssumeRoleandAssumeRoleWithWebIdentityno longer repeatRoleSessionNamein both segments ofAssumedRoleUser.Arn. The first segment is now the role name parsed out of the request'sRoleArn, matching AWS'sarn:aws:sts::<account>:assumed-role/<RoleName>/<SessionName>shape — fixed identically on the legacy Query XML and the typed JSON/CBOR paths -
[web] global search now hits the query cache instead of refetching on every keystroke — the S3, SQS, SNS, Kinesis, Lambda, Secrets Manager and Logs contributors built their cache-lookup key as
[service, resource, baseUrl](Logs omitted the endpoint entirely), which never matched the[baseUrl, region, service, resource]shape the real feature queries use, so the lookup always missed and every search ignored the selected region -
[web] a log group with no streams, or one whose streams failed to load, says so instead of rendering an empty table; the same page's stream list gains the standard skeleton while it loads. A secret whose rotation returned no versions no longer renders a headers-only table
Removed
- [web/ci] the dead Node BFF — the standalone entry point for the dev Hono server (
web/api/src/node.ts), the@overcast/apipackage manifest around it (web/api/package.json, its ownpnpm-lock.yaml, thedev/build/startscripts and@hono/node-server), and the "BFF server" bundle (pnpm run build:server,web/api/src/server.ts) that CI built on every push. All of it was a leftover from before the web console moved into the Go binary: nothing installed, built or started the entry point, and no image ever copied or started the bundle.web/api/srcis typechecked, tested and resolved fromweb/as plain source files
Release: https://github.com/Neaox/overcast/releases/tag/v0.0.1-alpha.37