v0.0.1-alpha.35
Pre-releaseOvercast v0.0.1-alpha.35
Docker Images
Full image with the web management console:
- Pull:
ghcr.io/neaox/overcast:0.0.1-alpha.35 - 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.35 - 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.35
docker run --rm -p 4566:4566 ghcr.io/neaox/overcast-slim:0.0.1-alpha.35Native Binaries
Download a binary for your platform from the assets below and verify it with SHA256SUMS.
| Asset | SHA256 |
|---|---|
overcast-darwin-amd64 |
c9943e14b893a43861a61b4c15077001999961b470045dad039e2c976f32fbfe |
overcast-darwin-arm64 |
7c5c3c6ad44d6273a39b60bec37d615310229a7d9807e283eace4b1304760f38 |
overcast-linux-amd64 |
f5854fd7f7e16471cca233a54343553cb35cf12fbba09fc0460a2ee4e6df3668 |
overcast-linux-arm64 |
db52c1d0661c01be4dbdc8b3f7d5d9c8006d2878d19972ec4cfb87ddb669c5d1 |
overcast-windows-amd64.exe |
6af8c890f09632bc5aa820447e602f39ac17a7a1c4219dc9199a98fac5c56f5b |
overcastd-darwin-amd64 |
7781e37051935faf61eaaf468ef269b4183001c66310bb63a93c15573bdadc34 |
overcastd-darwin-arm64 |
7e1fdc8af34928a4a68aa303dfa37ba72f9f2906a78b34b0c17bdc2a90018387 |
overcastd-linux-amd64 |
0329517ee093191a6ab257bc98e215a6e903f7b81efa38bd85f45dd564abd063 |
overcastd-linux-arm64 |
c5e67a209b43f3740a9c6e97da2ca8453cefa8c3012b544b63e148d0335a446f |
overcastd-windows-amd64.exe |
c5806039857913f5e50b2495397e0bbdbcc163972e5042ad69609af94f52b3a0 |
Release Notes
Added
-
[appconfig]
UpdateApplicationis implemented, atPATCH /applications/{ApplicationId}; an omittedNameorDescriptionleaves the stored value alone -
BREAKING [appconfig] the create operations require the members AWS marks required —
Nameon an environment,NameandLocationUrion a configuration profile,Content-Typeon a hosted version — and creating an environment or a profile under an application that does not exist answersResourceNotFoundExceptionin place of storing an unreachable record
migration: pass the required member, and create the application first -
[appconfig] the create operations apply an inline
Tagsmap,CreateHostedConfigurationVersionhonours theLatest-Version-Numberheader with aConflictException,ListConfigurationProfileshonourstypeandListHostedConfigurationVersionshonoursversion_label -
[appconfig] every list operation paginates on
max_resultsandnext_tokenand answers aNextToken; a token that cannot be decoded gets aBadRequestExceptionin place of a silent restart at the first page -
BREAKING [appconfigdata]
StartConfigurationSessionacceptsRequiredMinimumPollIntervalInSeconds, reports it back inNext-Poll-Interval-In-Seconds, and refuses a poll that arrives before it has elapsed, as AWS does; the member used to be ignored
migration: poll no more often than the interval the session asked for, or dropRequiredMinimumPollIntervalInSecondsfromStartConfigurationSession -
[backup] the vault and plan operations honour the members AWS models:
ListBackupVaultsandListBackupPlanspage onmaxResultsandnextToken, withListBackupVaultsalso filtering onvaultTypeandsharedand an undecodable token getting anInvalidParameterValueExceptionin place of a silent restart at the first page;CreateBackupVaultvalidates the vault name against the pattern AWS documents;DescribeBackupVaulthonoursbackupVaultAccountId; andGetBackupPlananswers not-found for aversionIdother than the plan's current one, rather than returning the current version under the requested version's name -
[console] a Lambda's Configuration tab says what its VPC configuration actually does here — the container really joins the VPC's network, but Overcast restricts nothing and security groups are never applied, so a test that "proves" the VPC wiring works passes whether or not it is correct. Shown only on functions that have a VPC configured; the empty state carries a one-line version for everyone else
-
[dns] a query for a resource endpoint the calling container cannot reach is refused instead of answered with Overcast's own address. Overcast is authoritative for every subdomain of its split-horizon domains, and a container endpoint is one of those — so a missing network alias never produced a clean failure, it produced Overcast's address, and the client connected to the emulator on the engine's port and waited for its own timeout. The refusal is immediate, and the log line names the resource, the caller, and the networks each is on
-
[dns] the refusal requires positive identification — some container is advertising that exact alias, on a network the caller is not attached to. A name that merely looks like a resource endpoint is still answered, because those labels are ordinary words in a bucket name:
my.rdsis addressed virtual-hosted asmy.rds.localhost, and refusing it would break the bucket -
[docs]
docs/networking.mdgains a section on Lambda, ECS and VPCs — a row-by-row comparison of what AWS routes and what Overcast currently allows, why the emulator's own APIs stay reachable from inside a VPC on purpose, and what to do about the two*_NETWORK-era migrations (OVERCAST_NETWORK, and the seeded default VPC turning up inDescribeVpcs) -
[ec2/ecs]
assignPublicIp: ENABLEDon anawsvpcConfigurationkeeps the task reachable from outside its VPC, matching what the field buys on AWS. It was stored and ignored before -
BREAKING [ecr] images pushed to the emulated ECR survive an Overcast restart. The registry container keeps its blobs in a named Docker volume,
overcast-ecr-registry-data-<port>, carrying the sameovercast.service=ecrlabels every other managed resource does; the container itself stays disposable. The gap this closes iscdk deploy: cdk-assets asksDescribeImagesfor a container asset's content-hash tag and skips both the build and the push when it resolves, so a registry that came back empty made every deploy after a restart rebuild and re-upload assets that had not changed. Only the fixed-port claim (OVERCAST_ECR_REGISTRY_PORT, default4510) gets a volume — an ephemeral registry's container name is deliberately random and its port is whatever the daemon had spare, so a volume keyed to either would be a fresh orphan on every start, and one well-known name shared between concurrent instances would put two registry processes on one filesystem. Repository metadata still follows the state backend, and re-creating a repository is enough for its images to reappear: the first read reconciles it against the registry
migration: a restart no longer clears the registry. Discard the images withdocker volume rm overcast-ecr-registry-data-4510while Overcast is down, or setOVERCAST_ECR_REGISTRY_PERSIST=falseto keep the old container-lifetime storage -
[ecr]
OVERCAST_ECR_REGISTRY_PERSIST(defaulttrue) backs the fixed-port registry with that volume. Set it false for storage that dies with the registry container, which is worth doing when the volume itself is the problem — a corrupt one, or a runner that has to start from nothing -
[eks]
DescribeAddonVersionsfilters onaddonName,kubernetesVersion,types,publishersandownersand pages withmaxResults/nextToken; omittingaddonNamereturns the whole add-on catalog rather than nothing, and each entry now carries itstype,publisherandowner -
[eks]
ListInsightshonours the modeledfilteron category, Kubernetes version and status, and pages withmaxResults/nextToken; an undecodablenextTokengets anInvalidParameterExceptionin place of a silent restart at the first page -
[lambda] an asynchronous invocation whose function returns an error is now retried, as AWS retries it: two more attempts, waiting one minute after the first and two after the second, matching AWS's default
MaximumRetryAttemptsand the wait AWS documents for a function error. Previously a single failure was final, so a handler that failed on a cold start and would have succeeded on the retry lost its event — and, with aDeadLetterConfigconfigured, dead-lettered an event AWS would have run. Each attempt acquires its own execution environment and is tracked as its own invocation, which is what a retry is on AWS rather than a resumption of the last one. A function throttled by its own reserved concurrency is the documented exception and is not retried again: AWS sends those events to the dead-letter queue "without any retries" -
[lambda]
DeadLetterConfigon a function is honoured: an asynchronous invocation that fails is delivered to the SQS queue or SNS topicTargetArnnames, carrying the original event as the message body and theRequestID,ErrorCodeandErrorMessagemessage attributes AWS documents. It is stored and echoed byCreateFunction,UpdateFunctionConfiguration,GetFunctionandGetFunctionConfiguration, and an explicitly emptyTargetArnremoves the target as on AWS. Overcast still makes one attempt where AWS makes three, so the event reaches the queue sooner than it would on AWS; nothing in a dead-letter message reports the attempt count, so what a consumer reads is unchanged.PutFunctionEventInvokeConfigon-failure destinations — a different feature, carrying the invocation record rather than the event — remain unimplemented outside event source mappings -
[lambda]
PutFunctionEventInvokeConfigand its family —Get,Update,DeleteandListFunctionEventInvokeConfigs— are implemented, so a function's asynchronous invocation settings are configurable per function, version or alias instead of being fixed at AWS's defaults.MaximumRetryAttempts(0-2) decides how many times a failed event is retried, where 0 genuinely means "do not retry";MaximumEventAgeInSeconds(60-21600) ends the retries once the event outlives it.Putoverwrites and removes members the request omits,Updateleaves them alone, and both are validated against AWS's ranges, so a policy that deploys against Overcast is one the account will also accept. Until now these operations had no routes at all, so an SDK call for one fell through to S3's catch-all and came back as an XMLNoSuchBucketa Lambda client could not parse -
[lambda] on-success and on-failure destinations now receive AWS's invocation record: the
{version, timestamp, requestContext, requestPayload, responseContext, responsePayload}envelope, withconditionreadingSuccessorRetriesExhaustedandapproximateInvokeCountnaming the attempt that settled it. SQS, SNS, Lambda and EventBridge destinations are delivered to, with an EventBridge entry carrying thelambdasource and AWS'sLambda Function Invocation Result - Success/- Failuredetail-type. A destination is not a dead-letter queue and the two are no longer conflated — a DLQ receives the bare event, a destination receives the record — so a function configured with both gets both, which is what AWS documents. An S3 on-failure destination returns501rather than accepting a configuration whose records would never be written, and an S3 on-success destination is rejected exactly as AWS rejects it -
[msk]
ListClustersV2, withclusterNameFilter,clusterTypeFilter,maxResultsandnextTokenread from the query string where AWS binds them; anextTokenthat cannot be decoded gets aBadRequestExceptionin place of a silent restart at the first page -
BREAKING [opensearch]
ListTags,AddTags,RemoveTagsandDescribeDomainsrequire the members AWS marks required, answeringValidationExceptionwhere an empty ARN or an empty domain list was accepted
migration: pass the required member —?arn=onListTags,ARNonAddTagsandRemoveTags, and at least one name inDescribeDomains -
[opensearch]
ListDomainNameshonours theengineTypequery parameter, and a domain'sEngineTypefollows from itsEngineVersioninstead of being reported asOpenSearchwhatever engine it runs; andCreateDomainapplies an inlineTagListat creation, soListTagssees a tagged create without a second call -
[rds]
PubliclyAccessibleon DB instances —CreateDBInstanceaccepts it,ModifyDBInstancechanges it, andCreateDBInstance,ModifyDBInstanceandDescribeDBInstancesall report it. It is the field that says whether a database is meant to be reachable from outside the VPC it was placed in, and until now Overcast did not have it at all: there was no way to ask for a database in a VPC that stays dialable from your machine, and no way for a template that sets it to round-trip. Unstated, it defaults the way AWS defaults it —truewithout aDBSubnetGroupName, because that instance lands in the region's default VPC and Overcast seeds that VPC with an internet gateway, andfalsewith one, because a named subnet group is a chosen placement that AWS treats as private. An instance created before Overcast had the field reports the value its create would have been given, so nothing an existing database says about itself changes on upgrade. Instance-level, as on AWS: an Aurora cluster carries noPubliclyAccessible, andCreateDBClusterdoes not accept one -
[web/lambda] a function's Configuration tab now has an Asynchronous invocation section showing its dead-letter target, with an Edit that sets or clears one — the console half of
DeadLetterConfig, which until now could only be set from an SDK or a template. It states the retry policy rather than offering fields for it — a failed invocation is retried twice by default — and points atPutFunctionEventInvokeConfigfor the settings this page does not edit, so nobody looks for a field that is not there
Changed
-
BREAKING [appconfig]
CreateHostedConfigurationVersionandGetHostedConfigurationVersionanswer with the configuration content as the response payload and the metadata in theApplication-Id,Configuration-Profile-Id,Version-Number,Description,Content-TypeandVersionLabelheaders, as AWS binds them. The create used to answer a JSON envelope, and both used inventedAppConfig-*header names
migration: read the version number from theVersion-Numberheader rather than from a JSON body -
BREAKING [appconfig]
Application,EnvironmentandConfigurationProfileresponses carry the members AWS models and no others; the inventedArnandTagsmembers are gone, andListConfigurationProfilesanswers profile summaries withValidatorTypesrather than whole profiles
migration: read tags throughListTagsForResource; an AppConfig ARN isarn:aws:appconfig:{region}:{account}:application/{id}[/environment/{id}|/configurationprofile/{id}] -
BREAKING [appconfig] configuration content over 1 MB comes back as
PayloadTooLargeExceptionwith HTTP 413, the error AWS binds to that operation, in place ofBadRequestExceptionwith HTTP 400
migration: match onPayloadTooLargeExceptionwhere a handler tested forBadRequestException -
BREAKING [backup] timestamps are epoch seconds, the encoding REST JSON binds them to, in place of RFC 3339 strings that every AWS SDK rejected outright when deserialising a response
migration: none for SDK callers; a hand-written client readingCreationDateorDeletionDateas a string must read a number -
BREAKING [backup] vaults and plans are scoped to the region they were created in, as on AWS, where one vault of a given name was shared by every region while its ARN named the creating region
migration: vaults and plans recorded by an earlier version are not visible to this one; recreate them -
BREAKING [backup]
DeleteBackupVaultanswers an empty 200, theUnitoutput AWS models, in place of an invented document
migration: read the vault's ARN and name from theCreateBackupVaultorDescribeBackupVaultresponse instead of the delete's -
BREAKING [backup]
ResourceNotFoundException,AlreadyExistsException,MissingParameterValueExceptionandInvalidParameterValueExceptionreplace the unmodeledValidationExceptionand the 404 status; AWS Backup models noValidationExceptionand gives all four HTTP 400
migration: match on the modeled error codes, and on HTTP 400 rather than 404 for a missing vault or plan -
[bedrock]
Conversereturns a completeConverseResponse:usage.totalTokensis present, as the AWS model requires, and the token counts andmetrics.latencyMsnow read zero because no inference is performed -
[bedrock]
InvokeModeltreats its request and response bodies as the opaque model-specific payloads AWS models, rather than reading{"prompt":…}and answering withConverse's shape; the canned reply is a singleovercastEmulatorfield, and an absent payload gets aValidationException -
BREAKING [cognito] the JWT issuer and OIDC discovery drop their region path segment:
issis now{endpoint}/{poolId}and discovery is served at/{poolId}/.well-known/openid-configuration, matching the path portion of AWS'shttps://cognito-idp.{region}.amazonaws.com/{poolId}. AWS carries the region in the hostname; Overcast serves one origin and a pool ID is{region}_{suffix}, so the segment restated what the pool ID already said
migration: tokens minted before this release fail validation — sign in again to get one carrying the new issuer. A client that hard-codes the issuer or the discovery URL drops the region segment; one that readsissuerout of the discovery document needs no change. Overcast's own validation reads the pool ID from the issuer path and is unaffected either way -
[compat] every suite's
lambda-crud:UpdateFunctionConfigurationnow sends aDeadLetterConfigand checks the response and a follow-up read, in all seven suites that implement the group — cli, go-sdk, node-js-sdk, python-sdk, java-sdk, dotnet-sdk and rust-sdk. The test previously sent a timeout and, in most suites, checked only that the call did not error, so the member that brokecdk deployfor every function with a dead-letter queue was wire-observable through all seven clients and caught by none of them. Each suite provisions a real SQS queue and uses its ARN rather than a plausible-looking string, so the assertion means the same thing against real AWS as it does here -
BREAKING [ec2/lambda/ecs/rds/elasticache] naming a VPC now restricts what a resource can reach, as it does on AWS. A Lambda or ECS task with a VPC configuration reaches resources in that VPC and no others; a resource in a VPC is not reachable from a caller outside it. Previously placement was additive and everything reached everything, so a test proving "my function can reach my database" passed whether or not the VPC wiring was correct — and then failed on deploy. A forbidden connection is refused by name, with a log line identifying the resource, the caller and the networks each is on, rather than hanging
migration: if something stopped resolving, the stack describes a connection that would not work on AWS either. Put the caller in the same VPC (VpcConfig/awsvpcConfiguration), or open the resource with the AWS field that does it:PubliclyAccessible: trueon an RDS instance,assignPublicIp: ENABLEDon an ECS task. There is no Overcast-specific override, on purpose — the fix that works here is the fix that works deployed. -
BREAKING [eks]
DescribeIdentityProviderConfigis aPOSTto/clusters/{name}/identity-provider-configs/describenaming the config in the body, and answers the modeled shape with the config nested underidentityProviderConfig.oidcalongside its ARN, status and tags
migration: replaceGET /clusters/{name}/identity-provider-configs/{type}/{name}with the POST, sending{"identityProviderConfig":{"type":"oidc","name":"…"}}, and read the result fromidentityProviderConfig.oidc -
BREAKING [eks]
ListInsightsis aPOSTto/clusters/{name}/insightscarryingfilter,maxResultsandnextTokenin the body
migration: replace theGETwith thePOST; an empty body{}returns what theGETused to -
BREAKING [iam] the IAM action a policy is evaluated against now uses AWS's action prefix rather than Overcast's internal service name, so policies written from the AWS documentation match for the first time. Affects
OVERCAST_ENFORCE_IAM=trueonly; the other 40 services already used AWS's name
migration: update policies written against the old names —msk:→kafka:,stepfunctions:→states:,efs:→elasticfilesystem:,opensearch:→es:,appregistry:→servicecatalog:,elbv2:→elasticloadbalancing:,cognito:→cognito-idp:,waf:→wafv2: -
[lambda]
CreateFunctionandUpdateFunctionConfigurationno longer answer501when the request carriesDeadLetterConfig. Under CloudFormation that refusal failed the resource, and with it the stack and the wholecdk deploy, for any function that named a dead-letter queue. Both operations moved together on purpose: implementing only the create path would have let the first deploy succeed and failed the redeploy that touched the function's configuration -
[lambda] an event source mapping's
DestinationConfig.OnFailure.Destinationmay now be an SNS topic, not only an SQS queue, which is what AWS accepts. The two failure paths — an ESM's on-failure destination and a function's dead-letter queue — now share one delivery, through the sameinternal/eventtargetdispatcher EventBridge and Pipes use, so a destination that does not exist reports the sink's own error instead of silently going nowhere -
[msk] a malformed request is answered with
BadRequestException, the only client-error shape the kafka model declares, in place ofValidationException -
BREAKING [opensearch] domains are scoped to the region they were created in, as on AWS, where one domain of a given name was shared by every region
migration: domains recorded by an earlier version are not visible to this one; recreate them -
[opensearch]
ResourceNotFoundExceptioncarries HTTP 409, the status AWS documents for it, in place of 404, and a store failure is reported as OpenSearch's ownInternalException -
BREAKING [router/web/cognito] every endpoint Overcast serves that is not an AWS API now sits under one reserved prefix,
/_overcast/. Sixteen internal path roots are now one, reserved by an S3 naming rule — a bucket name cannot begin with an underscore — which is a guarantee worth spending on a single prefix rather than on sixteen roots, each of which some future AWS service could want. The server's own endpoints move first:/_health,/_metrics,/_topology,/_/info,/_eventsand/_internal/domains/watchbecome/_overcast/health,/_overcast/metrics,/_overcast/topology,/_overcast/info,/_overcast/eventsand/_overcast/domains/watch, andovercast-mcp's own health endpoint moves the same way. The debug namespace and MCP follow:/_debug/*is now/_overcast/debug/*— all 23 routes, being state, traces, trace search, metrics, config, reset and the wholepproffamily — and/_mcpis now/_overcast/mcp. So do the per-service admin endpoints:/_lambda/instances,/_lambda/runtimesand/_lambda/layers/…under/_overcast/lambda/,/_ecs/…under/_overcast/ecs/,/_rds/…under/_overcast/rds/, EKS'sPOST /clusters/{name}/kubeconfigasPOST /_overcast/eks/clusters/{name}/kubeconfig, and the mail inbox from/_overcast/inboxto/_overcast/ses/inbox, where it sits with the SES admin routes it belongs to. The emulator-only Lambda endpoints the console uses leave AWS's path space with them —GET|PUT /2015-03-31/functions/{name}/source,GET /2015-03-31/functions/{name}/test-events,PUT|DELETE /2015-03-31/functions/{name}/test-events/{eventName}andPOST /2015-03-31/functions/{name}/invoke-with-progressare now under/_overcast/lambda/functions/{name}/…, having been invented paths inside a prefix AWS models, which is both misleading and a collision waiting for AWS to bind those sub-resources. The emulated data plane is the last group:/_appsync/{apiId}/…is now/_overcast/appsync/apis/{apiId}/…,/_cloudfront/{distId}/…is/_overcast/cloudfront/distributions/{distId}/…,/_apigateway/execute-api/…is/_overcast/apigateway/execute-api/…,/_lambda/url-invoke/…is/_overcast/lambda/url-invoke/…,/_elbis/_overcast/elb, API Gateway's WebSocket management API moves from/@connections/{apiId}/{stageName}to/_overcast/apigateway/connections/{apiId}/{stageName}, and Cognito's managed login from/_cognito/{poolId}/…to/_overcast/cognito/user-pools/{poolId}/…. Lambda's function-URL invoke path is untouched
migration: nothing an AWS SDK sends is affected — no AWS API path has ever started with an underscore — and the web UI andovercastCLI need no action, reaching all of these through their own API. Nothing changes for host-addressed callers either: an execute-api, AppSync or Lambda function URL resolves to these paths internally, so the URL you were given still works, as do application redirect URIs. What does need updating: container healthchecks and any script or dashboard polling the server endpoints (wget -qO- http://localhost:4566/_overcast/healthin place of/_healthis the common one — the shippedDockerfileand both compose files are already updated, but adocker-compose.ymlyou maintain yourself is not); MCP clients, which point at/_overcast/mcp; anything curling the debug endpoints by hand, where/_overcast/debug/state,/_overcast/debug/tracesand/_overcast/debug/metricsare the common ones andgo tool pprof http://localhost:4566/_overcast/debug/pprof/heapreplaces the/_debugform; a direct caller of the admin or emulator-only Lambda endpoints (aws eks update-kubeconfigis unaffected, being a CLI-side command that never sends this request); a hard-coded Cognito hosted-UI URL, though the OIDC discovery document regenerates itself so a client readingauthorization_endpointneeds no change; and a caller posting to/@connections/…by path rather than through the API Gateway Management API endpoint.OVERCAST_DEBUGstill gates the debug namespace, IAM policies are unchanged, the per-function authorization on the source and test-event endpoints is preserved, and JWT issuers and token validation are unaffected
Fixed
-
[appconfig] every AppConfig operation is served at the binding AWS models —
/applicationsand everything beneath it, and/tags/{ResourceArn}— so an unmodified SDK, CDK construct oraws appconfig …call reaches it instead of a bare 404 -
BREAKING [appconfig/appregistry]
POST /applicationssigned forappconfiganswers with an AppConfig application; it used to be answered by Service Catalog AppRegistry, returning aservicecatalogARN with HTTP 200 and writing into AppRegistry's store, with no error raised anywhere.GET,PATCHandDELETE /applications/{id}behaved the same way
migration: sign AppConfig calls with anappconfigSigV4 credential scope, which every AWS SDK does. Unsigned callers andservicecatalog-scoped callers still reach AppRegistry -
[appconfig]
ListTagsForResource,TagResourceandUntagResourcereach AppConfig's own tag store; an AppConfig ARN used to fall through to API Gateway's service-agnostic store, which answered 200 with an empty map for any ARN at all, and an ARN naming nothing now answersResourceNotFoundException -
[appconfig] deleting an application, environment or configuration profile deletes its tags, which used to be left behind for the next resource of the same identity to inherit; a single undecodable record is skipped and logged instead of failing a whole listing
-
[appconfig/cloudformation]
AWS::AppConfig::Application,::Environmentand::ConfigurationProfileprovision over the modeled REST binding, signed asappconfigso the dispatcher does not hand them to AppRegistry -
[appconfig/router] AppConfig requests are classified as
appconfigrather thanappregistry, so they are logged, metered and authorised under the service that answers them, and a modeled AppConfig path Overcast does not implement answers a protocol-correct 501 instead of an empty 404 -
[appconfigdata] both operations are served at AWS's own bindings —
POST /configurationsessionsandGET /configuration— so an unmodified SDK, CDK construct oraws appconfigdata …call reaches them instead of answering 501, andGetLatestConfigurationreads the session token from theconfiguration_tokenquery parameter, which is where every AWS SDK puts it rather than the path segment no client sends. Session handling follows AWS too: a configuration token is single-use and expires after 24 hours, so a spent or stale one getsBadRequestException, and a store failure while reading a session is reported asInternalErrorin place of an invalid-token error, with a single undecodable session record skipped and logged -
BREAKING [appconfigdata]
StartConfigurationSessionvalidates that its three identifiers are present and within the modeled 128 characters, and thatRequiredMinimumPollIntervalInSecondsis between 15 and 86400, answeringBadRequestException
migration: supplyApplicationIdentifier,EnvironmentIdentifierandConfigurationProfileIdentifier, and keep any requested poll interval inside 15-86400 seconds -
[appconfigdata/router]
detectServiceclaims/configurationand/configurationsessions, so these requests are logged and IAM-authorised asappconfigdatarather than falling through to S3's bucket routes -
[appsync]
EvaluateCodeandEvaluateMappingTemplateare served at the bindings AWS models —POST /v1/dataplane-evaluatecodeandPOST /v1/dataplane-evaluatetemplate— instead of under/v1/apis/{apiId}/…, so an unmodified SDK, CDK construct oraws appsync evaluate-codecall reaches them for the first time -
[appsync]
EvaluateCodereadscontextas the JSON string AWS models rather than an object, so a spec-shaped request no longer answersSerializationException; both operations now validateruntime,code,templateandcontextagainst the model, and both answer the modeled response envelope withevaluationResult,error,stashandlogs -
[appsync] an unimplemented path under
/v1/apis/{apiId}/answers aNotImplementedAWS error instead of a bare 404 from the sub-router -
[backup] every AWS Backup operation is served at the binding AWS models — vaults under
/backup-vaultsand plans under/backup/plans— so an unmodified SDK, CDK construct oraws backup …call reaches it instead of answering 501; Overcast registered no routes for this service at all. That includes the three operations AWS binds to a collection URI with a trailing slash —ListBackupVaultsatGET /backup-vaults/, andListBackupPlansandCreateBackupPlanat/backup/plans/— which is the spelling every SDK sends. Both spellings are registered, so a hand-written caller using the slash-less form keeps working too -
BREAKING [backup]
CreateBackupPlanmints a UUID forBackupPlanId, as AWS does, where the identifier was derived from the clock and two plans created in the same nanosecond collided — the second silently replaced the first
migration: treatBackupPlanIdas opaque; anything deriving a plan id from the creation time, or parsing the oldplan-<nanoseconds>form, must read it from the create response instead -
[backup]
UpdateBackupPlanmints a newVersionIdper update and returns the modeledCreationDate, where every update after the first used to report the same hardcoded version; and a single undecodable vault or plan record is skipped and logged instead of failing a describe with a 500 or disappearing from a listing without trace -
[backup/cloudformation]
AWS::Backup::BackupVaultandAWS::Backup::BackupPlanprovision over the modeled REST bindings, and the plan handler addresses the plan by id when deleting it, where it sent the ARN it uses as the physical ID and matched nothing — so a stack delete left the plan behind -
[backup/router] AWS Backup requests are classified from their path prefixes, so they are logged and authorised as
backuprather than falling through to S3 when unsigned -
[bedrock]
InvokeModelandConverseare served at the paths AWS binds them to —POST /model/{modelId}/invokeandPOST /model/{modelId}/converse— so an unmodified SDK reaches them instead of getting a 501;modelIdmay be an ID or an ARN -
[cloudformation] AWS::RDS::DBInstance forwards PubliclyAccessible. It was dropped at the template boundary, leaving a template's own opt-out from a subnet group's private default with no effect.
-
[cloudformation] every stack event carries a
ClientRequestToken, as on AWS — the caller's when the operation supplied one, and otherwise the request ID of the API call that started the operation. That fallback is what makes a failure traceable: the request ID is the key/_overcast/debug/trace/{requestId}is served under, so an event's token opens the request behind it, with every internal service call the operation made, their bodies and statuses, and its log lines. Before this a stack failure was an orphan — the only route back to the requests behind it was guessing from timestamps across a CDK deploy that issues thousands. The token belongs to the operation rather than the stack, so a create and a later update are told apart, and a nested stack's events carry the token of the parent operation that provisioned it. Real CloudFormation fills the field the same way when an operation arrives without one (the console'sConsole-CreateStack-<uuid>) -
[cloudformation] a persistence flush that does not finish no longer fails a stack that provisioned. A deploy died on
CREATE_FAILED — persistent state flush failed: context deadline exceededagainstAWS::CloudFormation::Stack, naming no resource because no resource was involved: every resource had been created and the stack had reachedCREATE_COMPLETEbefore the end-of-operation flush ran out of its five seconds. That flush is store-wide, so it carries every service's queued writes and a deploy that has just uploaded its assets can have far more than five seconds of them, and an uncommitted batch is neither lost nor abandoned — it goes back at the head of the pending queue and the pending log replays it after an unclean exit. It is logged now, into the trace of the request that started the operation as well as the server log, and the stack keeps the status it reached; whether the store is keeping up is what/_overcast/health'spersistent.pendingWritesand/_overcast/debug/metrics'flushHistoryare for -
[cloudformation] a stack that fails logs why, so the reason reaches the trace of the request that started the operation. The provisioning goroutine already carries that request's recorder; the failure reason only ever went to the stack event, so a reader working from the trace saw every internal call the deploy made and no statement of what went wrong at the stack level
-
BREAKING [cloudformation] a template over either of AWS's size quotas is refused with a
ValidationError, rather than accepted. An inlineTemplateBodyis capped at 51,200 bytes, and a template fetched from aTemplateURLat 1,000,000 — a decimal megabyte, which the quotas table states only as "1 MB" but AWS's own error names exactly ("Template may not exceed 1000000 bytes in size."); reading it as a mebibyte would have accepted 48,576 bytes' worth of templates AWS refuses. The inline cap belongs to the parameter, soCreateStack,UpdateStack,CreateChangeSet,ValidateTemplateandGetTemplateSummaryall apply it, and the URL cap covers a nested stack's child template because it arrives through the same parameter. Marked breaking because a template that deployed against Overcast yesterday may not today — though it would never have deployed against AWS, which is the divergence being closed
migration: for a template over 51,200 bytes, upload it to S3 and passTemplateURLinstead ofTemplateBody— what the same template already requires against real AWS.aws cloudformation deployandcdk deploydo this for you; a hand-rolledcreate-stackis the case that has to change. Over 1,000,000 bytes there is no parameter that helps: split the stack, or use nested stacks, as on AWS -
[cloudwatch] an alarm carrying metric math (
Metrics), an extended statistic or an anomaly band is created and declares what will not happen to it, instead of being refused with a 501. The refusal's reasoning was sound — an alarm that looks armed but is never evaluated is worse than no alarm — but the price was the whole environment: CDK'sMetric.createAlarmon aMathExpressionemits one of these per function, and a501failed the CloudFormation resource, the stack and the deploy over a resource whose only defect is one Overcast will not act on. It now says so in the three places anyone looks — the alarm'sStateReason, anx-overcast-emulation-limitationresponse header, and the CloudFormationResourceStatusReason, carried on theCREATE_COMPLETEevent so a deploy shows it as the resource goes by. An ordinary single-metric alarm carries none of that, because the signal is only worth anything if it marks the exceptions. PromQL alarms (EvaluationCriteria) are still refused, and a metric-math alarm that also names a top-levelNamespace/MetricNamestill gets the400 ValidationErrorAWS itself answers with -
[console] the WAF "Metadata only — Web ACL rules are not enforced" notice is readable on the light theme. It was styled with a fixed dark-theme amber (
text-amber-100, a near-white shade) on a panel that is near-white in light mode, so both its heading and body text were invisible there — the warning was present in the DOM and unreadable on screen. It now uses the semanticwarningtokens, which follow the theme -
[debug] unsigned requests are named in the request log and the debug trace instead of being labelled
s3. Signed traffic was never affected — the credential scope answers a step earlier — but unsigned traffic is the web UI, curl, and anything else that skips SigV4, and all of it was logged under a service it never touched: unfindable under the trace list's Service filter and under the log'sservicefield, and put in front of IAM enforcement as ans3:action. Four separate causes fed that one fallback. Query-protocol requests were the largest:detectServicetakes the request body variadically and the logger passed none, silently disabling the only step that can classify SQS, SNS, CloudFormation and the rest, whose operation is in the form body with no distinguishing path to go on — the bytes now come from what the handler had already read, so nothing is read a second time andbody()'s top-up from the socket stays reserved for the 5xx path. SQS and CloudWatch were additionally absent from the generated index classification reads, because it was built from each model's primary protocol alone: CloudWatch declaresawsQueryalongsideawsJson1_0, and SQS declares no Query at all, AWS having retired the trait from the model while still accepting the wire — which is what the AWS CLI and the web UI send. Smithy RPC v2 requests (/service/{service}/operation/{operation}) are now classified by the service and operation named in their own URI, gated on theSmithy-Protocolheader, because without it the router really does hand the same path to S3, whereserviceis an ordinary bucket name; the router's own registered services are consulted before the generated registry, which is what names ECS, CloudWatch Logs, SQS and WAF, all of which answer CBOR on a wire the pinned models bind no RPC v2 operation on. EventBridge and RDS were unattributable in the generated registry, which blanks the service when several modeled AWS services declare one wire key rather than guessing — but Overcast knew the owner in both cases and now says so:cloudwatch-eventsandeventbridgeare one service under two modeled identities, so all 51 sharedX-Amz-Targetcollisions were never collisions, and DocumentDB, Neptune and RDS share the2014-10-31API version and most of its action names across 71 query collisions where Overcast implements only RDS, which each Query service'sOwnsVersionalready routes by. The knock-on for IAM enforcement matters as much as the labelling: RPC v2 requests carried no operation at all, so its unnamed-action branch let them through, and an unsigned RDS Query request was labelled with the empty string and reached enforcement as if that were a service key — it now falls through to the documenteds3fallback, as theX-Amz-Targetstep already did. A label naming no service is still not believed, and a collision neither declaration resolves stays unattributed, as the four Timestream targets do -
[debug] a trace says why a body is missing instead of rendering an empty panel. Hop bodies were the silent case: a trace stops retaining them once it has kept 8 MiB (
MaxHopBodyBytes), which a CDK deploy reaches partway through, and every hop after that rendered no body section at all — indistinguishable from a hop that carried none. Each body now carries a reason (requestBodyOmitted/responseBodyOmitted), and the UI shows it: an inline chip when a prefix survived a size truncation, a notice in place of the body when nothing did, saying which limit applied and that the hop's service, operation, status, timing and ordering are still intact. Where the body was dropped for that budget the notice links to the hop's own trace, which holds it in full — every hop is dispatched through the router and has a trace of its own, so the copy on the parent is a duplicate and dropping it hides the body rather than losing it, which is the distinction the reader needs. The reason also separates three losses the oldrequestBodyTruncated/responseBodyTruncatedbooleans could not tell apart — a body cut short at 1 MiB, a body dropped for the per-trace hop budget, and a streamed response whose body was never captured at all — and those booleans are unchanged on the wire, now derived from the reason so the two cannot drift -
[debug]
GET /_overcast/debug/traces/search?q=…finds a retained trace by something it said — a hop response body, a hop error, a log line — rather than only by what it is. That is where the explanation of a failure actually lives: the CloudFormation stack failure that readspersistent state flush failedis a log line, and the ECS pull failure that readsImageNotFoundExceptionis an ECR hop's response body, and neither was reachable from the search box. Each match names where it hit (which hop, which log level) and carries an excerpt with the matched span split out, so a result places itself instead of only asserting that an answer exists somewhere. The scan is budgeted, resumable and cancellable, because the worst case is the case worth searching: a ring full of CDK deploy traces is ~8 GB of bodies. One call reads 64 MiB and hands back a cursor; a client that goes away cancels the request context, and the scan checks it between hops rather than between traces, so an abandoned search stops in milliseconds instead of running to completion for a result nobody will read. Nothing is copied to scan it — the matcher takes slice headers under a brief read lock and runs outside every lock, so searching a deploy does not block the deploy still writing to it -
[debug] neither listing traces nor admitting one scans the whole ring any more, which is what makes the larger retention limits affordable. Listing used to materialise and sort every retained trace to return fifty of them — 64 µs and a 164 KB allocation per poll at 1000 traces, and 700 µs / 1.6 MB at 10,000, once a second behind the trace list — and now reads one page at 3.2 µs and 8.4 KB, with a cost that no longer grows with how much is retained. Admitting an internal trace used to walk every retained entry to recycle the oldest poll once the internal population was full, on every health check: 44–53 µs at 10,000 retained traces, against 0.55 µs now
-
[debug] the trace list's own search matches a request's operation and the AWS error it answered with, alongside the request ID, path and service it always did. Every RPC-protocol request is
POST /, so the operation was the only thing distinguishing one trace from the next and it was the one thing search could not see — finding "theRunTaskthat returnedAccessDenied" meant paging the list by eye. This search stays deliberately cheap: matching bodies and log lines means scanning up to 8 MiB per trace, which is not something a list call polled once a second can do inline, and a test pins the separation so that it is crossed on purpose — reaching them is what the budgeted deep-search endpoint above is for. A query that matches nothing no longer allocates per trace either, which is every keystroke until it starts matching: on a full 1000-trace buffer that is 38 µs and 2 allocations per list call, against 88 µs and 1002 before -
[debug] trace retention is bounded by bytes as well as by count. The retention ceiling counts traces, and a count is a poor proxy for memory when one trace can be a thousand times the size of another — ten thousand
DescribeStackspolls are tens of megabytes, while ten thousand 1 MiB uploads are ten gigabytes, which is what a seeding script produces.OVERCAST_DEBUG_TRACE_BYTES_MB(default 512) reclaims the oldest overflow until the retained request and response bodies fit, and it bounds retained memory in full: traces kept because they failed were exempt from it and capped only by count, so the worst case wasOVERCAST_DEBUG_TRACE_PINNEDtraces of up to ~2 MiB each — around 2 GB at the defaults — however small the budget was set, and lowering it because your machine was struggling did nothing about the memory actually being held. It is a backstop on the burst, not an override of the floor: it reclaims down toOVERCAST_DEBUG_TRACE_BUFFERand stops, because a floor that is itself over budget is what the operator asked for and quietly discarding it would be the worse failure. Reclaiming goes cheapest-first — ordinary overflow above the floor before anything else, and a kept failure only when there is nothing cheaper left, so a failure stays vastly more durable than an ordinary trace while no longer being unbounded -
[debug] a retained failure keeps the calls it made. Pinning a failed deploy without them was half a rescue: each hop names a trace of its own — that is where its bodies live — and those traces are newer than the parent, so they were evicted moments after it, leaving a pinned timeline whose every body read "no longer retained". The calls are now pinned alongside the failure they belong to, but only ever into room that is spare, because two failures are two things somebody may come back for: a deploy with more calls than there is room keeps the ones that fit, and the rest still show their service, operation, status, timing and ordering, saying that only the body is gone. Calls are capped at half the ring for the same reason — a CDK deploy makes thousands of them, and left uncapped they crowded older failures towards the head of the ring until the next failure to arrive evicted one, which is a failure displaced by another failure's calls: a step later than the guarantee checked for, but displaced all the same. Failures now compete only with failures
-
[debug] a trace no longer keeps its own copy of every internal call's request and response body. Each hop is a router-dispatched request with a trace of its own, so the copy on the calling trace duplicated bytes the buffer already held — for a CDK deploy, up to 8 MiB per deploy trace on top of the traces those same bodies lived in. Hop bodies are resolved from the call's own trace when a trace is read, and the ring holds each body once. A hop whose own trace has since been evicted says so (
evicted) instead of rendering an empty body panel, and the per-view inlining limit says the body was not shown here rather than dropped, it being intact on the hop's own trace one click away. Deep search stops scanning the same bytes twice as a result: a body matches once, on the trace that owns it, rather than on both that trace and every trace whose hop copied it, and thehopRequestandhopResponsematch kinds are gone with the copies — searching for an error an internal call returned still finds it, attributed to that call, with the hops tab linking back to whatever made it -
[debug] retention says what it did, rather than leaving you to infer it. The trace list now states what is past the end of it — a list that simply stops cannot be told apart from a bug, since you cannot tell whether the request you are looking for was never traced or was traced and reclaimed — so the end reads "1,204 older traces no longer retained — 1,204 aged out after 1h · oldest retained 14:22:07", naming the rule that reclaimed them rather than just the number. A trace kept because it failed is marked
kept: error, because retention holds failures past the point where their neighbours are reclaimed and an old row sitting among new ones would otherwise read as eviction being broken. AndGET /_overcast/debug/traces/countcarries the full picture: per-ring occupancy, the floor, ceiling, window and pinned limits, retained bytes against the budget, the oldest retained timestamp, and running counts of what was reclaimed by each rule, withcountandcapacitykeeping their previous meanings -
[debug] the request that explains a failure is still there when you go looking. A
cdk deploypushes thousands of requests through in a couple of minutes and used to overrun the trace buffer — and because a failed deploy rolls back rather than stopping, what survived was the chatty teardown while the400that started it had already been evicted. Three rules now keep it. Traces that went wrong (a 4xx/5xx, an AWS error code, or a failed internal hop) are kept regardless of how much traffic follows them, up toOVERCAST_DEBUG_TRACE_PINNED(default 1000). A burst is no longer capped at the buffer size: retention grows pastOVERCAST_DEBUG_TRACE_BUFFER(default 1000) up toOVERCAST_DEBUG_TRACE_CEILING(default 10000) and holds the overflow forOVERCAST_DEBUG_TRACE_WINDOW(default 1h) before reclaiming it — sized for the gap between a deploy failing and somebody opening the trace UI, not for the deploy itself, so nothing needs configuring in advance, which was the point: by the time you know you wanted a bigger buffer, the trace that told you is gone. And infrastructure polling keeps its own ring, so an open web UI, a health check or an SSE reconnect can no longer evict a request you made on purpose — a buffer sized 1000 retains 1000 user-facing traces plus up to 200 internal ones, where the two previously shared those 1000, idle polling cost a fifth of the budget, and at a full buffer an arriving poll evicted the oldest real request. A trace is classified as worth keeping when it is evicted rather than when it arrives, because a request's status does not exist at request start and a CloudFormation hop can fail minutes later on a goroutine that outlives the request — anything decided earlier would miss exactly the asynchronous failures a deploy produces -
[debug] what makes that error searchable is a trace recording it at all, which nothing did.
protocol.recordAWSErrorfinds its recorder by type-asserting whicheverResponseWriterthe handler holds — and a wrapper that embeds one satisfies that assertion whether or not it honours it, so an unforwarded wrapper ends the chain without failing: the assertion succeeds, the value lands on an object nothing reads, and nothing warns. Three layers were doing it.RequestEventsbuilds a secondresponseWriteraroundLogger's, which cost every service its recorded error; DynamoDB wraps again for theX-Amz-Crc32header its clients check, and CloudFront for access logging, which cost those two even once the first was fixed. Every wrapper in the serving path now forwards, including the two that sit outsideLogger's writer today and so record nothing — the guarantee should not depend on the middleware registration order, which is what put the first of these there. Two things beyond the search box come back with it: the request log regains itsaws_error_codefield, and retention can once more recognise a 2xx carrying an AWS error as a failure worth keeping rather than reclaiming it as ordinary traffic. A source-level ledger now fails the build when a new wrapper does not forward, because a test per wrapper only covers the ones somebody remembered — which is how this arrived twice -
[dns] the container resolver no longer fails to start on Windows hosts where Hyper-V or WinNAT has reserved part of the dynamic port range. It drew its port from a range the kernel walks in sequence, so a reserved block could swallow every retry; candidates are now drawn independently
-
[docs] the support matrix no longer advertises 43 operations as supported across appconfig, appconfigdata, appsync, backup, bedrock, eks, msk, opensearch and elasticache. Each is implemented and works, but is served on a path or method AWS does not use, so an SDK or CLI calling it gets a 501. Every entry now names the issue tracking the move; no behaviour changed, the matrix just says what a client actually gets
-
[docs]
docs/networking.md§ Lambda, ECS and VPCs is rewritten around the restriction: what is refused, the three ways out, what is still not enforced, and why a native Windows or macOS host is exempt -
[docs] A new guide, "The inner loop", covers editing code and seeing it take effect without a rebuild or redeploy: when
cdk watchis the right answer, how Lambda and ECS hot reload differ, the sharedOVERCAST_HOT_RELOADflag family, and a full Laravel-on-Fargate walkthrough. It leads with the mistakes that make a correctly configured mount look broken — OPcache'svalidate_timestamps=0ignoring your edits, a bind-mountedvendor/crossing a VM boundary on every request, andphp artisan queue:workcaching the application in memory. -
[ecr]
DescribeImagesraisesImageNotFoundExceptionfor animageIdsentry it cannot resolve, as real ECR does, instead of answering 200 with a short list. That answer is how a publisher decides whether to push: cdk-assets treats any non-throwingDescribeImagesas "already published", socdk deployskipped building and pushing every container asset and reported a clean deploy over an empty repository. The ECS service running the asset then failed to stabilise withCannotPullContainerError … status 404 … not found, naming a registry that had never received the image. A call that names noimageIdsstill returns an empty list, because only a requested identifier can be missing -
[ecr]
repositoryUrinames the registry this run is serving, re-minted on every read instead of frozen into the repository atCreateRepository. The address is the registry container's published port; repositories are persisted and the registry is not, so acdk bootstrapthat ran before the registry was up sent every later deploy'sdocker pushat Overcast's own API port —unexpected status from POST request to http://…:4566/v2/…/blobs/uploads/: 405 Method Not Allowed— while the pull side, which resolves through the live registry, was reaching:4510in the same deploy. When there is no registry at all the API base is still the only address there is to state, but Overcast now says so in one warning rather than leaving a 405 to explain it -
[ecr]
repositoryUriandproxyEndpointnamelocalhostrather thanOVERCAST_HOSTNAMEwhenever startup proved the Docker daemon can reach the registry there — which is exactly what the port-selection probe establishes, by having the daemon dial it. The two are not interchangeable to a daemon even when they name one machine: Docker trusts plain HTTP tolocalhostand bypasses proxies for it, while a hostname likelocalhost.overcast.shis an ordinary domain, so a machine with a proxy configured sentdocker pushto the proxy —proxyconnect tcp: dial tcp 192.168.65.1:3128: i/o timeouton Docker Desktop — and never reached a registry that was listening the whole time. When the daemon proves nothing (a remote one, or published ports that are not on its loopback) the configured hostname still stands, since that is the address itsinsecure-registriesneeds -
[ecr] image records no longer outlive the images. A registry whose storage went with its container — the ephemeral fallback, or any registry before persistence landed — left the records built from its manifests behind, so after a restart
DescribeImages,ListImagesandBatchGetImagedescribed images that no longer existed anywhere, and a publisher was again told to skip the push. Reading a repository now drops the records the registry answers 404 for; a record written byPutImageis left alone, having never been in the registry -
[ecs] a task that fails to start records the reason on the container that failed, not on every container in the task. A launch failure —
CannotPullContainerErrorfor an image that could not be pulled, and the create/start/network failures alongside it — was copied onto all of the task's containers, so a four-container task definition reported the same error against all four and each of them named the one image involved. A CDK deployment failing on its ECR container asset therefore blamed its X-Ray sidecar too, for an image the sidecar does not run. As on AWS, the culprit carries the reason and the rest areSTOPPEDwith none; the task's ownstoppedReasonis unchanged -
[ecs] A task definition can point a scratch volume at a directory on your machine, so a save is live in the task on the next request with no image rebuild and no redeploy. Tag the task definition
overcast:hot-reload-path/<volume-name>with an absolute host path and start Overcast withOVERCAST_ECS_HOT_RELOAD=true. The volume stays an ordinary name-only scratch volume — legal on Fargate, deployable as-is — and only the tag, which real AWS stores and ignores, redirects it locally, so nothing about the task definition changes meaning on a real deploy. With one redirectable volume the bareovercast:hot-reload-pathworks too, the same key Lambda takes. -
[ecs] RegisterTaskDefinition accepts
tagsand stores them against the revision's ARN, on both wire protocols. AWS tags a task definition at registration and CloudFormation and CDK rely on it; previously tags sent with the call were dropped and only a separate TagResource took effect. -
[ecs] A bind mount the daemon rejects now stops the task with a
stoppedReasonnaming the host paths involved and pointing at Docker Desktop's File Sharing setting, rather than Docker's own message, which names neither. -
[ecs] Task definitions honour every volume shape AWS models, not just EFS. A name-only volume is now a scratch directory shared by the containers that mount it — the shape an nginx sidecar and an application container use for a document root, and the only non-EFS volume Fargate allows.
host.sourcePathbecomes a bind mount from the container instance, anddockerVolumeConfigurationa Docker volume with the task definition's own driver and driver options. Previously all three were dropped at decode and their mount points skipped with a warning, so a task started with the mount silently missing and wrote into its own layer instead. -
[ecs] RegisterTaskDefinition rejects the volume configurations real ECS rejects:
host.sourcePathanddockerVolumeConfigurationon a Fargate task definition,autoprovisionon a task-scoped volume, an unknown scope, and more than one configuration on a single volume. The Fargate message matches AWS's own, so a task definition that would fail a real deploy now fails locally first rather than passing here and failing there. -
[ecs] A
dockerVolumeConfigurationwithscope: sharedandautoprovision: falseadopts a volume you created yourself, by the name the task definition gives it, and fails the task if it does not exist — as AWS does. Pre-creating that volume with the local driver's bind options is the supported way to hand an EC2-launch-type task a directory from the host. -
[ecs] Task-lifetime volumes are removed when the task stops, retried while a dying container still holds them, and swept at startup if the process did not survive to do it. Shared-scope volumes are never removed, matching ECS's treatment of a volume on a container instance, and
ECS_KEEP_CONTAINERS=truenow keeps volumes too. -
[ecs/rds/lambda]
ECS_KEEP_CONTAINERS,RDS_KEEP_CONTAINERSandLAMBDA_KEEP_CONTAINERSnow survive a restart. Every removal path honoured the flag except the startup sweep, so a container held back for post-mortem inspection was removed the next time Overcast came up — which is when the inspection usually happens. The sweep now returns without listing anything when the flag is set -
[efs] the export-container and materialization-helper sweep is scoped to this instance's own resources too, on the same identity labels.
-
[eks] shutdown no longer leaves a live-mode cluster behind or waits one out. Cleanup works from the runtime registry, which a bootstrap writes only once it has started its container, so a shutdown landing in that window tore down the containers it knew about and left a still-starting k3s container running; and the bootstrap ran detached from the service, so the five-minute readiness poll of a control plane that never answers had nothing to end it and ran against a shutdown budget every other service shares. That poll is now ended, and the cluster is left in a terminal state saying the shutdown stopped it rather than still claiming to be
CREATING -
[eks]
DescribeAddonVersions,DescribeAddonConfiguration,DescribeIdentityProviderConfig,ListInsights,UpdateAddonandUpdateNodegroupVersionare served at the paths and methods AWS models, so an unmodified SDK, CDK construct oraws eks …call reaches them instead of answering 501 — four of the six were registered on the wrong HTTP method, and two on a path one character away from a real one. Two of them also answer properly now that they are reachable:DescribeAddonConfigurationanswers the schema for the add-on version that was asked for and rejects a missingaddonNameoraddonVersion, where it used to ignore the requested version and answer from a table that held only one; andUpdateNodegroupVersionacceptsreleaseVersionandlaunchTemplateand no longer rejects a request that omitsversion, the model marking none of them required, soaws eks update-nodegroup-version --release-version …was refused -
[eks] the support matrix marks
UpdateKubeconfigas an Overcast extension rather than an AWS operation, and links theaws eks update-kubeconfigCLI command it stands in for instead of an AWS API page that does not exist -
[eks] a live-mode cluster's k3s control plane joins the VPC its
resourcesVpcConfig.subnetIdsname, instead of always landing on the default network. A cluster placed into a VPC that cannot take containers goes toFAILEDwith aConfigurationConflictissue rather than starting a control plane nothing in the VPC can resolve -
[elasticache] shutdown no longer waits out a container start that cannot finish. The background start every create path spawns ran on a context nothing cancelled, so one against an unresponsive Docker daemon held shutdown until the Docker client's own 30-second timeout expired — out of a budget shared with every other service's shutdown. They now run on the service context that shutdown cancels, and it cancels before waiting for them rather than after
-
[elasticache/cloudformation] A replication group created with a CacheSubnetGroupName is now placed in that subnet group's VPC, like a cache cluster. The field was accepted on the wire and discarded by both the Query handler and the typed request, and CloudFormation did not forward it, so a replication group always landed on the shared data plane and was the one container-backed resource that could not be isolated in a VPC. A group created without a subnet group still stays on the default plane.
-
[eventbridge/scheduler] AWS cron expressions are parsed by one shared implementation that understands the whole six-field syntax: the three-letter month and day names (
JAN,MON-FRI, case-insensitively), steps over a range (0-6/2), and theL,LW,<day>W,<day>Land<day>#<n>day specifiers. There were two partial implementations before, and ten forms AWS accepts were rejected by both. EventBridge's was the worse failure: it matched a raw expression field by field on every candidate minute, so a rule writtencron(15 10 ? * MON-FRI *)was stored happily and then never fired, "MON-FRI" having matched no number and nothing having said so -
[eventbridge]
PutRulerefuses a schedule expression it cannot honour instead of storing a rule that never fires, and says what was wrong with it: the expression, the field at fault, and — for the five-field Unix cron that AWS also refuses — the six-field form that was wanted. It previously answeredcron expression has no next fire within search window, after walking 2.6 million candidate minutes to get there -
[iam/msk] IAM enforcement no longer lets signed requests through unauthorized because it could not work out which service they were for.
detectServicereturned the SigV4 signing name from the credential scope —kafka,servicecatalog,monitoring,elasticloadbalancing— where the rest of the emulator is keyed by Overcast's service name, so the generated operation registry matched nothing, no action was inferred, andIAMEnforce's fail-open branch for unnamed actions passed the request on with no policy evaluated. MSK's whole v1 surface and AppRegistry's attribute groups were affected: withOVERCAST_ENFORCE_IAM=trueevery signed call to them was allowed regardless of policy. Log lines, trace badges and operation metrics for those services carried the signing name too, and now name the service -
[lambda] logs a container printed as it crashed or timed out are no longer dropped from CloudWatch Logs when the Docker log reader had not yet read anything from that container
-
[lambda]
OVERCAST_HOT_RELOADenables bind-mount source reload for every compute service at once, withOVERCAST_LAMBDA_HOT_RELOADandOVERCAST_ECS_HOT_RELOADoverriding it per service in either direction — so one service can be opted out of an umbrellatrue.OVERCAST_LAMBDA_HOT_RELOADon its own behaves exactly as before. -
[lambda]
GetFunctionConcurrencyreturns 200 with an empty body for a function that has no reserved concurrency, as AWS does. It answered404 ResourceNotFoundExceptioninstead, so an SDK client asking whether a function reserves concurrency caught an exception rather than reading the answer — and had no way to tell that apart from the function not existing, which is what the operation'sResourceNotFoundExceptionactually models. A reservation of0is still reported rather than omitted: zero is a reservation in its own right, AWS's documented way to switch a function off and the one place Overcast really does throttle, so conflating it with "unset" would have been a worse bug than the 404 -
[lambda/ecs/ses] successful responses now carry the request-ID header AWS answers every call with. Lambda's entire success surface — 38 operations across eight files — encoded JSON straight onto the ResponseWriter instead of going through a
protocol.Write*helper, and the header is the helper's job, so an SDK read emptyResponseMetadatafor every Lambda call that worked and a trace had nothing to correlate on. ECS's account-settings and container-instance handlers had the same hole while the rest of their own service already wrote throughprotocol.WriteAWSJSON, as did SESv2'sGetEmailIdentityandListTagsForResource. Nothing caught it because every request-ID assertion in the suites happened to be on an error response, and errors do go through a helper — so the gap was invisible exactly where it was widest -
[logging] SES's admin identity routes are logged as
sesrather thaninternal./_overcast/ses/identitieswas already under that prefix but only the inbox was named, so the identity routes fell through to the catch-all -
[msk] an IAM denial for MSK is now returned as JSON, the format MSK's own handlers and clients use, rather than in the Query XML envelope
-
[msk] the v2 cluster API is served at
/api/v2/clusters, the path AWS binds it to, soCreateClusterV2andDescribeClusterV2are reachable from an unmodified SDK instead of answering 501; they were registered at the invented/v2/clustersin every release sincev0.0.1-alpha.0 -
[msk]
CreateClusterV2rejects a request naming bothprovisionedandserverless, or neither, in place of silently creating a serverless cluster the caller never asked for -
[msk]
ListClusters'clusterNameFiltermatches on the cluster-name prefix, as AWS documents; it used to require the whole name -
[msk] a cluster's brokers now join the VPC its
brokerNodeGroupInfo.clientSubnetsname, instead of always landing on the default network. The subnets were stored and never resolved, so a Lambda function or ECS task created in the VPC reached the broker only because a VPC-placed resource also kept the default network — and on AWS there is no non-VPC MSK cluster, so this was the normal case rather than a corner one. A VPC whose Docker network could not be created now fails the container start instead of quietly placing the brokers somewhere the VPC cannot reach; a cluster with no subnets, or subnets EC2 has no record of, still uses the default network -
[opensearch] every OpenSearch operation is served at the binding AWS models — the domain surface under
/2021-01-01/opensearch/, andListDomainNames,AddTags,ListTagsandRemoveTagsdirectly under/2021-01-01/— so an unmodified SDK, CDK construct oraws opensearch …call reaches it instead of answering 501 -
BREAKING [opensearch]
CreateDomainanswersResourceAlreadyExistsExceptionfor a domain name already in use in the region, where a second create silently replaced the first
migration: delete the existing domain, or create under a different name -
[opensearch]
DeleteDomainremoves the domain's tags; they were written under the resource ARN and deleted by domain name, so a deleted domain left its tags behind for the next domain of that name to inherit -
[opensearch/cloudformation]
AWS::OpenSearchService::Domaindispatches over the modeled REST binding and addresses the domain by name when deleting it, where it sent the ARN it uses as the physical ID and matched nothing -
[opensearch/router] OpenSearch requests are classified from the
/2021-01-01path prefix, so they are logged and authorised asopensearchrather than falling through to S3 when unsigned -
[protocol]
x-overcast-emulation-limitationis a response header for stating what Overcast created but will not fully act on. It is additive and repeatable, so several limitations on one response each get their own line, and it is distinct fromx-emulator-unsupported, which marks a501— the difference being whether the request was carried out at all -
[rds] An Aurora member instance inherits its cluster's DB subnet group, and with it the cluster's VPC, when the instance names none of its own. CDK's rds.DatabaseCluster emits the member with no subnet group, so the writer previously landed outside the VPC its own application was in.
-
[rds] CreateDBInstance no longer demands MasterUsername and MasterUserPassword from an instance that names a DBClusterIdentifier. RDS keeps those on the cluster and rejects them on a member, so the shape CDK emits was refused outright; the member now takes the cluster's. A standalone instance still requires both.
-
[rds/ecs/efs/elasticache/lambda/msk] two Overcast instances sharing a Docker daemon no longer destroy each other's containers and volumes. Every sweep and every reconcile identified a resource by what it was rather than by whose it was, and two instances keep separate state stores, so each saw the other's live resources as orphans. Cleanup was the destructive half. The shutdown sweep listed every managed container for its service and stopped and force-removed the lot with no state check, taking out another instance's running RDS databases, ECS tasks, Lambda runtimes and MSK brokers; the startup sweep went further and destroyed data, because an RDS container carries no volume or bind mount and so keeps its database in its writable layer — one instance starting up permanently deleted another's stopped DB instances. ElastiCache's startup sweep had the same bug without even the ownership veto meant to guard it, ECS's task-volume sweep asked its own task records which volumes were abandoned and pulled the scratch and overlay mounts out from under a running task mid-flight, and EFS's live-mode reconciliation removed every managed volume whose file system was absent from its own store — which in live mode is the real data Lambda
FileSystemConfigsand ECSefsVolumeConfigurationmounts read and write. Matching was the other half: RDS, ElastiCache, MSK and ECS all paired a stored record with a container onovercast.resource-idalone, and the reconcile indexes kept one container per resource ID, so a neighbour's could shadow this instance's own before ownership was ever asked about. A DB instance identifier, cache cluster ID, replication group ID and serverless cache name are all names the caller chooses, so two Overcasts can each hold amydbor asessions— andStopDBInstancestopped the other emulator's running database, handing a live one to the next sweep to delete, while a rebuild adopted the neighbour's container off the shared name and left both emulators serving and stopping the same one. MSK cluster ARNs and ECS task IDs carry minted UUIDs, so their exposure is narrower: a stale container beside the live one for MSK, and for ECS a shared state directory, where a foreign container exit stopped a task still being placed — one with no container IDs recorded yet satisfies "nothing still running" vacuously — tearing down its volumes and charging the death to its deployment. Containers and volumes now carry the identity of the state store that created them: a sweep removes only its own, and a container is matched only when it carries this instance's identity, or carries none at all and the record itself names it, which is how containers created before the label keep being managed. ElastiCache and MSK also refuse to reuse a container another Overcast created for the same name, since a record whose container is gone rebuilds straight back through that path, and ECS's volume sweep now asks the daemon which volumes no container references — the one view every instance agrees on. That last fix does not transfer to EFS, where a file system is unreferenced whenever nothing is mounting it, which is most of the time. With the defaultmemorybackend the identity is minted fresh each start, so neither sweep reclaims anything from a previous run: an orderly shutdown still cleans up, and what leaks is a crashed run's containers and volumes, recoverable withdocker rm $(docker ps -aq --filter label=overcast.managed=true)anddocker volume prune --filter label=overcast.managed=true. Leaking is the safe direction to err. Found by running two instances against one daemon. -
[router] a Query-protocol request whose body is too large to parse as a form is answered
413 RequestEntityTooLargeinstead of501 NotImplemented. The 501 came from the sharedPOST /dispatcher treating a failed form parse as "no service claims this", so an oversizedCreateStackcame back carryingx-emulator-unsupported: true— telling tooling that Overcast lacks the operation, when the operation is implemented and it was the body that was refused. This applies to every Query service, none of which could say so before. The size at which a request is refused is unchanged: the newprotocol.MaxQueryRequestBodystates the ceilingnet/httpwas already enforcing unnamed, so nothing that worked before stops working -
[router] a source-level ledger now fails the build when a service handler writes its own success response, rather than waiting for someone to assert on the one operation that regressed. Emulator-only surfaces — the
/_<service>console routes, Cognito's hosted-UI and JWKS endpoints, and the in-container Lambda Runtime/Extensions API — are listed with the surface each one serves, and an entry that stops describing anything is failed as a stale excuse -
[scheduler] cron day-of-week is AWS's 1-7 from Sunday. EventBridge Scheduler read the field as Go's
time.Weekday— 0-6 from Sunday — under a comment asserting AWS did the same, socron(0 12 ? * 2 *)fired on Tuesday where AWS fires it on Monday, and7, being outside the range it allowed, never fired at all -
[state] a Scan or List against the hybrid store no longer drops a key, or resurrects a deleted one, when a background flush commits partway through the read. The base SQLite read was taken first and the pending-write overlay consulted afterwards, so a flush landing between the two halves moved a key out of the overlay and into SQLite after the base result had already been captured — leaving it in neither half. Every SQLite-backed namespace was exposed on every read (s3:objects, sqs:messages, lambda:function-code, ecs:tasks, kinesis:records and the rest), and memory-resident namespaces were exposed for as long as the startup seed was still running
-
[tests] a Docker-backed test server removes the two networks it minted for itself. They were named per test run so parallel packages could not race each other, and then never removed: a daemon subnets a few dozen networks out of its default address pools, so a suite that leaks a pair per server exhausts them, and from then on every
docker network createfails — which the emulator reports as "Docker not available", leaving ECS metadata-only and every container test failing for a reason unrelated to the code under test -
[tests] Docker-dependent test servers connect to the platform's Docker endpoint rather than a hardcoded
/var/run/docker.sock, which does not exist on Windows. Every such test built a server whose Docker client could not connect while the test's own gate connected fine, so the suite skipped or ran against an emulator with no Docker — container behaviour was verified on Linux only, which is how a broken ECR-to-ECS image pull shipped from a Windows workstation with a green suite
Removed
-
BREAKING [appconfig] the emulator-only
/_appconfig/*path prefix and the inventedAppConfig.X-Amz-Targetnamespace, which duplicated every operation on a wire contract the pinned model gives AppConfig no trace of
migration: use AWS's own bindings —POSTandGET /applications,GET,PATCHandDELETE /applications/{ApplicationId},…/environments,…/configurationprofiles,…/hostedconfigurationversionsandGET,POSTandDELETE /tags/{ResourceArn} -
BREAKING [appconfigdata] the emulator-only
/_appconfigdata/*path prefix, and the inventedAppConfigData.X-Amz-Targetnamespace with it
migration: use AWS's bindings —POST /configurationsessions, andGET /configuration?configuration_token=<token> -
BREAKING [appconfigdata] the non-AWS
AppConfig-Configuration-Versionresponse header onGetLatestConfiguration
migration: the configuration payload and theNext-Poll-Configuration-Token,Next-Poll-Interval-In-SecondsandContent-Typeheaders are the operation's whole modeled response; AWS never sent a version header here -
[appsync] the invented
AppSync.X-Amz-Targetnamespace and the Smithy RPC v2 CBOR surface behind it, which duplicated 70 operations on a wire contract the pinned model gives AppSync no trace of, and had already drifted from the REST handlers -
BREAKING [backup] the invented
AWSBackup.X-Amz-Targetnamespace and the Smithy RPC v2 CBOR surface it registered, neither of which the pinned model gives AWS Backup any trace of
migration: use AWS's own bindings —PUT,GETandDELETE /backup-vaults/{BackupVaultName},GET /backup-vaults,PUTandGET /backup/plans, andGET,POSTandDELETE /backup/plans/{BackupPlanId} -
BREAKING [bedrock] the emulator-only
/_bedrock/*path prefix, the inventedBedrock.X-Amz-Targetdispatch, and the Smithy RPC v2 endpoint that came with it
migration: call AWS's own paths — bedrock-runtime is modelledrestJson1and sends no target header, so an AWS SDK was never able to use any of the three -
BREAKING [cloudfront] the undocumented singular
/2020-05-31/distribution/{id}/monitoring-subscriptionalias. AWS binds monitoring-subscription to the plural/2020-05-31/distributions/{id}/..., which every SDK sends and which Overcast has always served
migration: none for SDK callers. A hand-written request to the singular path moves to the plural one -
BREAKING [eks/addons] the emulator-only
GET /addons/{addonName}/versionsandGET /addons/{addonName}/configurationroutes
migration: use AWS's paths —GET /addons/supported-versions?addonName=…andGET /addons/configuration-schemas?addonName=…&addonVersion=…, both of which take every input as a query parameter -
BREAKING [eks/nodegroups/addons] the emulator-only
POST /clusters/{name}/node-groups/{nodegroupName}/updatesandPOST /clusters/{name}/addons/{addonName}/updatesroutes
migration: drop the trailingsand use AWS's paths —.../node-groups/{nodegroupName}/update-versionand.../addons/{addonName}/update -
BREAKING [eks] the
DescribeAccessPolicyandUpdateIdentityProviderConfigcapability rows and the emulator-only routes behind them, which named operations AWS does not model at all and were advertised as supported
migration: read a policy's ARN fromListAccessPolicies, which returns the whole policy; replace an identity-provider-config update withDisassociateIdentityProviderConfigfollowed byAssociateIdentityProviderConfig, which is what AWS models -
BREAKING [msk] the emulator-only
/v2/clusterspath prefix
migration: use AWS's paths —POST /api/v2/clusters,GET /api/v2/clusters/{ClusterArn}with the ARN percent-encoded into a single path segment, as every AWS SDK sends it -
BREAKING [msk/cloudformation] MSK's
X-Amz-Targetdispatch and its typed JSON/CBOR surface, a second copy of the REST handlers for a protocol AWS does not model for kafka;AWS::MSK::ClusterandAWS::MSK::Configurationprovisioning now dispatches over MSK's REST routes
migration: call MSK over HTTP the way the AWS SDKs do — no MSK operation carries anX-Amz-Targetheader or a Smithy RPC v2 binding in the pinned model, so nothing an SDK sends is affected -
BREAKING [opensearch] the emulator-only
/_opensearch/*path prefix and the inventedOpenSearch.X-Amz-Targetnamespace, which duplicated every operation on a wire contract the pinned model gives OpenSearch no trace of
migration: use AWS's own bindings —POST /2021-01-01/opensearch/domain,GETandDELETE /2021-01-01/opensearch/domain/{DomainName},POST /2021-01-01/opensearch/domain-info,GET /2021-01-01/domain,POST /2021-01-01/tags,GET /2021-01-01/tags?arn=…andPOST /2021-01-01/tags-removal
Security
- BREAKING [s3/msk] IAM enforcement applies to every path under
/api; the middleware exempted the whole prefix for a console API that has never been behind it, so withOVERCAST_ENFORCE_IAM=truean S3 bucket namedapiwas served without a policy ever being evaluated — and so was MSK's v2 cluster API once it moved to/api/v2/clusters, the path AWS binds it to
migration: withOVERCAST_ENFORCE_IAM=truean unsigned request under/apiis now refused rather than served, and a signed one is evaluated wherever Overcast can name the operation — grant the principal thes3:actions it needs on a bucket namedapi, andkafka:ListClustersV2/kafka:CreateClusterV2for MSK's v2 cluster API. Note the prefix: the[iam]entry above renames MSK's actions from Overcast'smsk:to AWS'skafka:in this same release, so a policy written againstmsk:will not grant
Release: https://github.com/Neaox/overcast/releases/tag/v0.0.1-alpha.35