Overcast is alpha — behaviour and APIs may change between releases. Pin your version and read the changelog before upgrading.

overcast

release history

Overcast releases

Download native binaries directly from any listed release below, or open a release on GitHub for full change details.

latest release

v0.0.1-alpha.44 prerelease

Published

View on GitHub

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.44

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.44

Downloads

Changelog

11 added · 26 fixed · 7 breaking

Breaking changes

  • Added

    enforce Lambda TOKEN/REQUEST authorizers (REST v1) and Lambda REQUEST authorizers (HTTP v2)

    • requests to a method or route with a Lambda authorizer were let through unchecked; they now reach the integration only when the authorizer allows them

    • AWS_IAM authorizers remain unenforced

    Migration

    make the authorizer function return an Allow policy (or isAuthorized: true) for the requests your tests make, or remove the authorizer from the local deployment

    apigateway
  • Fixed

    Lambda proxy integrations answer AWS’s 502 for a malformed response and base64-encode binary request bodies

    • a response with no statusCode, or isBase64Encoded: true with a body that is not base64, used to answer 200 with a guessed body

    Migration

    return a statusCode from the handler, and base64-encode the body whenever isBase64Encoded is true

    apigateway
  • Fixed

    CreateGraphqlApi no longer auto-creates a default API key for authenticationType=API_KEY

    • AWS’s CreateGraphqlApi response carries no apiKey field; the console’s “default key” is a separate CreateApiKey call it makes, not something the API itself does

    Migration

    call CreateApiKey explicitly after CreateGraphqlApi if you relied on the implicit key

    appsync
  • Fixed

    CreateGraphqlApi/UpdateGraphqlApi validate logConfig and additionalAuthenticationProviders shape

    • fieldLogLevel and each provider’s authenticationType are checked against AWS’s documented enums; these passthrough fields previously accepted any shape

    Migration

    fix a logConfig missing fieldLogLevel, or a provider naming an unrecognized authenticationType, before deploying

    appsync
  • Fixed

    AWS::SES::ConfigurationSet fails the resource with SES’s own 501 instead of reporting a success that created nothing

    • SES configuration sets are not implemented, so the stub’s CREATE_COMPLETE left templates relying on a configuration set that did not exist

    Migration

    keep AWS::SES::ConfigurationSet out of the template you deploy to Overcast, for example behind a condition, until SES implements configuration sets

    cloudformation ses
  • Fixed

    creating a database or table that exists is AlreadyExistsException; a table’s database must exist (#2064)

    • names are folded to lowercase as on AWS, and Glue client errors are HTTP 400 where EntityNotFoundException was 404.

    Migration

    change a definition with UpdateTable or UpdateDatabase instead of re-creating it; re-create mixed-case state.

    glue
  • Fixed

    PublishVersion returns the existing version instead of allocating a new one when nothing changed since it

    • matches AWS: code and configuration are compared against the highest existing version; RevisionId/CodeSha256 mismatches answer PreconditionFailedException/InvalidParameterValueException

    • PublishTo returns 501 before mutation, matching CreateFunction’s gate

    Migration

    a script or test that publishes twice with nothing changed in between gets the same version back, as on AWS; change the code or configuration between publishes to get a new one

    lambda
Added 11
  • Amazon S3 Tables: table buckets, namespaces and Iceberg tables, with all 49 modeled operations answering; AWS::S3Tables::TableBucket, Namespace, Table, TableBucketPolicy and TablePolicy provision through it

    • Each table gets a real --table-s3 warehouse bucket in S3; CreateTable with a schema writes the first Iceberg metadata.json.

    • UpdateTableMetadataLocation is a compare-and-swap on versionToken; maintenance, expiration and replication are stored, never run.

    s3tables
  • partitions (create, get, update, delete, singly and in batches; GetPartitions filters by Expression), UpdateTable with VersionId concurrency and archived table versions, UpdateDatabase and BatchDeleteTable; AWS::Glue::Partition provisions and AWS::Glue::Table keeps every TableInput property (#2064)

    • the expression subset is comparisons, AND/OR/NOT, IN, BETWEEN, LIKE and IS NULL; anything else is InvalidInputException.

    • a stale VersionId is ConcurrentModificationException; GetTableVersion(s), DeleteTableVersion and BatchDeleteTableVersion read and prune versions.

    glue
  • event buses store Description, DeadLetterConfig and KmsKeyIdentifier; PutPermission/RemovePermission manage a bus policy

    • DescribeEventBus reports the policy PutPermission/RemovePermission build as Policy; stored, not enforced.

    eventbridge
  • CreateDBInstance/CreateDBCluster accept ManageMasterUserPassword, RDS’s own generated-and-managed master password.

    • the generated password lands in a new Secrets Manager secret (rds!db-<uuid>/rds!cluster-<uuid>), returned as MasterUserSecret.

    • ModifyDBInstance/ModifyDBCluster turn it on or off; turning it off, or deleting the DB, deletes the secret, as on AWS.

    rds
  • CreateGraphqlApi boundary tests cover queryDepthLimit (0/75/76) and resolverCountLimit (0/10000/10001)

    appsync
  • every release ships compat-report.json, the public compatibility report

    • Each result that does not pass carries a reason code and, where one exists, its tracking issue. go run ./cmd/compat --publish-report builds it; overcast.sh/compat renders it.

    compat
  • pin and unpin services straight from the sidebar and the dashboard, not only from global search

    • sidebar rows show the star on hover; dashboard cards and list rows keep a filled star on pinned services

    web
  • the object inspector previews CSV, TSV, JSON Lines and Parquet in a virtualized data grid that scrolls the whole file, millions of rows included, with a toggle back to the raw text

    3 details
    • a worker indexes text files as they stream and reads any block by HTTP Range; Parquet shows its schema and reads only the rows and columns in view (ZSTD and GZIP included)

    • the grid has a cell cursor, ⌘G Go to row, ⌘C copy as TSV, a cell inspector, resizable and hideable columns, and Find over the loaded rows

    • Iceberg *.metadata.json gets a summary above its JSON; Avro files say they are not previewed instead of showing nothing

    web s3
  • a full-page data viewer at /s3/<bucket>/view?key=…&row=… opens a data file with the whole page to scroll it, deep-linked to a row

    web s3
  • a Lambda Task’s state inspector shows each attempt’s invocation: its logs, REPORT metrics, log stream and error, and Step Functions errors show the function’s own error type, message and stack trace

    • Opens by default on a Lambda Task that failed, was caught or retried; matched by request id, the attempt’s error, or timing, and says which. Common Lambda failures get a hint.

    • “Replay in Lambda” saves the attempt’s event as a test event and opens the function’s Test tab.

    web stepfunctions
  • state machine, execution, IAM and log-stream ARNs link to their console pages, in the region the ARN names

    web
Fixed 26
  • CloudFront Functions fail as on AWS: one that throws or has no compilable handler answers 503, and one that returns something other than a request or response object (say, a forgotten return) answers 502

    • they used to answer 500, or be skipped silently so the request went to the origin as if no function were attached.

    • a viewer-request function returning a uri not beginning with / gets a 502, and the origin is not called.

    cloudfront
  • viewer-response functions run on a cache hit, once per viewer, instead of a hit replaying the headers set for the viewer that filled the cache; they no longer run when the origin answers 400 or above

    cloudfront
  • request paths keep the viewer’s encoding: a % (/100%25) reaches the origin instead of answering 502, a // prefix keeps both slashes, and a function’s event.request.uri is percent-encoded as sent

    • the origin receives the path byte-for-byte, for custom and emulated origins alike; a uri returned with raw UTF-8 or spaces reaches the origin percent-encoded.

    • the // path used to lose a slash and share a cache entry with the single-slash path.

    cloudfront
  • cache behaviours match as on AWS: after decoding escaped unreserved characters (/%7Euser matches /~user/*, %40 does not match @) and after resolving dot segments and collapsing repeated slashes

    • /a/b/.. matches /a*, not /a/b*, and /a//b matches as /a/b. The origin still receives the path exactly as sent.

    • a viewer-request function that rewrites the uri no longer changes the cache TTL: it comes from the behaviour the viewer’s path matched.

    cloudfront
  • the access log’s cs-uri-stem is the viewer’s path, not Overcast’s internal /_overcast/cloudfront/distributions/{id}/... route

    • free-text fields use CloudFront’s log encoding, so an encoded %20 in the path is logged as %2520.

    cloudfront
  • host-style invoke URLs keep %2F inside a path segment, so /@scope%2fpkg matches /{package} as on AWS.

    • it used to be decoded into a separator and answer 403 Missing Authentication Token; path-style invoke was unaffected.

    apigateway
  • load balancers, CloudFront and function URLs pass a %2F in the path on still encoded.

    • ALB targets and CloudFront origins receive the path as the client sent it; a function URL’s rawPath keeps its encoding.

    elbv2 cloudfront lambda
  • an instance no longer shows InService while its launch activity still reads PreInService, and DescribeLaunchConfigurations returns the UserData CreateLaunchConfiguration stored

    • the launch activity is now marked Successful before the instance goes InService, so DescribeAutoScalingInstances and DescribeScalingActivities always agree.

    autoscaling
  • properties the backing service supports are forwarded instead of dropped: Tags on AWS::Events::Rule, AWS::Events::EventBus and AWS::CloudFront::Distribution; CloudTrail, AppConfig, OpenSearch, ACM, Athena, Shield and Transfer properties; AWS::WAFv2::WebACL Description and Tags

    3 details
    • a tags-only update to an AppConfig, OpenSearch, ACM, Athena or Shield resource, including a stack-tag change, now reconciles the tags in place instead of replacing the resource, as on AWS

    • EventBus properties with no service member (Description, DeadLetterConfig, KmsKeyIdentifier, Policy) are reported as unconsumed instead of dropped silently.

    • a CloudFront distribution with Tags dispatches to CreateDistributionWithTags, so ListTagsForResource and the custom_id tag both work again; Fn::GetAtt Arn/Id on a WebACL now resolves.

    cloudformation
  • AWS::AutoScaling::AutoScalingGroup forwards Cooldown, HealthCheckType, HealthCheckGracePeriod and TerminationPolicies, and AWS::AutoScaling::LaunchConfiguration forwards KeyName, IamInstanceProfile and UserData

    • these were dropped on create and update.

    cloudformation autoscaling
  • AWS::RDS::DBInstance/AWS::RDS::DBCluster forward ManageMasterUserPassword, MasterUserSecret.KmsKeyId and Tags.

    • those were previously dropped entirely; Fn::GetAtt MasterUserSecret.SecretArn now resolves and Tags reconcile on update.

    cloudformation rds
  • AWS::OpenSearchService::Domain without a DomainName now generates one instead of failing CreateDomain

    cloudformation opensearch
  • AWS::IAM::ServiceLinkedRole uses AWS’s documented per-service role names, and an AWS::IAM::Role update no longer double-encodes a string-form AssumeRolePolicyDocument

    cloudformation iam
  • accept Binary key attributes over Smithy RPC v2 CBOR

    dynamodb
  • GetTable returns the whole TableInput it was given, and GetDatabase the whole DatabaseInput; deleting a database deletes its tables, partitions and versions, and deleting a table deletes its partitions (#2064)

    • StorageDescriptor, PartitionKeys and Parameters were dropped on write; tables now carry CreateTime, UpdateTime and VersionId.

    glue
  • PutRecord/PutRecords route by the MD5 hash-key range AWS uses, honouring ExplicitHashKey

    • previously routed by a byte-sum modulo shard count, which disagreed with the HashKeyRange values ListShards/DescribeStream report

    kinesis
  • GetFunction and GetFunctionConfiguration honour the Qualifier parameter

    • resolves a version number, an alias (through its FunctionVersion), or $LATEST by default, embedded in the name or given as a query parameter

    • an unknown version or alias answers ResourceNotFoundException instead of silently reporting $LATEST

    lambda
  • a timed-out invocation’s handler is stopped immediately, not left running past REPORT, and its REPORT Duration is the timeout

    • the container is killed at the deadline rather than after Release, so late output and side effects can no longer happen; the next invoke gets a fresh environment

    • Duration used to be the timeout plus up to 2 s of output wait.

    lambda
  • a synchronous Invoke now answers with the invocation’s request id in x-amzn-RequestId

    • It was absent, so SDK callers read an empty id and Step Functions’ lambda:invoke returned SdkResponseMetadata.RequestId "".

    lambda
  • CreateDBInstance rejects AdditionalStorageVolumes instead of silently dropping it, and DBInstance models the resting StorageOperationStatus fields

    • Oracle/SQL Server only on AWS and neither engine is emulated, so it is now InvalidParameterCombination.

    • The two storage-operation fields stay correctly omitted since Overcast runs no storage operation.

    rds
  • DeleteObject/DeleteObjects honour If-Match; a mismatch is 412, a missing key 404, instead of succeeding (#2037)

    s3
  • PutResourcePolicy answers PublicPolicyException for a public statement, not MalformedPolicyDocumentException

    secretsmanager
  • Athena, Glue, Firehose, OpenSearch, ACM, AWS Backup, CloudTrail, Organizations, Route 53 and Transfer Family are no longer listed as not emulated in global search and on the dashboard

    • Bedrock now shows as a stub, not unsupported, and a stub’s page says which operations it answers instead of claiming all return 501.

    web
  • the system map shows SQS queue nodes’ real message counts again (not 0), draws a dead-letter edge when maxReceiveCount is a string as the AWS CLI writes it, and shows CloudFront distributions with edges to their S3 origins

    web map
  • accepting the connection dialog’s prefilled endpoint opens the console without a reload, and a ?region= in the URL is no longer overwritten at startup by the server’s default region

    web
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.44

Older versions

v0.0.1-alpha.43 prerelease 13 assets
13 added · 4 changed · 32 fixed · 5 breaking stepfunctions cloudformation web secretsmanager eventbridge +25 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.43

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.43

Downloads

Changelog

13 added · 4 changed · 32 fixed · 5 breaking

Breaking changes

  • Changed

    Ref on an AWS::IAM::InstanceProfile is the profile name, as AWS documents it; the ARN is Fn::GetAtt "Arn"

    Migration

    a template passing the Ref somewhere an ARN was wanted needs Fn::GetAtt instead — the reverse of what it needed before

    cloudformation iam
  • Fixed

    an AWS::IAM::Policy naming no Groups, Roles or Users fails the stack instead of writing its document to nobody

    • AWS requires at least one of the three, and the resource does nothing at all without one

    Migration

    name the principal the policy is for; a stack that reported CREATE_COMPLETE without one was granting nothing

    cloudformation iam
  • Fixed

    an instance profile holds one role, AWS’s quota, and one still holding a role refuses to delete

    • AddRoleToInstanceProfile answers LimitExceeded for a second, different role; DeleteInstanceProfile answers DeleteConflict

    Migration

    RemoveRoleFromInstanceProfile before replacing a profile’s role or deleting the profile, as against real AWS

    iam
  • Fixed

    a Parallel or Map failure keeps the branch’s own error name instead of States.BranchFailed/States.TaskFailed.

    Migration

    a Catch or Retry naming States.BranchFailed should name the branch’s error, or use States.ALL

    stepfunctions
  • Fixed

    CreateStateMachine rejects duplicate state names across branches and States.ALL outside the last Retry/Catch.

    • it also rejects fields of the other query language (a JSONPath Output, a JSONata InputPath), matching AWS

    • and, for EXPRESS, .sync, .waitForTaskToken, activities and distributed Map, which AWS also refuses there

    Migration

    rename nested states so every name is unique, and move States.ALL into its own final entry

    stepfunctions
Added 13
  • a complete Amazon States Language interpreter: JSONata on a JSONata 2.x engine, workflow variables (Assign) in both query languages, all 18 intrinsic functions, and full JSONPath paths (wildcards, descent, slices, unions, filters).

    • AWS’s added functions ($partition, $range, $hash, $uuid, $parse) work, $now/$millis read Overcast’s clock, and $eval is withdrawn as on AWS

    • Parallel branches and Map iterations run concurrently, honouring MaxConcurrency; a failure aborts the siblings, and history links each branch and iteration through previousEventId

    stepfunctions
  • callbacks, activities and distributed Map: .waitForTaskToken, SendTaskSuccess/SendTaskFailure/SendTaskHeartbeat with HeartbeatSeconds enforced, child executions, S3 ItemReader, ItemBatcher, failure tolerance, ResultWriter, and the map-run API.

    • aws-sdk: Tasks call every modeled action of every service Overcast implements, over all six AWS wire protocols; results keep user-data map keys as written and errors are named as the SDK names them (S3.NoSuchKeyException)

    • dynamodb:deleteItem and events:putEvents optimized integrations

    stepfunctions
  • executions parked on a task token, activity or Wait survive a restart on a persistent store, deadlines included.

    • any other execution a restart interrupts ends FAILED with States.Runtime and can be redriven, instead of staying RUNNING forever

    stepfunctions
  • RedriveExecution, TestState, ValidateStateMachineDefinition, state machine versions and aliases, and paginated list operations.

    • a redrive re-runs only the failed Parallel branches, Map iterations and map-run children, from the state each stopped in

    stepfunctions
  • AWS::StepFunctions::Activity, StateMachineVersion and StateMachineAlias, and DefinitionS3Location.

    stepfunctions cloudformation
  • Step Functions pages draw each state machine as a flow diagram, with Parallel branches and Map item processors as nested lanes.

    3 details
    • executions play live on it: running states pulse, taken paths light up, and each Map iteration can be viewed on its own; click a state for its runs, input, output, retries and errors; export the diagram as SVG or PNG

    • execution history reads as a timeline of state runs by branch and iteration, or a filterable, searchable event list

    • create or edit a definition beside a live diagram preview, from ready-to-run templates, and stop, redrive or re-run executions

    web stepfunctions
  • DeleteSecret now schedules the delete behind a recovery window, and RestoreSecret cancels it

    • the window is 7 to 30 days, 30 by default; ForceDeleteWithoutRecovery still deletes at once, and supplying both parameters is InvalidParameterException as on AWS

    • a secret inside its window keeps its record with DeletedDate, is hidden from ListSecrets unless IncludePlannedDeletion is set, and refuses value operations with InvalidRequestException

    secretsmanager
  • event patterns match on prefix, suffix, exists, equals-ignore-case, numeric and anything-but, not only exact values (#148)

    • anything-but covers a single value, a list, and a nested prefix, suffix or equals-ignore-case clause

    • cidr, wildcard and $or stay unimplemented, and PutRule and TestEventPattern answer InvalidEventPatternException naming the match type rather than storing a rule that never matches

    eventbridge
  • CreateFunction with Publish: true publishes version 1 in the same call and answers with that version, as on AWS

    • every FunctionConfiguration now carries Version ($LATEST for the unpublished function), which the SDKs model and AWS always returns

    • PublishTo: LATEST_PUBLISHED (the $LATEST.PUBLISHED version of Lambda Managed Instances) still returns 501

    lambda
  • Cognito issues and completes SMS_MFA, SELECT_MFA_TYPE and MFA_SETUP challenges, alongside the existing software-token MFA.

    • SetUserMFAPreference and AdminSetUserMFAPreference accept SMSMfaSettings, and GetUser/AdminGetUser report UserMFASettingList and PreferredMfaSetting

    • AssociateSoftwareToken and VerifySoftwareToken accept an MFA_SETUP challenge Session, so a user can enrol a software token mid-sign-in

    cognito
  • StopQueryExecution is implemented: it answers an empty document for a known query id and InvalidRequestException for an unknown one

    athena
  • 33 operations newly modeled by AWS are recognised.

    • a signed request to one reaches a protocol-correct 501 in that service’s own error envelope, instead of the S3 fallback’s bucket-or-object answer

    router
  • hovering a node on the system map dims everything except that node, its connections and its neighbours

    • hovering a legend entry does the same for one connection type

    web
Changed 4
  • the system map has its own layout engine, built and tuned against a written objective instead of dagre

    4 details
    • each connected flow is laid out on its own, the flows are packed into a wide canvas, and unconnected resources are tiled by service beneath them; every connection is routed around the nodes it is not attached to and drawn as one smooth curve

    • a resource card’s box matches its rendered height, a function’s log group and stream filter are drawn inside the function’s stack, and the map can zoom out far enough to fit a large topology

    • the legend lists only the connection types present; below 0.6× zoom the lists inside nodes fade and edge labels hide, so the map reads as boxes and wires

    • the objective (no overlaps, no wires through cards, fewest crossings, shortest wires, straight rows), its weights and its fixtures are in docs/plans/map-layout-objective.md; a test fails if any fixture’s score regresses, and the engine handles 1,000 resources in under a second

    web
  • emulator-only operations are counted separately from AWS API operations.

    • CloudFront’s ProxyRequest, API Gateway’s ExecuteRestAPI/ExecuteV2API, AppSync’s ExecuteGraphQL and EKS’s UpdateKubeconfig carry an EmulatorOnly flag and list under an “Emulator extensions” heading on their operations pages, without an invented AWS docs link

    • CloudFront reads 88 AWS operations rather than 89, API Gateway 102 of 104, AppSync 81 of 81 and EKS 49 of 49 in STATUS.md, the docs service index and service-support.json; capgen --check-model rejects the flag on an operation AWS does model

    capabilities
  • Docker-dependent ECR operations are declared Partial and the scanning operations Inert or Unsupported, matching what they do

    ecr
  • service documentation reconciled with behaviour: ACM and KMS accept JSON 1.0 alongside JSON 1.1 and RPC v2 CBOR as the framework-wide rule; Backup answers only Smithy restJson1 at its modeled paths; AppRegistry’s List operations accept but ignore pagination (#60)

    • the Cognito pages describe UserPoolTier gating (SignInPolicy, ALLOW_USER_AUTH, WebAuthn), the SRP and WebAuthn verification boundary, and AdminGetDevice/AdminListDevices IAM and pagination-token behaviour

    acm kms backup appregistry cognito
Fixed 32
  • malformed state machine input is refused as AWS refuses it, rather than reported as a state machine that does not exist (#2003).

    • CreateStateMachine answers InvalidName for a name outside AWS’s charset — whitespace, brackets, wildcards, reserved specials, control characters, over 80 characters

    • it answers InvalidArn for a roleArn that is not an IAM role ARN, as do the six operations taking a stateMachineArn when given something that is not a state machine ARN; a well-formed ARN naming a state machine that does not exist still answers StateMachineDoesNotExist

    stepfunctions
  • an execution resumed after a restart is no longer failed as unresumable while it is finishing, and RedriveExecution is accepted the instant an execution reads terminal instead of sometimes refusing it as already being redriven.

    • a resumed execution kept the previous process’s runner ID for its whole life, so a read arriving as it finished could end it FAILED with States.Runtime

    • a distributed Map also starts no further child once a failure has passed its tolerance, rather than racing the child that ended the run

    stepfunctions
  • Choice type mismatches evaluate false, a Fail state without Error is catchable, ItemSelector reads the Map input, JitterStrategy: FULL is applied, and States.Timeout also matches States.HeartbeatTimeout.

    • GetExecutionHistory with includeExecutionData: false no longer blanks the payloads out of the stored history

    stepfunctions
  • nine request-validation rules AWS documents are now enforced with AWS’s wording, where each request used to succeed with 200 (#1707).

    3 details
    • items over 400 KB by AWS’s size accounting, a Query that does not constrain the partition key, and empty-string key attributes are refused with a ValidationException

    • BatchWriteItem and TransactWriteItems refuse two operations on one item, BatchGetItem refuses more than 100 keys, and Scan refuses a Segment at or past TotalSegments

    • CreateTable refuses AttributeDefinitions that do not match the table and index key schemas exactly, choosing between AWS’s four wordings as AWS does

    dynamodb
  • Query key conditions are checked against the key schema in play, and may be written in either order and in parentheses (#135).

    • a second condition on a non-key attribute used to be answered as if it named the sort key; a sort-key condition written before the partition key, and any parentheses, used to be rejected outright

    • two conditions on one key are now AWS’s “KeyConditionExpressions must only contain one condition per key”

    dynamodb
  • a TTL transition accepted while Overcast is starting up settles as it should, instead of being cancelled by the start-up re-arm

    • the re-arm read the table list once and armed what that snapshot said, so an UpdateTimeToLive accepted in between lost the settle for its own deadline

    • the table kept a transition marker that outlived its window, and a completed disable never dropped its TTL configuration

    dynamodb
  • SetUserPoolMfaConfig stores and returns SmsMfaConfiguration, SoftwareTokenMfaConfiguration and EmailMfaConfiguration.

    • the three factor configurations were parsed and dropped, so GetUserPoolMfaConfig answered with MfaConfiguration alone

    • the request now replaces the MFA configuration as a unit, and is rejected when it enables MFA with no factor or turns MFA off while configuring one

    cognito
  • UpdateUserAttributes now sends a code for an attribute in the pool’s AutoVerifiedAttributes, and ForgetDevice/AdminForgetDevice answer ResourceNotFoundException for a device key the user never registered instead of a silent HTTP 200.

    • CreateUserPool, UpdateUserPool and DescribeUserPool store and return AutoVerifiedAttributes, which the service previously dropped, and SignUp returns CodeDeliveryDetails

    • CodeDeliveryDetails destinations are now masked the way AWS masks them

    cognito
  • PutRule now validates the rule trigger, the event bus and the event pattern, and DeleteRule holds a rule to AWS’s ordering (#148)

    • AWS requires at least an EventPattern or a ScheduleExpression; a rule with neither is a ValidationException instead of a rule that can never fire, a rule naming an event bus that was never created is a ResourceNotFoundException, and a pattern whose shape or match type EventBridge does not define is an InvalidEventPatternException

    • a rule with targets attached refuses DeleteRule with ValidationException; Force is the managed-rule escape AWS documents, not a way around the check; deleting an AWS::Events::Rule through CloudFormation needs no change, as the stack detaches the targets it attached

    eventbridge
  • the request validation AWS documents is enforced, where CreateDeliveryStream, PutRecord and PutRecordBatch used to answer 200 (#149).

    3 details
    • a second CreateDeliveryStream for a name in use reports ResourceInUseException instead of silently overwriting the stream’s type, ARN and tags

    • PutRecordBatch enforces 1-500 records, the 1,000 KiB per-record cap and the documented 4 MiB per call, and no longer acknowledges records it never decoded

    • every exception carries HTTP 400, the status the Firehose model gives them, where a missing delivery stream answered 404

    firehose
  • Ref for a delivery stream is its name, and deleting a stack now deletes the stream (#149).

    • the physical ID was the ARN, so teardown asked Firehose to delete a stream named by an ARN, which matched nothing and left the stream behind

    cloudformation firehose
  • Fn::GetAtt "Arn" keeps a role’s or instance profile’s Path across a stack update

    cloudformation iam
  • ListStreams, ListShards, DescribeStream and ListTagsForStream paginate, instead of returning everything in one page

    3 details
    • each honours its documented cursor, page size and truncation flag: Limit/MaxResults, ExclusiveStart{StreamName,ShardId,TagKey}, NextToken, HasMore{Streams,Shards,Tags}

    • a ListShards NextToken identifies its own stream and expires after 300 seconds with ExpiredNextTokenException, and combining one with StreamName or ExclusiveStartShardId is refused, as on AWS

    • ListStreams also returns the modeled StreamSummaries alongside StreamNames, and ListShards accepts StreamARN in place of StreamName

    kinesis
  • errors match the AWS model: every exception is HTTP 400, and a missing parameter is InvalidArgumentException

    • ResourceNotFoundException answered 404 where all Kinesis exceptions are 400, and a missing required member answered MissingParameter, a code no Kinesis operation models and no SDK error type matches

    kinesis
  • CreateCacheCluster and CreateReplicationGroup validate their inputs and return the CacheNodes and NodeGroups shapes AWS models

    3 details
    • identifiers follow AWS’s grammar (1-50 for a cluster, 1-40 for a group, letter-led, no trailing or doubled hyphen) and are stored lowercase

    • NumCacheNodes is bounded per engine, AZMode and PreferredAvailabilityZones are Memcached-only, and a ReplicationGroupId must name a group that exists

    • ConfigurationEndpoint is now Memcached-only on a cluster and absent on a cluster-mode-disabled group, matching AWS; the address moved to the node and node group

    elasticache
  • the cluster, replication-group and parameter-group not-found faults answer HTTP 404 rather than 400.

    • CacheClusterNotFound, ReplicationGroupNotFoundFault and CacheParameterGroupNotFound each bind httpResponseCode: 404 in the model; the wire codes and messages are unchanged, and CacheSubnetGroupNotFoundFault keeps the 400 its own trait declares

    elasticache
  • domain names are validated, DescribeCertificate returns DomainValidationOptions, and ListCertificates honours CertificateStatuses (#1994).

    3 details
    • a DomainName or SAN outside AWS’s domain pattern, or an over-long one, is refused with a ValidationException naming the member — any non-empty string used to be issued a certificate

    • DescribeCertificate reports one entry per domain — SUCCESS, the requested method, and for DNS a CNAME that stays the same across calls, so publish-then-wait flows have something to act on

    • the status filter used to be ignored, so a PENDING_VALIDATION poll saw its own issued certificate; an unknown status is now refused rather than matching everything

    acm
  • responses now carry the 2020-05-31 XML namespace and errors use the restXml ErrorResponse envelope.

    3 details
    • every response root declares xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/", the namespace CloudFront’s API has always specified

    • errors move from S3’s bare <Error> body to <ErrorResponse><Error><Type>…</Type><Code>…</Code><Message>…</Message></Error><RequestId>…</RequestId></ErrorResponse>; codes and statuses are unchanged

    • Distribution, DistributionConfig and DistributionSummary emit their members in the order AWS documents

    cloudfront
  • DomainName and EngineVersion are checked against the constraints AWS declares, where a malformed value used to create a domain, and DomainStatus carries ClusterConfig (#165)

    3 details
    • a name outside 3-28 characters of [a-z][a-z0-9-] produced a malformed ARN and endpoint hostname, and an engine version outside OpenSearch_X.Y/Elasticsearch_X.Y produced a wrong EngineType

    • DescribeDomain and DeleteDomain answer ValidationException for such a name rather than ResourceNotFoundException, because AWS validates input before it looks anything up

    • ClusterConfig echoes the configuration the create asked for, or an empty object when it asked for none — Overcast runs no cluster to describe

    opensearch
  • ChangeResourceRecordSets rejects a record value that does not fit its record type

    • an A whose value is not a dotted quad, an AAAA holding an IPv4 address or a CNAME with two values answers InvalidChangeBatch, as on AWS, instead of being stored

    • NS, PTR, MX, SRV, CAA, TXT and SPF are checked too; NAPTR, DS, TLSA, SSHFP, SVCB and HTTPS values are still stored as given

    route53
  • CompleteMultipartUpload checks part ETags, ordering and the 5 MiB minimum, answering InvalidPart, InvalidPartOrder or EntityTooSmall (#1706)

    • an empty parts list is MalformedXML, and a refused completion leaves the upload intact to retry or abort

    s3
  • protections refuse a duplicate resource, carry their own ARN, ListProtections filters and paginates, and ResourceNotFoundException answers HTTP 400 as the API Reference documents rather than 404.

    3 details
    • a second CreateProtection for a ResourceArn that already has a protection reports ResourceAlreadyExistsException instead of creating a second record

    • Protection carries ProtectionArn (arn:aws:shield::<account>:protection/<id>) on DescribeProtection and ListProtections, as real Shield always does

    • ListProtections honours InclusionFilters, MaxResults and NextToken, rejecting an unrecognised token with InvalidPaginationTokenException

    shield
  • Scope is validated as AWS’s CLOUDFRONT | REGIONAL enum, where any string used to be accepted and create an unreachable namespace (#197).

    • CreateWebACL, GetWebACL, ListWebACLs and DeleteWebACL answer WAFInvalidParameterException (400) for a value outside the enum, on both the JSON and RPC v2 CBOR paths

    waf
  • not-found errors answer HTTP 400, the status AWS JSON 1.1 gives InvalidRequestException, instead of 404

    athena
  • ListHostedConfigurationVersions’ version_label filter now matches a trailing * as a prefix, per AWS docs

    appconfig
  • CreateFargateProfile rejects a request with no podExecutionRoleArn as MissingParameter instead of silently storing an empty one (#1979)

    eks
  • ListRolePolicies, ListUserPolicies and ListGroupPolicies return inline-policy names in a stable order

    iam
  • CreateSchedule rejects duplicate names within a group and an invalid FlexibleTimeWindow.MaximumWindowInMinutes

    scheduler
  • the topology graph shows EC2 instances again, grouped inside their VPC, and draws a pipe edge for any EventBridge pipe whose Source and Target name SQS, SNS, DynamoDB stream or Lambda resources

    • GET /_overcast/topology decoded instance records with the wrong field names and never emitted an ec2 node, and assumed every pipe was DynamoDB → SQS, so a pipe such as SQS → Lambda produced no edge

    router web
  • the system map no longer sends an SQS peek request for every non-queue node, and shows its spinner until the first layout is ready

    web
  • prose-only pull requests no longer fail every test job: the aws-sdk shape-table pack step is skipped, like the checkout it needs, when no code changed

    ci
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.43

View on GitHub
v0.0.1-alpha.42 prerelease 13 assets
6 added · 2 changed · 6 fixed · 1 breaking lambda ecs web cloudwatch-logs cloudwatch +6 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.42

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.42

Downloads

Changelog

6 added · 2 changed · 6 fixed · 1 breaking

Breaking changes

  • Fixed

    CreateCacheCluster and CreateReplicationGroup refuse an unknown CacheSubnetGroupName with CacheSubnetGroupNotFoundFault.

    • the name used to be accepted and ignored, which placed the cache on the default plane with nothing said, and the first symptom was a consumer in the intended VPC that could not resolve the endpoint.

    • an empty name is still fine — that is the default VPC, on AWS and here.

    Migration

    create the cache subnet group before the cluster or replication group that names it, or correct the name; a create that names a group Overcast has no record of now fails instead of silently landing outside its VPC.

    elasticache
Added 6
  • step debugging of user code inside emulated Lambda functions and ECS tasks: OVERCAST_DEBUGGER=true plus an overcast:debug=true tag.

    5 details
    • Node.js and Java get the inspector or JDWP flag injected; Python reads OVERCAST_DEBUG_PORT for debugpy; images and custom runtimes name a protocol with overcast:debug-protocol.

    • the function timeout clock stops while a debugger is attached (OVERCAST_DEBUGGER_TIMEOUT=attached|paused|strict), and the port survives hot reload replacing the container.

    • overcast:debug-wait=true holds an invocation until a debugger attaches, so the invocation that starts the container pauses at its breakpoints too; bounded by OVERCAST_DEBUGGER_WAIT_TIMEOUT (default 120s), after which it runs with a WARN, and the function’s own clock starts when the event is dispatched rather than while it waits.

    • a tagged function is registered with the debugger when created or tagged, and after a restart, so a session is offered before it first runs.

    • the listener follows OVERCAST_LISTEN, so -p 9229-9329:9229-9329 reaches it when Overcast runs in Docker.

    lambda ecs
  • step debugging inside the console: breakpoints, stepping and a paused view in the function page’s Code tab, for Node.js functions.

    4 details
    • Locals, Watch, Call stack and Breakpoints beside the code, logs and a debug console with a REPL below; panels and drawer resize, and a session left open is restored on return.

    • source maps beside or inline in the deployment show the original TypeScript, translated both ways, with a per-function switch to debug the compiled output instead.

    • a Debug tab on the function and task pages carries a ready-made attach configuration for VS Code, JetBrains, Chrome DevTools and the command line, reads attached/paused while a session holds the port, and offers a Wait for a debugger switch that holds the first invocation.

    • behind it, a WebSocket bridge onto the function’s debug port and a source endpoint that reads every deployment file by path, so a hot-reloaded function’s mounted directory is listed and read live — .map files and nested paths included.

    web lambda
  • CloudWatch Logs metric filters, and AWS::Logs::MetricFilter to provision them.

    3 details
    • PutMetricFilter, DescribeMetricFilters, DeleteMetricFilter and TestMetricFilter, over AWS JSON and RPC v2 CBOR

    • a log event that matches a filter publishes a CloudWatch datapoint, so GetMetricStatistics reports it and an alarm on the metric fires from log lines alone

    • nothing is published while OVERCAST_SERVICE_METRICS=disabled; the filters are still stored, described and tested

    cloudwatch-logs cloudwatch cloudformation
  • Fn::GetStackOutput resolves a stack output in any region without an export — the weak cross-stack reference CDK now emits.

    • the resource that made a dangling reference fails, naming the stack or output that was not found; RoleArn is accepted but not assumed, since Overcast emulates one account.

    cloudformation
  • 29 operations newly modeled by AWS are recognised.

    • a signed request to one reaches a protocol-correct 501 in that service’s own error envelope, instead of the S3 fallback’s bucket-or-object answer

    router
  • the data-plane-name-refused health advisory names each endpoint the resolver refused, and both containers involved.

    • the refusal is the placement the template asked for — the two resources are in different VPCs — but from inside the application it is only Temporary failure in name resolution. The WARN line carries the fix too.

    router
Changed 2
  • the system map’s log peek renders rows through the stream viewer’s pipeline instead of as raw text.

    • level tint and badge, Format/Syntax/Wrap/Collapse, ANSI colour, a filter over the loaded events, and an “Open in Logs” link to the full view

    cloudwatch web
  • the CloudWatch Metrics page groups metrics by namespace, adds a filter, and draws the Monitor tab’s chart.

    • long metric names and dimension values now truncate or wrap inside their column instead of spilling past it; the range presets extend to 7 and 30 days

    cloudwatch web
Fixed 6
  • the stream viewer’s Tail toggle now catches up on events logged between the last fetch and the session opening.

    • one FilterLogEvents read per opened session, from the newest event on screen up to now, reconciled with the page and the live buffer by count

    • syntax highlighting also applies to collapsed log rows, on their single-line form

    cloudwatch web
  • a mount target’s NFS export joins its subnet’s VPC network, on create and on adoption after a restart.

    • it carried the file system’s endpoint names on the default plane only, so a task or function in the mount target’s VPC could not resolve them.

    efs
  • a live cluster joins its VPC network before it starts, rejoins it after a restart, and honours endpointPublicAccess.

    • DescribeCluster and UpdateKubeconfig also hand a sibling container the cluster’s endpoint name on the API port, rather than a host port nothing inside the network listens on.

    eks
  • cache endpoints are minted for whoever asks, on every read, instead of overwritten with the address Overcast itself dials.

    • a Lambda function or ECS task gets the endpoint hostname and the engine port, the host the published port. The record used to be rewritten after the container started with the address Overcast itself dials, so a function reading the endpoint at runtime was given 127.0.0.1 and connected to itself.

    • a replication-group container adopted after a restart also rejoins its subnet group’s VPC network; it used to be re-attached with no VPC at all and landed on the default plane, where the tasks in its VPC could not resolve it, while a freshly created group was placed correctly.

    elasticache
  • PubliclyAccessible decides placement as well as the response.

    • a public instance in a subnet group joins the shared data plane as well as its VPC network, and ModifyDBInstance re-attaches a running container when the flag changes. It was stored and echoed but never reached the wiring.

    rds
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.42

View on GitHub
v0.0.1-alpha.41 prerelease 13 assets
18 added · 6 changed · 36 fixed · 22 breaking ec2 autoscaling cloudformation lambda backup +27 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.41

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.41

Downloads

Changelog

18 added · 6 changed · 36 fixed · 22 breaking

Breaking changes

  • Changed

    UpdateTimeToLive is asynchronous, as on AWS: TTL changes pass through ENABLING/DISABLING before they settle.

    3 details
    • DescribeTimeToLive reports all four TimeToLiveStatus values, and the sweeper expires items only once the status is ENABLED.

    • a second UpdateTimeToLive inside the window, re-enabling an ENABLED table, or disabling a DISABLED one is now a ValidationException, matching AWS.

    • both TimeToLiveSpecification members are required (AttributeName 1..255), and AWS::DynamoDB::Table refuses an attribute rename while TTL is enabled.

    Migration

    change a TTL attribute name by disabling TTL, waiting for DISABLED, then enabling it again — the same two-step AWS requires.

    dynamodb
  • Changed

    OVERCAST_ACCOUNT_ID now rejects a value that is not exactly 12 ASCII digits at startup.

    Migration

    set OVERCAST_ACCOUNT_ID to a 12-digit value or unset it.

    config
  • Fixed

    Describe* resolves an explicit resource ID; a miss is Invalid<Resource>ID.NotFound or .Malformed, not an empty 200.

    3 details
    • twelve operations: Vpcs, Subnets, SecurityGroups, RouteTables, InternetGateways, NetworkInterfaces, Instances, Addresses, NatGateways, VpnGateways, VpcEndpoints and VpcPeeringConnections.

    • a filter matching nothing is unchanged, still 200 with an empty list; AWS’s per-resource casing is exact, and where AWS documents no Malformed code a wrongly shaped ID is .NotFound.

    • DeleteVpc, DeleteSecurityGroup, StopInstances, ReleaseAddress and every other single-ID lookup answer AWS’s per-resource code too, not InvalidId.NotFound.

    Migration

    code that read an empty list for a named ID as “it is gone” must catch the error, and code matching InvalidId.NotFound must match the per-resource code — as it already must against AWS.

    ec2
  • Fixed

    Describe* raises the documented not-found error for an ARN or name it cannot resolve, instead of an empty 200.

    3 details
    • DescribeLoadBalancers, DescribeTargetGroups and DescribeListeners, by ARN and by name; a value that is not an ARN for the resource raises ValidationError.

    • a Describe* naming no identifier still lists the region, and DeleteListener now names ListenerNotFound rather than LoadBalancerNotFound.

    • CreateLoadBalancer requires at least one subnet, CreateTargetGroup validates Protocol and Port, and new ARNs end in AWS’s sixteen hex characters.

    Migration

    code that named a load balancer or target group and read the empty list as “it is gone” must catch the error, as it already has to against AWS.

    elbv2
  • Fixed

    a group must name AvailabilityZones or VPCZoneIdentifier, and if both, they must agree — otherwise AWS’s ValidationError.

    3 details
    • a group with neither used to be stored and launched nowhere in particular, falling back to <region>a; UpdateAutoScalingGroup applies the same rule to the group the update would leave.

    • a subnet outside the listed zones used to be stored unchecked and round-robinned independently, so a launch could land in a zone its own subnet contradicted.

    • given only VPCZoneIdentifier, the group takes its zones from the subnets, as AWS does; each launch goes to a subnet and lets EC2 place it in that subnet’s zone.

    Migration

    give every group --availability-zones or --vpc-zone-identifier; when both, list exactly the zones the subnets are in, or drop AvailabilityZones and let the subnets set them.

    autoscaling
  • Fixed

    a DynamoDB reserved word used as a bare attribute name in an expression is rejected, as AWS rejects it (#1638).

    • covers UpdateExpression, ConditionExpression, FilterExpression, KeyConditionExpression and ProjectionExpression, with AWS’s message naming the parameter and the keyword.

    • size(...) and the other function names are not attribute names and keep parsing; the 573-word list is generated from the AWS Developer Guide.

    Migration

    reach a reserved word through an ExpressionAttributeNames alias (#s mapped to size), which is what the same expression already needs on AWS.

    dynamodb
  • Fixed

    key attributes are validated against KeySchema/AttributeDefinitions on every item and key operation (#1637).

    • a key whose type disagreed with the declared one used to be stored under an encoding a correctly typed GetItem never looked under.

    • covers PutItem, GetItem, UpdateItem, DeleteItem, the batch and transact operations, and Query key conditions; non-key attributes stay schemaless.

    Migration

    send key attributes with the type AttributeDefinitions declares, or declare the type your code sends — real AWS rejects the mismatch either way.

    dynamodb
  • Fixed

    Decrypt and ReEncrypt refuse a KeyId/SourceKeyId that did not produce the ciphertext with IncorrectKeyException, as AWS does.

    • both decrypted anyway before, so multi-key rotation and key-hierarchy mistakes all looked like successes.

    Migration

    name the key that produced the ciphertext, or omit KeyId/SourceKeyId and let the symmetric ciphertext blob resolve it.

    kms
  • Fixed

    GenerateDataKey*, Encrypt, ScheduleKeyDeletion and CreateAlias enforce AWS’s parameter rules instead of accepting what AWS refuses.

    3 details
    • GenerateDataKey and GenerateDataKeyWithoutPlaintext require exactly one of KeySpec (AES_256/AES_128) or NumberOfBytes (1-1024); both, neither or out of range is ValidationException.

    • Encrypt enforces the 4096-byte Plaintext limit and ScheduleKeyDeletion the 7-30 day PendingWindowInDays range, both as ValidationException.

    • CreateAlias requires the alias/ prefix (alias/aws/ reserved) — InvalidAliasNameException — and refuses a name in use with AlreadyExistsException; UpdateAlias still re-points.

    Migration

    pass exactly one length parameter (KeySpec=AES_256 for the old 32-byte default), envelope-encrypt over 4096 bytes, clamp windows into 7-30, re-point an alias with UpdateAlias.

    kms
  • Fixed

    a malformed policy document is refused with MalformedPolicyDocument, as AWS does, instead of being stored unparsed.

    • covers CreatePolicy, CreatePolicyVersion, CreateRole, UpdateAssumeRolePolicy and the three Put*Policy writers, through the parser the simulator already evaluates with.

    • structural only: valid JSON, a Statement that is an object or a non-empty array, each statement an exact Allow/Deny Effect and an Action or NotAction. ARN syntax stays unchecked.

    Migration

    give any placeholder document a real statement — {} and an empty Statement list were never accepted by AWS either.

    iam
  • Fixed

    runtime_iam_create_role defaults to a real sts:AssumeRole trust policy, and both IAM create tools refuse a malformed document.

    • the placeholder default carried an empty Statement list, which grants nothing and which the IAM API itself will not store, so a role created through MCP could not be assumed.

    • the check is iampolicy.ValidateDocument, the same one CreateRole and CreatePolicy apply, so the two write paths agree on what a well-formed document is.

    Migration

    pass a document with at least one statement to runtime_iam_create_role and runtime_iam_create_policy, or leave the trust policy out and take the default.

    mcp
  • Fixed

    ListObjectsV2 and ListObjects validate max-keys instead of returning a 500 or ignoring it.

    3 details
    • max-keys=0 returns an empty untruncated page rather than crashing the request; a negative value crashed it too.

    • a non-numeric or out-of-int32 value is rejected with InvalidArgument where it used to be replaced by the 1000 default.

    • a value above 1000 is clamped, and MaxKeys reports the clamped page size; ListObjectVersions shares the validation.

    Migration

    clients sending a malformed or negative max-keys now get a 400 and must send a value between 0 and 2147483647.

    s3
  • Fixed

    PutBucketTagging and PutObjectTagging enforce the documented tag-set limits.

    • a tag set repeating a key, or carrying a key outside 1-128 characters or a value over 256, is rejected with InvalidTag instead of silently collapsing to the last value.

    • more than 50 bucket tags or 10 object tags is rejected with BadRequest.

    Migration

    a client sending duplicate tag keys or an over-long key/value now gets a 400 and must deduplicate or shorten the tag set.

    s3
  • Fixed

    CreateBucket rejects an invalid bucket name with S3’s documented InvalidBucketName code instead of InvalidArgument.

    • the reserved -an suffix follows it; an unrecognised x-amz-bucket-namespace value stays InvalidArgument, which is what S3 documents for a bad request argument.

    Migration

    a test asserting InvalidArgument from CreateBucket on a malformed name should expect InvalidBucketName — the code real S3 returns.

    s3
  • Fixed

    the three batch operations enforce AWS’s entry limits, rejecting a whole batch rather than half-applying it.

    • an empty batch, more than 10 entries, or duplicate entry Ids are refused with EmptyBatchRequest, TooManyEntriesInBatchRequest and BatchEntryIdsNotDistinct.

    Migration

    a client sending oversized or duplicate-id batches was silently succeeding locally and failing on AWS; chunk to 10 distinct-id entries per call.

    sqs
  • Fixed

    GetQueueAttributes rejects an unmodelled attribute name with InvalidAttributeName.

    • it used to answer with whatever else matched, so a typo read as “that attribute is empty”.

    Migration

    a typo in an AttributeNames entry now fails the call; correct the name.

    sqs
  • Fixed

    PutLogEvents enforces AWS’s ingestion window and batch ordering.

    • an event more than 2 hours ahead, older than 14 days, or older than the log group’s retention is discarded behind a 200 and reported in rejectedLogEventsInfo, as on AWS.

    • a batch whose events are not in chronological order is refused whole with InvalidParameterException.

    Migration

    send current timestamps and sort each batch by timestamp — events outside the window are no longer stored, and the field is the only signal they were dropped.

    cloudwatch-logs
  • Fixed

    {{resolve:ssm-secure:...}} resolves only in the properties AWS allows secure strings in (#605).

    • a reference anywhere else fails the resource and rolls the stack back, naming the reference and the property path; the parameter is never decrypted.

    • secretsmanager and plain ssm references stay usable in any property.

    Migration

    move an ssm-secure reference outside AWS’s enumerated properties to secretsmanager, or to a stack parameter.

    cloudformation
  • Fixed

    CreateAddon and AWS::EKS::Addon refuse an addonName that DescribeAddonVersions does not publish.

    • Ref on AWS::EKS::Addon now returns <cluster>|<addon>, as on AWS.

    Migration

    use a published add-on name (vpc-cni, coredns, kube-proxy, aws-ebs-csi-driver, eks-pod-identity-agent) or add the add-on to the emulated catalog.

    eks cloudformation
  • Fixed

    a GraphQL authorization failure now returns AppSync’s GraphQL error envelope instead of the AWS JSON envelope.

    • status code and errorType/message are unchanged; only errors[0].errorType replaces the top-level __type.

    Migration

    read errors[0].errorType instead of __type when detecting an auth failure on POST …/graphql.

    appsync
  • Fixed

    ResourceNotFoundException now answers HTTP 400, matching AWS docs, instead of 404.

    Migration

    raw-HTTP or status-code-based ACM error handling should branch on 400, not 404.

    acm
  • Fixed

    BlockPublicPolicy refuses a * principal whose Condition is an empty object, as it already did for an omitted one.

    • AWS defines a public statement as one with no condition keys, so Condition: {} narrows nothing; the check is now shared with Lambda through serviceutil.

    Migration

    give a policy that relied on Condition: {} a real condition key or a specific principal, or send BlockPublicPolicy: false.

    secretsmanager
Added 18
  • EC2 launch templates, and Auto Scaling groups that launch from them — a CDK ASG stack now deploys instead of hitting a 501.

    3 details
    • seven EC2 operations, lt- IDs, versions from 1 with $Latest/$Default, and RunInstances merging a template under explicit parameters.

    • AWS::EC2::LaunchTemplate provisions for real: Ref is the template ID, and a data change makes a new default version rather than replacing.

    • CDK’s autoscaling.AutoScalingGroup emits a launch template by default, which is what used to stop the whole stack.

    ec2 autoscaling cloudformation
  • Get/Put/DeleteResourcePolicy manage a function’s resource-based policy as one document, with optional enforcement on service invokes.

    3 details
    • the document AddPermission and GetPolicy already maintain, by function, version or alias ARN; PutResourcePolicy replaces it wholesale and a stale RevisionId is PreconditionFailedException.

    • OVERCAST_ENFORCE_LAMBDA_RESOURCE_POLICY=true makes S3, SNS, API Gateway and EventBridge check the policy before invoking and fail as AWS does; direct client Invoke is never gated.

    • off by default, so a stack that never called AddPermission keeps working; statements evaluate as on AWS — Principal.Service, AWS:SourceArn/AWS:SourceAccount, explicit Deny winning.

    lambda
  • sandbox.localdomain resolves to the execution environment’s own loopback inside a Lambda sandbox, as it does on AWS.

    • an extension subscribing the destination AWS documents — http://sandbox.localdomain:<port> — receives its records; before, the subscription was accepted and every delivery died in the resolver.

    • the container carries an /etc/hosts entry for the name, so the runtime, the function and every extension resolve it too, not only the telemetry path.

    lambda
  • Backup Access Points — create, describe, list, delete and the two filtered listings, at AWS’s /backup-access-point bindings.

    3 details
    • metadata only, like the rest of the service: AccessPointPolicy is dropped and no S3 access point is created, so S3AccessPointArn/S3AccessPointAlias are absent rather than invented.

    • no backup job runs, so the two ListBackupAccessPointsBy* listings filter real stored metadata and come back empty.

    • /backup-access-point now classifies as backup; an unsigned request there was labelled, and IAM-authorised, as s3.

    backup
  • organizational units and the organization root, with AWS-shaped IDs, ARNs and paths — ListRoots plus the five OU operations.

    3 details
    • create, describe, rename, list by parent and delete a unit, nested under the root or another unit; the tag operations accept a unit or root ID as well as a policy ID.

    • every identifier is derived rather than minted, so the root and each unit keep their IDs, ARNs and Path across a restart or a state export/import.

    • inert, as the policy surface is: nothing is placed in a unit and Root.PolicyTypes is always empty, because accounts and policy-type enablement are not emulated.

    organizations
  • optional verification of AMAZON_COGNITO_USER_POOLS bearer tokens against the local Cognito user pool.

    3 details
    • OVERCAST_ENFORCE_APPSYNC_COGNITO_AUTH=true checks the RS256 signature, issuer, token_use, expiry and appIdClientRegex; anything failing gets AppSync’s 401 UnauthorizedException.

    • off by default, so a resolver test can keep sending an unsigned JWT to populate $ctx.identity.

    • the primary authorization mode and any Cognito entry in additionalAuthenticationProviders follow the same rule.

    appsync
  • one-line installers: curl -fsSL https://overcast.sh/install.sh | sh and irm https://overcast.sh/install.ps1 | iex.

    • detects OS and CPU, verifies the download against SHA256SUMS and installs to a per-user directory without sudo; flags and variables in docs/install.md.

    • each release carries a copy of both scripts pinned to that release, and CI installs from the published assets on Linux, macOS and Windows.

    cli
  • an EKS cluster, nodegroup or MSK cluster property the handler does not act on is reported instead of dropped in silence.

    • it becomes the resource’s ResourceStatusReason (Overcast: AWS::MSK::Cluster properties not applied: …), so it appears in cdk deploy output beside the resource it was dropped from.

    cloudformation eks msk
  • ListCertificateDomainValidations returns one synthesized SUCCESS summary per domain on a certificate.

    • covers the base DomainName plus each SubjectAlternativeNames entry, MaxItems/NextToken paginated.

    acm
  • FilterLogEvents returns an eventId for every event, and GetLogRecord resolves one back to that event.

    cloudwatch-logs
  • GenerateRandom returns NumberOfBytes (1-1024) cryptographically secure random bytes; it previously returned 501.

    • CustomKeyStoreId and Recipient are ignored.

    kms
  • a cluster created with clientAuthentication or encryptionInfo now says the broker behind it does not enforce them.

    • both are stored and returned by DescribeCluster as AWS does, but the Redpanda broker listens in plaintext, and a cluster that looked authenticated was worse than one that admits it.

    msk
  • the vpc-network-unbacked advisory reports a VPC whose Docker network Docker refused to create, the moment it happens.

    • CreateVpc still answers 200 and the next reconcile retries, but the health page names the VPC and quotes Docker’s reason rather than leaving an RDS or ECS failure to say so later.

    router ec2
  • encoding-type=url and fetch-owner on the bucket listings.

    • Key, Prefix, Delimiter, StartAfter and CommonPrefixes come back percent-encoded, with EncodingType echoed; an unsupported value is rejected with InvalidArgument.

    • fetch-owner=true adds the Owner element to each ListObjectsV2 entry.

    s3
  • SendMessage and SendMessageBatch return MD5OfMessageAttributes when a message carries attributes.

    sqs
  • a table row can open a detail panel underneath itself, and several rows can be open at once.

    • Step Functions’ state history keeps its per-event JSON; IAM group members, usage-plan keys and ECS task containers move from a panel at the bottom of the list to the row they belong to.

    web
  • the RDS instance Events feed is back on the shared table, failure rows still tinted red.

    • it gains the skeleton, error and empty states every other list has, and a sortable Time column.

    web
  • the service page says how to reach StartLiveTail from Go, which needs an immutable endpoint.

    • the Go SDK prefixes streaming- onto whatever host you configure, and only the legacy resolver can suppress it.

    docs cloudwatch-logs
Changed 6
  • tables: a focused row carries the 2px accent ring, wide tables reflow at narrow widths, and the Columns menu appears only where it earns its place.

    3 details
    • an outline on a <tr> painted only the row’s top and bottom edges; the ring is drawn across the row’s cells, on the hover wash. The dashboard’s service table gained the horizontal scroller.

    • a scroller with content off either edge shows a shadow there, and page-header controls wrap instead of running off-screen.

    • a Columns menu now needs five columns or more and never appears on a sub-table; the old rule put one on nearly every list, and 70 tables had turned it off by hand.

    web
  • a sorted list is a shareable link on every index page, and Back out of a request trace returns to where you were in the list.

    3 details
    • the sorted view lives in the URL as ?sort=<column> / ?sort=-<column>, surviving a reload and Back; Lambda, ECS, Auto Scaling, Applications, SQS, SNS, Kinesis, MSK and Pipes join the rest.

    • scroll offsets are cached per history entry, so a fresh navigation to the same page still starts at the top.

    • the cross-region notice — “No queues in us-east-1. There are 3 in ap-southeast-2.” — now sits under the empty state, inside the list card, on every page that shows one.

    web
  • every service page and guide was confirmed against the code, and what had drifted was corrected.

    3 details
    • ACM’s unimplemented import/renewal operations answer 400 UnknownOperationException, not 501; SSM’s RemoveTagsFromResource is supported and listed; the sample 501 body is the real one.

    • Lambda’s pages pointed “every LAMBDA_* variable” at docs/configuration.md, which no longer lists any; the LocalStack matrix now names /_overcast/metrics and lists /_localstack/state.

    • the guides had the same pass: missing elb/localhost.floci.io host-routing rows, which egress variables reach a Lambda versus an ECS task, an unmeasured startup claim, a stale Testcontainers tag.

    docs
  • every service quick start says what to do about credentials, and every divergence table reads | Area | On AWS | Overcast |.

    3 details
    • the 50 landing pages gained one line under the quick-start block: with an empty ~/.aws the CLI refused to sign and the first command died before reaching Overcast.

    • 24 pages had the AWS half buried inside the Overcast cell or negated into the Area cell; same facts, own column. make docs-check fails a two-column table unless a marker says why.

    • docs-lint also rejects two callouts stacked with nothing between them, which 24 pages had, and the design-rationale asides the template bans were cut across the ACM–WAF pages.

    docs
  • the S3, RDS and ECR limitations pages have room to grow again, README points at the hub instead of reproducing it, and docs/dev has a length budget.

    3 details
    • S3’s EventBridge material is now s3/notifications.md and RDS’s master account rules rds/master-account.md; ECR’s paragraphs became tables, 4,720 to 2,583 characters.

    • fourteen README routing rows were a second copy of docs/README.md; both are now the fact plus a link, and README is linted for the house-style tells.

    • docs/dev pages get 26,000 characters of prose and 30,000 of page; dev/networking.md drops from 41,448 to 24,123, its container half staying in dev/container-networking.md.

    docs
  • the real-engine RDS master-user tests retry an engine container the Docker daemon declined to start, and say what the container did when they fail.

    • a daemon that cannot start the engine image at all is a skip naming the reason, rather than a failure that reads as an RDS regression.

    • a failure quotes the container’s state and the last 40 lines of its output, where the reason on the record was one truncated line.

    ci
Fixed 36
  • two unnamed resources of one type in a stack no longer share a generated name, and generated names are capped at each service’s own limit.

    3 details
    • 31 handlers named an unnamed resource from the stack name alone, so the second collided with or overwrote the first — ELBv2, EKS, MSK, RDS, ElastiCache, Cognito, Glue, Firehose, Pipes, WAFv2 and more.

    • S3 bucket, SQS queue, SNS topic, Lambda function and layer, IAM role, user, group, policy and instance profile, and Kinesis stream names were minted against a 255 default real AWS rejects.

    • IAM allows a group, policy or instance profile 128 characters and a role or user 64; one constant carried 64 for all of them. An unnamed AWS::Glue::Database now deploys at all.

    cloudformation
  • AWS::EKS::Cluster, AWS::EKS::Nodegroup and AWS::MSK::Cluster now pass every documented property through.

    3 details
    • the cluster sent its VPC config under the template’s key names, so a CDK cluster ignored SubnetIds and stayed on the default network plane; AccessConfig, Logging and Tags were dropped.

    • the nodegroup dropped InstanceTypes, AmiType, DiskSize, CapacityType, Labels, Taints, LaunchTemplate, UpdateConfig and five more; UpdateConfig now also applies on a stack update.

    • MSK dropped ClientAuthentication, EncryptionInfo, ConfigurationInfo, LoggingInfo and four more; all are stored and echoed as AWS does, the configuration under currentBrokerSoftwareInfo.

    cloudformation eks msk
  • a resource that fails because a service call did now reports CloudFormation’s own reason shape instead of the raw error body.

    • the reason names the service, status, error code and request ID, then the operation’s token and a HandlerErrorCode, as real CloudFormation does.

    • a body that parses as neither XML nor JSON still falls back to the status and the body, so nothing is lost.

    cloudformation
  • Ref on AWS::EKS::Cluster returns the cluster name, as on AWS, instead of the ARN.

    • a nodegroup, Fargate profile or add-on wired to its cluster by Ref now reaches CREATE_COMPLETE; Fn::GetAtt Cluster.Arn still returns the ARN, and older stacks still delete cleanly.

    cloudformation
  • AWS::AutoScaling::AutoScalingGroup reads VPCZoneIdentifier in its documented list form, not only as a string.

    cloudformation autoscaling
  • VPC networks are no longer deleted when Overcast restarts into a schema migration, stranding every ECS task and Lambda in the VPC.

    3 details
    • the Docker reconcile ran on the daemon’s connected event without waiting for the state store, so on an upgrade carrying a migration it read no VPCs at all.

    • every EC2 network then looked unclaimed and was swept as an orphan, and the completed pass vouched for the result, so nothing looked at the question again.

    • placements failed with ResourceInitializationError: … connect to VPC network <id>: 404: network <id> not found for the life of the process, through redeploys alike.

    ec2 router
  • wiping the data directory no longer strands the Docker networks and containers the previous run created, so a redeploy gets its CIDR back.

    3 details
    • the identity every Docker resource is labelled with was a UUID stored inside the very state store it named, so a wipe minted a new one and the old resources became nobody’s.

    • each wipe held another VPC’s /16 on the daemon until CreateVpc was refused with Pool overlaps with other one on this address space and the deploy failed at RDS.

    • the identity is now derived from the data directory — its volume or bind in a container, its path and host name natively — so it survives a wipe and a memory-backed restart.

    ec2 docker
  • RunInstances places an instance in the availability zone of its SubnetId, and honours Placement.AvailabilityZone on the typed path (#1722, #1743).

    • the zone fell back to <region>a whatever the subnet or placement said, so a multi-zone Auto Scaling group launched every instance into one zone, and a placement contradicted its own subnetId.

    • a Placement.AvailabilityZone that disagrees with the subnet’s zone is now refused with InvalidParameterValue, as EC2 does.

    ec2
  • ModifyLoadBalancerAttributes and DescribeLoadBalancerAttributes are emulated rather than answering 501.

    • CloudFormation treats a failed in-place update as a replacement, and CDK sets deletion_protection.enabled on every load balancer it creates.

    • so each stack update tore the load balancer down and took its listener and the target group the ECS service was registered with along with it.

    elbv2
  • an ELB Classic call (API version 2012-06-01) now gets a 501 instead of an ELBv2-shaped answer.

    • Classic and ELBv2 share the elasticloadbalancing signing name and most action names, so DescribeTags was answered 200 by ELBv2 and CreateLoadBalancer 400, naming an ELBv2 member.

    • a Query request is resolved by the API version the pinned models attribute it to before any action-name match, so the log, trace and IAM labels name the service that answered.

    elbv2 router
  • StartLiveTail opens with the initial-response frame, so the AWS SDK for Go v2 gets a session instead of deadlocking (#1064).

    • the AWS JSON protocols carry an operation’s initial response document there, and smithy-go parks the deserializer on that frame before it yields the stream.

    • sessionStart and sessionUpdate follow it unchanged, so the console’s tail view and every JS SDK consumer see what they saw before.

    cloudwatch-logs
  • DescribeLogGroups and DescribeLogStreams honour limit and page with nextToken.

    • the page defaults to and is capped at AWS’s documented 50 items; an unrecognised nextToken reports InvalidParameterException instead of silently restarting at page one.

    cloudwatch-logs
  • a real, unimplemented AWS operation now answers 501 NotImplemented, not a 400 claiming it does not exist.

    3 details
    • also CloudWatch Logs, CloudTrail, ECS, Secrets Manager, Shield and Step Functions; ACM’s ImportCertificate, RenewCertificate and ExportCertificate answered UnknownOperationException.

    • the 501 is in the request’s own wire format, so an RPC v2 CBOR caller gets CBOR.

    • a name AWS does not model keeps each service’s own 400 — and now gets it on the no-codec legacy path too, where ACM, Kinesis, Shield and Step Functions answered a blanket 501.

    acm dynamodb sqs kinesis cloudwatch
  • List* operations return AWS’s default page of 25 when maxResults is omitted, instead of the whole collection.

    • omitting it meant “everything”, so a client’s paging loop terminated on the first call locally and paged for real against AWS; one constant now serves both the cap and the default.

    appsync
  • a TTL transition settling at the same moment as a new UpdateTimeToLive no longer overwrites it.

    • the settle now runs under the table’s lock and only for the transition it was armed for, so a just-disabled table cannot read ENABLED.

    dynamodb
  • Get* and Create* return a resource’s Tags, AttachmentCount is counted, and List* trims to AWS’s documented subset.

    3 details
    • GetRole, GetUser, GetPolicy and GetInstanceProfile return the Tags that were stored but never rendered; List* still omits them, as AWS does, and points at the matching Get*.

    • AttachmentCount counts the users, groups and roles a managed policy is attached to instead of always reading 0; PermissionsBoundaryUsageCount is reported too, derived on read.

    • ListRoles and ListUsers omit PermissionsBoundary and ListPolicies omits Description; GetAccountAuthorizationDetails keeps both, as AWS documents.

    iam
  • GenerateDataKey* honour NumberOfBytes; ScheduleKeyDeletion and CancelKeyDeletion return the key ARN, and the former its PendingWindowInDays.

    • a request for 64 bytes returned 32, so envelope-encryption code splitting a data key derived both halves from overlapping bytes.

    • the deletion calls returned the bare key id, which does not parse where a caller feeds it into something ARN-shaped, and omitted the waiting period that applied, hiding the 30-day default.

    kms
  • token validation no longer mints a signing key for the pool named in an unverified issuer claim.

    • a bearer token with a made-up issuer reaching an API Gateway Cognito authorizer cost an RSA-2048 generation and a permanent cognito:sigkeys record per issuer.

    cognito
  • extensions subscribed to the Telemetry API or the Logs API now receive their records on Docker Desktop.

    • batches are POSTed to the extension’s listener by the in-container init, from inside the execution environment, exactly as AWS’s platform posts them.

    • previously the emulator posted them from the host at the container’s bridge IP, which Docker Desktop’s VM-hosted engine has no route back from, so every delivery timed out on Windows and macOS.

    lambda
  • cancelling a delayed state transition at the moment it fires no longer crashes the process.

    • Cancel, Stop and a rescheduled key all read the timer’s own report of having been stopped as proof its callback would not run, so both sides could finish the same transition.

    • the second close of its completion channel then panicked; ownership is now claimed once, under the scheduler’s lock, and a transition a cancel wins does not run its callback at all.

    lifecycle
  • Docker network events reach the daemon for the first time, so health stops reporting a removed network and a repaired one reports ok again.

    3 details
    • the event stream filtered on the overcast.managed label, and the Engine sends no labels at all on a network event — so every network event had always been suppressed.

    • the daemon re-verifies a managed network on its create event against the spec the probe resolved for it — read-only, so it never races overcast network reset.

    • a destroy delivered after the probe recorded a network heals on the create behind it, rather than vanishing from docker.networks until Overcast is restarted.

    networking
  • the organization ID has AWS’s shape (o- plus ten characters), and invalid input is InvalidInputException with the modeled Reason.

    • the ID is derived deterministically from the account ID, so it stays stable for the life of the store; it was the eight-character o-overcast.

    • MaxResults outside 1-20 is refused instead of silently clamped, and a policy Name/Description over 128/512 characters is MAX_LENGTH_EXCEEDED.

    organizations
  • ChangeResourceRecordSets no longer 404s for boto3 and the AWS CLI — the trailing-slash URI they build is registered.

    • botocore binds the operation to POST /2013-04-01/hostedzone/{Id}/rrset/, the Smithy model Overcast pins binds it without the slash, and chi treats the two as separate routes.

    • ListResourceRecordSets answers both spellings too, and the reachability sweep now probes a modeled trailing slash instead of normalising it away.

    route53
  • an unsatisfiable Range answers 416 with the InvalidRange document real S3 sends, and without the Content-Range header real S3 omits.

    3 details
    • it previously announced the whole object length and sent no body, so SDK clients saw a broken connection rather than an error code.

    • the body carries RangeRequested and ActualObjectSize, the two fields a ranged-download client compares to find its own off-by-one; confirmed against real S3 on 2026-09-06.

    • HeadObject honours Range too, and a Range S3 cannot parse is ignored — the whole object, as on AWS — rather than a 416.

    s3
  • PutObject and CompleteMultipartUpload honour If-None-Match and If-Match, so conditional writes no longer overwrite.

    • a guarded write against a key that already has a current object answers 412 PreconditionFailed; If-Match against a key with none answers 404.

    • an If-None-Match carrying an ETag rather than * answers 501 NotImplemented instead of being ignored.

    s3
  • GetObject, GetObjectTagging and CopyObject’s source lookup return NoSuchBucket for a bucket that does not exist (#1635).

    • previously both answered NoSuchKey, indistinguishable from a missing key in an existing bucket.

    s3
  • HeadObject returns the x-amz-meta-* user metadata GetObject returns for the same object, instead of an empty metadata map.

    s3
  • a FIFO ReceiveMessage batch drains a whole message group instead of one message per group.

    • MaxNumberOfMessages=10 against a five-message group returned one message; it now returns all five in sequence order and fills the batch from other unblocked groups, as AWS does.

    sqs
  • GetQueueAttributes reports CreatedTimestamp, LastModifiedTimestamp and ApproximateNumberOfMessagesDelayed.

    • All is honoured from any position in the AttributeNames list, not only the first.

    sqs
  • JSON-protocol error responses carry x-amzn-query-error, matching AWS’s awsQueryCompatible behaviour (#1810).

    • e.g. GetQueueUrl on a missing queue sends AWS.SimpleQueueService.NonExistentQueue;Sender alongside the JSON body’s own __type.

    sqs
  • overcast serve --help links the published configuration reference instead of an internal/config/config.go source path.

    cli
  • the OVERCAST_STATE_<SERVICE> unknown-service error points at docs/configuration.md#service-names instead of the removed docs/README.md table.

    config
  • “Hide internal” on the traces page hides the console’s own topology and Lambda-instance polling.

    • the emulator classified /_overcast/topology and /_overcast/lambda/instances as user traffic, so a row of each appeared roughly once a second.

    • a deep-search match is filtered the same way as a listed row, which it was not before — a hidden service reappeared as soon as a search matched a body.

    web
  • the deploy-diagnostics tab no longer crashes for a stack whose failed resource is not an ECS service.

    • the server omits a resource’s evidence sections when no collector covers its type, which is every type but AWS::ECS::Service; the console’s type declared the field required.

    web
  • a right-aligned sortable column right-aligns its header, and clicking beside the copy control in an S3 bucket’s URL cell no longer opens the bucket.

    web
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.41

View on GitHub
v0.0.1-alpha.40 prerelease 13 assets
1 added · 5 changed · 2 removed docs release ci

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.40

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.40

Downloads

Changelog

1 added · 5 changed · 2 removed

Added 1
  • make docs-lint now holds every published page to a length budget and the house style.

    3 details
    • a page over 6,000 characters of prose, or 12,000 of page, fails unless it states a “ reason — which fails in turn once the page comes back inside the budget.

    • the stock tells go too: “not X — it’s Y”, “seamless”, “delve”, the three-adjective slogan, “Under the hood”, “Note that”, and openers that narrate the page. An allowlist takes a real exception.

    • both measures exclude the generated capability block, and no published page may link outside docs/, which is all the console can open.

    docs
Changed 5
  • the long guides are landing pages now, one sub-page per concern, and every link that named a section points at the page holding it.

    3 details
    • networking gains ten sub-pages and CDK five; the CLI reference splits one page per command group, and configuration, HTTPS, performance and the migration guide each keep a short landing page.

    • the Docker mechanics behind an EC2 VPC move to docs/networking/vpc-backing.md: backing bridge, labels, gateway isolation and the OVERCAST_EC2_VPC_STRATEGY CIDR strategies.

    • the console serves the new pages, and the health advisories and reference rows that deep-linked a moved section were repointed with them.

    docs
  • a service’s limitations page is a table now, with a concern page behind each row that needs one.

    3 details
    • Lambda splits into concurrency, execution environments, event delivery and retries, logging and runtimes; ECS’s scheduler and CloudFormation’s updates, teardown and dynamic references follow.

    • a service directory may hold concern-named sub-pages beside operations, limitations, troubleshooting and examples: a hyphenated slug, linked from its landing page, opening its own Related with it.

    • the Lambda init-volume advisory and its troubleshooting entry deep-link docs/services/lambda/execution-environments.md, which now carries that section.

    docs
  • each fact in the docs has one home, the hub routes to it, and every page ends with a Related footer.

    3 details
    • 79 pages gained a Related section and 21 bare “Back to X.” openers became a line that says what the page holds; the hub opens with a runnable quick start and lists one line per guide.

    • the Docker Desktop egress caveat, the OVERCAST_VPC_EGRESS mode list and the auto storage default are each stated once and linked from the pages that repeated them.

    • five reference pages came inside the length budget, the Lambda examples page leads with its examples, and docs/index.md is titled “Reference index”.

    docs
  • the 0.0.1-alpha.39 notes are re-curated to a summary line and at most three short detail lines per entry.

    • every fact is kept: what does not fit points at the page that carries it, and every migration: note stays, trimmed in wording only.

    • the section measures the same as a fragment now — 69 bullets, none over three detail lines or 200 characters, where 43 were over before.

    release
  • changelog.py check budgets an entry’s detail: three continuation lines, 200 characters each.

    • a breaking entry’s migration: note does not count towards the three, and is held to the same length.

    • “ above an entry buys it more, and fails once that entry is back inside the budget.

    ci
Removed 2
  • the operation manifest page, a hand-committed snapshot of go run ./cmd/stub-report output.

    • it listed unexported Go type names, no target regenerated it, and nothing but docs search read it.

    • docs/dev/smithy.md points at the command instead, so the inventory is produced on demand rather than read stale.

    docs
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.40

View on GitHub
v0.0.1-alpha.39 prerelease 11 assets
23 added · 15 changed · 26 fixed · 1 removed · 2 deprecated · 3 breaking networking cli router config compat +23 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.39

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.39

Downloads

Changelog

23 added · 15 changed · 26 fixed · 1 removed · 2 deprecated · 3 breaking

Breaking changes

  • Changed

    OVERCAST_VPC_EGRESS=open|routed|none decides container egress; open is the default, so every container reaches the internet.

    3 details
    • none makes every network Overcast creates --internal — both planes and every per-VPC network — for deterministic CI and air-gapped hosts. Overcast’s APIs and the Runtime API keep working

    • routed decides egress per subnet, from that subnet’s route table — see the Added entry above. An invalid value fails startup rather than falling back

    • a VPC network still follows its internet gateway under open, and that now costs nothing; the gateway no longer decides egress alone, which made a private-with-NAT subnet look isolated

    Migration

    nothing to do on Docker Desktop, which already behaved this way. A containerised or native-Linux host with an --internal control plane now gets egress: set OVERCAST_VPC_EGRESS=none

    networking
  • Changed

    the GitHub org and Go module path moved to overcast-sh.

    • repo github.com/Neaox/overcast -> github.com/overcast-sh/overcast; images now publish as ghcr.io/overcast-sh/overcast[:tag]

    Migration

    update imports to github.com/overcast-sh/overcast and pull ghcr.io/overcast-sh/overcast[:tag], not ghcr.io/neaox/overcast. Old module versions and Neaox URLs still resolve

    release
  • Changed

    POST /_overcast/debug/reset moved to POST /_overcast/reset and no longer needs OVERCAST_DEBUG.

    Migration

    call /_overcast/reset (and /_overcast/reset/{service}); the debug-gated path is gone

    router cli
Added 23
  • OVERCAST_VPC_EGRESS=routed gives each subnet the egress its route table describes, and withholds it everywhere else.

    3 details
    • a 0.0.0.0/0 route to an attached internet gateway, or an available NAT gateway, grants that subnet’s containers a route out. Anything else withholds it: connections fail with ENETUNREACH

    • a route table that changes moves running containers on or off a second, routable per-VPC network in place, so an in-flight invocation keeps its Runtime API. The VPC’s plane stays --internal

    • run Overcast in a container: on a native Windows or macOS host it cannot withhold egress, and says so. Full rules in docs/networking.md

    networking
  • OVERCAST_VPC_EGRESS_POOL sets the range routed carves its per-VPC egress networks from, one /24 each.

    3 details
    • it defaults to 198.18.0.0/16, the RFC 2544 benchmarking range, which supports 256 VPCs with egress and is never routed on the internet. /8 to /24, validated at startup in every mode

    • the pinned range never draws on Docker’s own default address pools, which stretch to about 31 networks on a stock daemon and are shared with every other tool. Each VPC keeps its /24 across restarts

    • an exhausted pool fails the placement, naming the pool and how to widen it, rather than quietly starting a container without the egress its template grants

    networking
  • a health advisory, vpc-egress-not-withheld, when the host cannot withhold the egress the configured mode asks for.

    3 details
    • on Docker Desktop with Overcast on the host, containers reach the Lambda Runtime API at the host’s own address, so that network stays routable and keeps a route out. Until now: one WARN at boot

    • under routed it reports VPC placement only: the mode leaves the shared data plane routable, as AWS does outside a VPC, so a container there takes a route its subnet never granted; none isolates it

    • it distinguishes a shortfall from a choice: the deprecated OVERCAST_CONTROL_PLANE_INTERNAL=false gives the isolation up, and the advisory says so rather than reporting a host that refused

    networking
  • every Docker network Overcast reuses is verified field by field against the state this configuration would create, on every start.

    3 details
    • driver, isolation, IPv6, IPAM and driver options are all compared, not just the isolation flag; a network with no overcast.network.spec-hash label counts as mismatched, and those have been wrong

    • a mismatched plane with nothing attached is recreated; one with containers attached is left alone, warned about by name and field, degraded in /_overcast/health, and raised as a console advisory

    • after upgrading every VPC network mismatches and is rebuilt once under its containers, dropping open connections — see docs/networking.md

    networking
  • isolating the control plane now warns loudly at startup, because the cost lands a long way from the cause.

    • a VPC-attached container joins its VPC network and the control plane; when both are internal it gets no default route at all and fails with ENETUNREACH inside application code, minutes later

    • docs/troubleshooting.md gains a symptom-to-fix entry for ENETUNREACH, covering the hybrid case of reaching real AWS from a locally emulated function

    networking
  • overcast network status and overcast network reset report and rebuild the Docker networks Overcast manages.

    • status reports each VPC’s egress network beside its plane, says which one carries the route out, and exits non-zero on drift, which is what makes it a CI gate

    • reset stops the containers Overcast started, disconnects containers it did not and leaves them running, then rebuilds the network to spec. --dry-run prints the plan and changes nothing

    cli
  • overcast start / stop / restart / logs manage a background instance, natively or as a container.

    3 details
    • native by default, no Docker required; --docker runs the instance as a container instead, with --image/--channel/--data-volume/--mount-docker-socket

    • overcast logs streams a background instance’s output either way, with -f/--follow and -n/--tail

    • overcast status lists every registered instance — name, backend, endpoint and running/stopped/unknown state — beside its health check, and overcast wait blocks until the daemon is healthy

    cli
  • overcast env and overcast aws point a shell or the AWS CLI at the emulator without stray AWS_* variables reaching it.

    3 details
    • env prints exports for sh, PowerShell and fish, including unset lines for any other AWS_* variable the shell exports (AWS_PROFILE, AWS_SESSION_TOKEN, per-service endpoint overrides, …)

    • so nothing left over can redirect a call to real AWS. aws runs the host AWS CLI with those same variables scrubbed

    • it tab-completes its passthrough arguments through the AWS CLI’s own aws_completer when that is installed, falling back to file completion otherwise

    cli
  • overcast reset [service], overcast services and overcast config cover the rest of a local loop.

    • reset wipes emulated state behind a TTY confirmation prompt, and services lists enabled services and their emulation tiers

    • config shows the running daemon’s effective config (needs OVERCAST_DEBUG)

    cli
  • /_health and /_localstack/health are served as aliases of /_overcast/health, so a LocalStack healthcheck works.

    3 details
    • a 404 there is read by an orchestrator as a dead container: it restarts Overcast, and on the default in-memory state backend a restart wipes every resource a deploy in flight had created

    • /_localstack/health answers in LocalStack’s shape (a services map plus edition and version), and init, init/{stage} and POST state/reset run the same handlers as the /_overcast/ ones

    • the rest of /_localstack/ 404s naming the Overcast endpoint that replaces it — diagnose, config and usage included — instead of an S3 error

    router
  • LocalStack’s /_aws/ses (GET, DELETE) and /_aws/sqs/messages inspection endpoints are served, so test assertions carried over from LocalStack pass.

    3 details
    • /_aws/ses lists captured emails in LocalStack’s {"messages": [...]} shape from the same inbox as /_overcast/ses/inbox/messages, with ?id= and ?email= filters; DELETE clears them

    • /_aws/sqs/messages peeks a queue without consuming it, as an SQS ReceiveMessageResponse in XML or JSON per Accept, honouring ShowInvisible and ShowDelayed

    • the rest of /_aws/ answers 404 naming the Overcast endpoint that has the data, instead of an S3 error

    router
  • a volume mounted at LocalStack’s /var/lib/localstack becomes the state directory when nothing else says where state goes.

    • a compose file migrated from LocalStack unchanged used to run silently ephemeral, because the mount was not where Overcast looks

    config
  • more of a LocalStack environment carries over unchanged, and its Docker-flag and network variables are recognised as inert.

    3 details
    • LS_LOG, ENFORCE_IAM, DNS_ADDRESS=0 and LAMBDA_REMOVE_CONTAINERS are honoured; the last inverts onto LAMBDA_KEEP_CONTAINERS, and the two spellings agreeing is agreement, not a conflict

    • DOCKER_HOST is used when LAMBDA_DOCKER_SOCKET is unset, so Colima, Rancher Desktop, Podman and rootless Docker work; unix://, tcp://, npipe://, http:// dial, ssh:///https:// fall back

    • MAIN_DOCKER_NETWORK, ECS_DOCKER_FLAGS, EC2_DOCKER_FLAGS and BATCH_DOCKER_FLAGS are inert — the Java Testcontainers module sets the three flag ones, and Overcast labels its own containers

    config
  • the LocalStack Testcontainers modules for Java, Node, Python, Go and .NET start the Overcast image with only the image name changed.

    • Overcast prints LocalStack’s Ready. line once every listener is bound, and the image answers /usr/local/bin/docker-entrypoint.sh; snippets and tag rules are in docs/testcontainers.md

    • a Testcontainers module for Go of our own, github.com/overcast-sh/overcast/testcontainers/go, starts the container, waits on /_overcast/health, and exposes endpoint, region and credential helpers

    compat testcontainers
  • LAMBDA_RUNTIME_API_HOST=auto|<address> pins the address containers dial for the Runtime API, skipping the probe

    lambda
  • 34 operations newly modeled by AWS are recognised, spanning one service new to the corpus (iam-toolbox).

    • a signed request to one reaches a protocol-correct 501 marked x-emulator-unsupported, in that service’s own error envelope

    • it used to fall through to the S3 fallback and come back as a bucket or object answer

    router
  • service icons in the console sidebar, dashboard and search use each service’s catalog colour; toggle under Settings → Appearance.

    3 details
    • on by default. The sidebar, dashboard tiles and global search repaint live the moment the switch flips — an active row keeps its service colour, “you are here” moving to the row background and label

    • the topology map and the ARN combobox already coloured unconditionally for functional reasons and are unchanged

    • the tab favicon keeps its fixed ink, since a coloured 16px browser-tab icon loses legibility against arbitrary browser chrome

    web
  • code examples in the docs viewer are tabbed by language and syntax-highlighted.

    • multi-language SDK examples render as language tabs and the pick sticks across code groups and pages, as an overcast:code-tabs comment-sentinel region that still reads as plain sections on GitHub

    • fenced blocks in the docs viewer and the per-service modals are highlighted for bash/sh, go, typescript/ts/tsx, javascript, json/jsonc, yaml, python, java, csharp, powershell and sql; others are plain

    web docs
  • a LocalStack compatibility matrix, and a CLI reference covering every overcast subcommand.

    • the matrix lists every port, URL, hostname, container convention, client tool and behavioural convention with its status, each gap naming its issue; the migration guide links to it and stays short

    • the CLI page covers start/stop/restart/status/wait/logs/services/reset/config/env/aws/import/bridge/https/trust/mcp, and is linked from the README and the docs index

    docs
  • a one-page content charter (docs/dev/content-charter.md) for published docs, enforced by the docs lint.

    • covers citation, prose-economy and table-vs-prose rules; referenced from CONTRIBUTING.md and AGENTS.md

    • scripts/docs-index.go --check rejects a published doc citing docs/dev/** or docs/plans/** — literal path or resolved link — and one whose frontmatter description exceeds 220 characters

    docs
  • a moving :latest Docker tag on both images, tracking the alpha channel until the first stable release ships and stable releases from then on

    release
  • changelog entries must lead with a standalone summary sentence, capped at 160 chars.

    • scripts/changelog.py check enforces the cap on new and edited fragments; detail beyond the summary goes on indented continuation lines, rendered as their own line under the bullet

    • breaking entries also sort first within their category when assembled, so a scanner hits BREAKING on the first bullet

    release
  • a skip link, aria-current on the sidebar, and keyboard reordering of pinned services.

    • the sidebar is forty-odd links deep, so reaching the content by keyboard meant tabbing the whole service list after every navigation; the collapse toggle names what it collapses and reports its state

    • pinned services reorder from a drag handle that takes focus — Space picks a row up, the arrows move it, Space drops it, Escape cancels — where the sidebar had a PointerSensor and nothing else

    web
Changed 15
  • a second Overcast on one host keeps Lambda and Inbox capture: busy default ports 9001 and 1025 fall back to ephemeral ones.

    • LAMBDA_RUNTIME_API_PORT and OVERCAST_SMTP_PORT accept 0 for an ephemeral port; any other value is pinned

    • a pinned port that cannot bind is a startup warning naming the variable, and /_overcast/health reports the failed listener, its bind error and the fix under listeners

    lambda ses
  • twenty more LocalStack variables are recognised as inert instead of silently ignored, each with a startup line saying why.

    • SQS_ENDPOINT_STRATEGY, S3_SKIP_SIGNATURE_VALIDATION, IAM_SOFT_MODE, LAMBDA_DOCKER_FLAGS, SNAPSHOT_*, PROVIDER_OVERRIDE_* and the CORS knobs among them

    config
  • reported as partial tier rather than inert in /_overcast/health, and the console’s “inert service” banner is gone.

    • PutEvents fans out to targets with retries and dead-lettering, which was never inert; capability notes now list ECS and event-bus targets, and docs/services/eventbridge.md already said partial

    eventbridge
  • the console passes an axe-core WCAG 2.2 AA sweep of every surface, in both themes, at two widths and with dialogs and menus open.

    3 details
    • 160 violations before, 0 after: an unnamed region combobox, service cards claiming to be buttons, header cells announcing “blank” above every row, code blocks and tables the keyboard could not scroll

    • labels attach through the htmlFor Field never passed, with their hints, errors and aria-invalid; tabs pair with panels; search is a real combobox; the sidebar a list; tables report their length

    • the accent and the three semantic colours clear 4.5:1 on every surface in both themes and nothing sits below 11px; pnpm a11y and pnpm type-audit drive a console and fail on a regression

    web
  • the docs navigation and search index are derived at runtime instead of generated into the repository, and the console fetches them.

    3 details
    • web/src/docs-nav.gen.ts and internal/docssearch/index.gen.jsonl are deleted: per-page manifests every docs PR rewrote, so two docs branches conflicted on files nobody had written by hand

    • internal/docsindex parses the docs/ set the binary embeds and internal/bff serves it from GET /api/docs/nav, encoded once behind a sync.Once: ~360us to ~30us warm, 21 allocations to 11

    • the console no longer bundles a 7,332-line generated module and retains 3.5 MB rather than 4.6 MB; make docs-index becomes make docs-lint, and docs/dev/generated-files.md is the inventory

    build web
  • every service page is rewritten to the service page template, with the long-form material on sub-pages.

    3 details
    • each opens with one line of positioning, a status token and a copy-pasteable quick start, then what works and how it diverges from AWS; the operation table moves to <service>/operations.md

    • every service in docs/services/README.md is covered: Lambda drops from 1,169 lines to 130, ECS from 490 to 94, CloudFormation from 450 to 109, RDS from 464 to three pages

    • the gap lists are table-first and Ctrl+F-navigable; ten stale “Summary” tables that had drifted from the generated one below them are gone, as are the CloudFormation and Lambda duplicate tables

    docs
  • one voice across the service pages and guides: same table columns, Related order, status rule and spelling everywhere.

    3 details
    • Differences from AWS is | Area | On AWS | Overcast | on every page (two-column where a page never stated the AWS half); What works is | Area | Behaviour |

    • a page’s **Status:** follows its coverage tier — Comprehensive is Supported, everything else is Partial — which reconciles ten pages that disagreed with the generated index

    • Related runs sub-pages, siblings, the service index, configuration, guides, AWS reference last; descriptions say what a page contains; docs/services/README.md is an index, not a prose page

    docs
  • the guides are tightened to the content charter, and each subject has one owner.

    3 details
    • docs/README.md splits into standalone pages — configuration, debug endpoints, storage, networking, troubleshooting; docs/persistence.md folds into docs/storage.md, the networking pages into one

    • docs/troubleshooting.md opens with a symptom index spanning every guide, the root README is a front door rather than a second CLI reference, and pages under “Getting Started” get real sections

    • explanations written out twice now have one owner and a link — S3 reserved service labels, the status-token legend, the six-field cron(...) rule — and docker run examples pull :latest

    docs
  • heading anchors in the console and the anchor checker match GitHub’s, so a section link resolves on github.com, the website and the console alike.

    • punctuation is dropped rather than folded into a hyphen and repeats are numbered from -1, as github-slugger does it; Data-plane endpoints — RDS… is #data-plane-endpoints--rds…, not one hyphen

    • the console’s docs viewer ids headings with inline code, which it used to id as object-object, and a # comment inside a fenced example no longer counts as a heading

    docs
  • the LocalStack migration guide and compatibility matrix describe LocalStack’s March 2026 editions instead of the retired “Community Edition”.

    • the guide says which services on LocalStack’s paid plans carry over unchanged, and that a carried-over LOCALSTACK_AUTH_TOKEN is logged once as inert

    • the matrix states the one behavioural divergence plainly: LocalStack isolates no network, so a gateway-less VPC there still has egress — which OVERCAST_VPC_EGRESS=open, the default, also gives you

    docs
  • the container-image deployment path is documented end to end, and pinned by an ECR-push-to-invoke integration test.

    • docs/services/lambda/examples.md carries the minimal PackageType=Image recipe — push to repositoryUri, deploy the amazonaws.com ImageUri, override with ImageConfig

    • troubleshooting gains a section on containers that cannot reach the Runtime API

    lambda
  • the local VPC guide leads with a local resources stack that creates the VPC in CDK, replacing the bootstrap-script-plus-metadata-file pattern.

    • application stacks still take an ec2.IVpc and stay environment-agnostic, and the fromLookup/fromVpcAttributes provider pattern is kept for VPCs created outside CDK

    • the page intro is cut to the content charter’s two-sentence budget

    docs cdk
  • capability notes say what the origin proxy, TestFunction and the AppSync data source types actually do

    cloudfront appsync
  • the console previews text-like objects larger than 1 MiB instead of declining the object outright.

    • shows the first megabyte, via the ranged read it already made

    web s3
  • the service docs modal follows links into the new sub-pages in place, with a way back, instead of leaving them as dead relative links

    web
Fixed 26
  • a Docker network Overcast could not read, or that is no longer there, is no longer reported as correct.

    3 details
    • an inspect that failed for any reason other than “no such network” fell straight into the create, and Docker returns an existing network unchanged — so a drifted network stayed drifted, unreported

    • a failed read is retried once and a network that still cannot be read is reported as unverified; every create is verified afterwards, whichever way it got there, at one inspect per network per start

    • a destroyed network is dropped from the report when the Docker watcher sees it go, and reset says when it could not judge one — see docs/networking.md

    networking
  • a Docker network whose isolation disagrees with the configuration is recreated at startup, when nothing is attached to it.

    • Docker never applies --internal retroactively, so a plane created before alpha.37 silently kept egress forever — the machine that “still worked” in the report that prompted this

    • a network that still has containers attached is left alone and warned about at WARN, naming the overcast network reset that fixes it; both planes are checked, not just the control plane

    networking
  • one Overcast instance no longer deletes another’s VPC networks, and a state reset no longer orphans its own.

    3 details
    • the reconcile sweep removed every network labelled overcast.service=ec2 its own store did not claim — on a shared daemon, a neighbour’s live VPC network. Only the creating instance removes one

    • every network now carries overcast.instance, which names the instance rather than its emulated state: <service>:instance is exempt on every reset path, overcast reset <service> included

    • it used to be wiped, leaking one network per VPC per reset. Networks already orphaned that way are not collected — a gone identity looks like a live neighbour’s — so network status reports them

    ec2 state
  • the startup reconcile covers VPCs in every region, so a VPC outside the default region gets its Docker network back after a restart.

    3 details
    • the default region’s pass took every other region’s network for an orphan and removed it; a network is unclaimed only when no VPC in any region names it

    • a region the startup pass missed is reconciled on the first placement into it — that lazy pass adopts what exists and creates what is missing, never rebuilding a drifted network under containers

    • a placement that cannot read the daemon or the store waits 30 seconds before asking again, and a VPC created while the reconcile read the store keeps its network instead of being recorded unbacked

    ec2
  • attaching an internet gateway takes a VPC network out of --internal even with containers on it, and fails the call when it cannot.

    • containers are moved to the recreated network with their addresses and DNS aliases; a refusal answers InternalError and records no attachment

    • under the shared strategy a network shared by several VPCs is external while any of them has a gateway; a flag found stale at startup is repaired or raised as vpc-network-isolation-stale

    ec2
  • the Runtime API address is chosen by container reachability, not bindability, and a container that dies during INIT says why.

    3 details
    • Overcast binds each candidate and has a throwaway container connect back, keeping the first that answers — host.docker.internal now outranks a host address that bound behind a Windows firewall

    • when none is reachable, /_overcast/health reports the listener unreachable, a critical advisory names every address tried, and Runtime.InitError says the same. The verdict is remembered

    • the probe’s image pull happens once, before the candidate walk and on its own clock, not inside its 45s budget — see docs/services/lambda/troubleshooting.md

    lambda
  • the init volume is no longer deleted by another Overcast instance sharing the daemon.

    3 details
    • it carries overcast.instance, the same identity stamped on VPC networks and containers. Reuse across instances of the same build is unaffected, the volume’s name being content-addressed already

    • pruning a superseded build’s volume, or an empty one after a failed start, happens only for a volume this instance created; pruning is per architecture, so an amd64 seed cannot prune a live arm64 one

    • a volume found empty that this instance may not delete is never reused — the next cold start copies the init in directly — and is surfaced as an advisory rather than a debug-level log line

    lambda
  • LastUpdateStatus is reported, so aws lambda wait function-updated and the SDK, CDK and SAM waiters over it return instead of timing out (#1550).

    3 details
    • every FunctionConfiguration carries LastUpdateStatus with its LastUpdateStatusReason/LastUpdateStatusReasonCode; a zip deploy and every config change answer Successful outright

    • an UpdateFunctionCode pointing a PackageType=Image function at a new image answers InProgress and settles when the pull does — Successful, or Failed with ImageAccessDenied/InvalidImage

    • a second update, or a PublishVersion, arriving inside that pull is refused with ResourceConflictException as on AWS, and CloudFormation’s stabilizer waits out an in-place update, not only a create

    lambda
  • a stack whose AWS::Scheduler::Schedule carries no Name deploys.

    3 details
    • Name is optional and CDK’s L2 Schedule leaves it out, but the handler forwarded the empty string and CreateSchedule binds the name into its path, so the dispatch went to /schedules/, unmatched

    • it fell through to the fallback that claims the bare /schedules DataBrew models and answered 501, so every default CDK schedule died on CreateSchedule: HTTP 501 and took its stack down with it

    • the name is minted from the stack and logical ID the way AWS::Scheduler::ScheduleGroup’s already was, and an update reads it back off the physical ID rather than off the previous template

    cloudformation scheduler
  • a stack recreated under a deleted stack’s name no longer inherits its events; DescribeStackEvents by name lists the current stack alone.

    • events are keyed by the stack generation — the uuid in its StackId — rather than by name, as on AWS. The CDK’s retry of a ROLLBACK_COMPLETE CDKToolkit stack read the old CREATE_FAILED as its own

    • a deleted generation’s events stay readable by passing its StackId, even after the name has been reused; events written by earlier versions are moved to the new layout on first read

    cloudformation
  • LocalStack-alias configuration works against the Docker images.

    3 details
    • they baked ENV defaults for OVERCAST_PORT, OVERCAST_LISTEN, OVERCAST_LOG_LEVEL, OVERCAST_DEFAULT_REGION, OVERCAST_ACCOUNT_ID and OVERCAST_DEBUG, indistinguishable from explicit settings

    • so docker run -e DEFAULT_REGION=eu-west-1 (or EDGE_PORT, GATEWAY_LISTEN, DEBUG) failed startup as a disagreement with the image itself

    • the images bake only OVERCAST_DATA_DIR=/data and its provenance marker, the binary defaults cover the rest, and the DATA_DIR alias overrides that default instead of conflicting with it

    docker config
  • overcast status probed /health, a path the daemon does not serve, so it reported an error against a healthy daemon.

    • it checks /_overcast/health and enriches its one-liner with the version and storage backend

    cli
  • a drained or deleted service’s tasks left their internal.ecs.pause network-namespace containers running on the daemon.

    • they stayed until the background container GC got to them, while the same teardown removed the application containers inline

    • the service scheduler’s scale-down and a failed launch’s unwind take the namespace container down before returning, so the API call’s return means the task’s containers are actually gone

    ecs
  • shutdown waits for the daemon to confirm the registry container is actually removed, instead of giving up once its name merely stopped resolving.

    • the container’s AutoRemove can race Docker’s own exit-triggered removal against our explicit one; only the daemon’s own /wait?condition=removed signal can tell them apart from a stalled removal

    ecr
  • an email send no longer hangs when the SMTP capture server failed to bind; it fails at once with the reason and the variable to change

    ses
  • GetLatestConfiguration returns the Version-Label header when the hosted configuration version carries a label.

    • the AppConfig control plane already stored the label; the data plane omitted it and claimed the control plane did not

    appconfigdata
  • the post-run leaked-container audit read one docker ps snapshot the instant the resource sweep finished.

    • containers whose removal the emulator had already queued — ECS pause containers, Lambda execution environments, ElastiCache nodes — were reported as leaks on nearly every CI run

    • it polls for up to 30 s and reports only the containers that survive the grace period

    compat
  • an empty DynamoDB, SNS or Kinesis list in the console says when the resources are in another region.

    • a CLI deploying into the region AWS_REGION names while the console lists the server default read as “ListTables returns []”; the empty state names the region that has them and offers to switch

    • credentials never partition state: a signed CLI client and the console, in the same region, see the same tables, queues and buckets, and integration tests now pin that

    web dynamodb sns kinesis
  • a DynamoDB table that fails to load says so instead of rendering an empty page.

    • a failed DescribeTable returned nothing at all — no heading, no message; it follows the same error treatment as every other list in the app

    web
  • the Lambda VPC notices no longer say Overcast enforces no isolation.

    • a call to a resource outside the VPC is refused by name; what is never enforced is the finer filtering — security groups, NACLs, and the public/private subnet split. The banner title said the opposite

    • the title claims only what holds on every host — placement is enforced through Overcast’s DNS resolver, which does not run on a native Windows or macOS host — and the body carries the condition

    web
  • the “Preview (first 1 MiB)” notice appeared on every ranged preview, a 36-byte object included, because S3 answers 206 for any satisfiable range.

    • it is keyed on Content-Range and shows exactly when the object holds more bytes than the preview does

    web s3
  • the SES dashboard card no longer promises delivery history; the console SES page manages identities only, and sent mail is in the Inbox

    web
  • the console honours the saved theme from its first paint, and a missing bundle asset answers 404 rather than the SPA index page.

    • the theme was applied after the connection gate, so the connect and cold-boot screens — the first thing a new user sees — rendered in the default one whatever they had chosen

    • the SPA fallback answered every unmatched path with index.html, so a stale or missing /assets/* file arrived as HTML and failed wherever it was parsed as script; only a real app route falls back

    web
  • the networking pages agree with the code about egress, isolation and what an internet gateway decides.

    3 details
    • the gateway stopped deciding egress, not isolation: under open a VPC network is still --internal without one, which three pages and one self-contradicting ASCII diagram had inverted

    • six places promised OVERCAST_VPC_EGRESS=none is hermetic without qualification — on Docker Desktop the control plane stays routable so the Runtime API survives, and one Lambda row leaned on that

    • the Runtime API address is described as measured, not inferred, and the seven behaviours this release changes are written down — see docs/networking.md

    docs
  • stale claims across the service pages are corrected against the code.

    3 details
    • cognito, kms, waf, sts, cloudfront, appsync, elb, apigateway, cloudwatch-logs, cloudtrail, backup, msk, rds, s3, glue, scheduler, pipes, eventbridge, ec2, transfer, opensearch and elasticache.

    • docs/README.md no longer claims the Inbox captures SNS mobile push (application subscriptions are rejected at Subscribe time), the counts read 127 provisioned and 9 stubs of 136 registered

    • docs/configuration.md gains OVERCAST_SERVICE_METRICS and OVERCAST_UI_PORT and fixes the Windows LAMBDA_DOCKER_SOCKET default; the Lambda CDK examples point AWS_ENDPOINT_URL at 4566

    docs
  • the CHANGELOG.md version compare links point at github.com/overcast-sh, so every one of them resolves.

    • they still pointed at the pre-rename owner, which also failed the release gate that checks them

    release
Removed 1
  • the dev-only overcast mcp workspace-MCP subcommand — never shipped in a release.

    • the workspace MCP server (repo-aware tools for agents/editors) is now its own standalone command, cmd/overcast-mcp, run with go run ./cmd/overcast-mcp --stdio

    cli
Deprecated 2
  • OVERCAST_CONTROL_PLANE_INTERNAL — set OVERCAST_VPC_EGRESS instead. Still honoured, and setting it logs a notice.

    • it pins one network, and a container takes its default route from whichever of its networks is routable, so isolating one of them settled nothing

    • true becomes OVERCAST_VPC_EGRESS=none, false becomes open

    networking
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.39

View on GitHub
v0.0.1-alpha.38 prerelease 11 assets
13 added · 4 changed · 12 fixed · 25 breaking apigateway web cloudformation s3 iam +19 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.38

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.38

Downloads

Changelog

13 added · 4 changed · 12 fixed · 25 breaking

Breaking changes

  • Changed

    HTTP (v2) API error metrics are recorded under AWS’s real metric names, 4xx and 5xx

    Migration

    CloudWatch queries or alarms watching 4XXError/5XXError on an HTTP API must switch to 4xx/5xx; REST API metric names are unchanged

    apigateway
  • Changed

    CreateBucket now rejects a global-namespace bucket name ending in the reserved -an suffix

    Migration

    rename a bucket ending in -an, or create it with x-amz-bucket-namespace: account-regional instead

    s3
  • Fixed

    Usage plan responses (CreateUsagePlan, GetUsagePlan, GetUsagePlans) no longer return a keyIds member — the API Gateway UsagePlan shape has no such member; the underlying key associations are unaffected and remain fully readable

    Migration

    stop reading keyIds from usage plan responses; the AWS SDKs never surfaced the member there, so SDK-based clients are unaffected — use GetUsagePlanKeys (the modeled channel) to read a plan’s attached API key IDs

    apigateway
  • Fixed

    RestApi responses (CreateRestApi, GetRestApi, GetRestApis, UpdateRestApi) no longer return an arn member, and HTTP Api responses (CreateApi, GetApi, GetApis) no longer return one either — neither the REST (v1) RestApi model nor the v2 Api model has an arn member anywhere

    Migration

    stop reading arn from REST API or HTTP API responses; the AWS SDKs never surfaced the member on either shape, so SDK-based clients are unaffected

    apigateway
  • Fixed

    ListApplications and DeleteApplication no longer leak tags/applicationTag, CreateAttributeGroup/UpdateAttributeGroup no longer leak attributes, ListAttributeGroups/DeleteAttributeGroup no longer leak tags/attributes, and associated-resource entries no longer carry a creationTime — the AppRegistry model binds these operations to ApplicationSummary, AttributeGroup, AttributeGroupSummary and ResourceInfo respectively, narrower shapes than the ones the emulator was reusing verbatim from Create/Get/Update

    Migration

    stop reading tags, applicationTag, or attributes off list/delete responses and creationTime off associated-resource entries — real AWS never sent them there either; the fields remain on Create/Get/UpdateApplication and Create/Get/UpdateAttributeGroup, where the model defines them

    appregistry
  • Fixed

    ApiCache, DataSource, FunctionConfiguration and Resolver no longer echo an apiId member, and DisassociateSourceGraphqlApi/DisassociateMergedGraphqlApi now return only sourceApiAssociationStatus instead of the full sourceApiAssociation — matching the AppSync model, which never declares apiId on those four shapes and only sourceApiAssociationStatus on either Disassociate response

    Migration

    a raw-JSON consumer should stop reading apiId off cache/data-source/function/resolver response objects (the caller already supplied it as the request path parameter) and read sourceApiAssociationStatus directly instead of sourceApiAssociation.sourceApiAssociationStatus on the two Disassociate responses

    appsync
  • Fixed

    The Events API management operations (CreateApi/GetApi/ListApis/UpdateApi and the ChannelNamespace equivalents) emitted created and lastModified as ISO-8601 strings.

    • The AppSync model declares these members as plain restJson1 timestamps — epoch seconds on the wire — and strict deserializers such as the AWS SDK for Go v2 fail on the string form (“expected Timestamp to be a JSON Number”), while boto3 and the CLI happened to tolerate it. The fields are now JSON numbers carrying epoch seconds with millisecond precision, verified end-to-end against the Go SDK v2 appsync client.

    Migration

    Event APIs and channel namespaces persisted by earlier versions store the old string timestamps and fail to load after upgrading; delete and recreate them, or start from fresh state.

    appsync
  • Fixed

    GetWorkGroup no longer echoes a Tags member on WorkGroup — the Athena model’s WorkGroup shape has no Tags member; it was leaking straight out of the persisted record

    Migration

    read tags via ListTagsForResource instead of WorkGroup.Tags

    athena
  • Fixed

    DescribeChangeSet no longer returns a ChangeSetType member — ChangeSetType is an input-only member of CreateChangeSet;

    • DescribeChangeSetOutput does not have it. The value is still recorded internally and used to drive change-set behavior

    Migration

    stop reading ChangeSetType from DescribeChangeSet responses; the AWS SDKs never surfaced the member there, so SDK-based clients are unaffected — the value you set on CreateChangeSet is already known to the caller

    cloudformation
  • Fixed

    OriginRequestPolicyConfig’s CookiesConfig, HeadersConfig, and QueryStringsConfig now round-trip the modeled CookieBehavior/HeaderBehavior/QueryStringBehavior member alongside their lists, instead of a bare Quantity/Items list that silently dropped the behavior

    Migration

    stored origin request policies created before this change lose their persisted cookie/header/query-string lists on next read, because the old JSON shape ({"quantity":N,"items":[...]}) does not populate the new {"cookie_behavior":...,"cookies":{...}} shape; recreate any pre-existing OriginRequestPolicy records after upgrading. SDK clients are unaffected going forward, since they already send the modeled, required *Behavior member and now get it back on Get/List responses instead of a bare list.

    cloudfront
  • Fixed

    RealtimeLogConfig’s Fields list now uses <Field> child elements, matching the FieldList shape’s xmlName trait, instead of the generic <member> element used by other lists

    Migration

    SDK and CLI clients are unaffected, since they already send and expect <Field>. Anything hand-parsing the previous, incorrect <member> wire shape needs to switch to <Field>.

    cloudfront
  • Fixed

    GetTrailStatus emitted a fabricated LatestCloudWatchLogsError member, always empty.

    • The real optional member is LatestCloudWatchLogsDeliveryError, which real AWS omits from the response while empty; overcast never populates it, so the field is dropped entirely rather than emitted empty.

    Migration

    LatestCloudWatchLogsError is gone; the real member is LatestCloudWatchLogsDeliveryError, omitted from the response while empty.

    cloudtrail
  • Fixed

    self-service GetUser no longer fabricates UserCreateDate/UserLastModifiedDate. AWS’s GetUserResponse shape carries only Username, UserAttributes, MFAOptions, PreferredMfaSetting and UserMFASettingList — the two timestamp members belong to AdminGetUserResponse/UserType, which AdminGetUser and ListUsers keep returning unchanged.

    Migration

    read a user’s creation/modification timestamps from AdminGetUser instead of self-service GetUser, as on real AWS.

    cognito
  • Fixed

    table descriptions no longer carry fabricated top-level BillingMode, TTL or Tags members on CreateTable/DescribeTable/UpdateTable/DeleteTable responses

    Migration

    raw-JSON consumers must read BillingModeSummary.BillingMode (absent for a table left on the default PROVISIONED mode, matching AWS), DescribeTimeToLive and ListTagsOfResource — the modeled channels, already supported — instead of the table description; SDK clients are unaffected

    dynamodb
  • Fixed

    CreateSecurityGroup, AssociateAddress and DeleteVpcEndpoints stopped emitting a <return> element the AWS model never defines for their results (CreateSecurityGroupResult, AssociateAddressResult and DeleteVpcEndpointsResult declare no such member — verified against the Smithy EC2 model), and AssociateRouteTable now names its association ID <associationId>, matching AssociateRouteTableResult’s xmlName/ec2QueryName trait, instead of <newAssociationId>, which belongs to a different pair of operations (ReplaceRouteTableAssociation/ReplaceNetworkAclAssociation).

    • Strict deserializers such as the AWS SDK for Go v2 decode by the modeled name, so AssociateRouteTable previously handed every SDK caller back an empty AssociationId; it now comes back populated.

    Migration

    raw-XML or regex-based consumers of AssociateRouteTableResponse must read associationId, not newAssociationId. Callers going through an AWS SDK are unaffected by the removed return fields (SDK output types never exposed them) and gain a correctly populated AssociateRouteTable AssociationId.

    ec2
  • Fixed

    SetRepositoryPolicy no longer returns a repositoryArn member — the AWS ECR API models that response as [registryId, repositoryName, policyText] only, matching its sibling GetRepositoryPolicy/DeleteRepositoryPolicy operations

    Migration

    stop reading repositoryArn from SetRepositoryPolicy responses; the AWS SDKs never surfaced the member, so SDK-based clients are unaffected — use DescribeRepositories to read a repository’s ARN

    ecr
  • Fixed

    RegisterContainerInstance/DeregisterContainerInstance/DescribeContainerInstances, RunTask/StopTask/DescribeTasks and CreateService/UpdateService/DeleteService/DescribeServices no longer emit clusterName on a container instance, networkConfiguration/networkNamespaceId on a task, dockerId on a task’s containers, or taskSets on a service — none of these are members of AWS’s ContainerInstance, Task or Service shapes.

    • A task’s awsvpc details were always available on attachments as well, which is where AWS puts them; networkConfiguration only really exists on Service, Deployment and TaskSet, and Overcast keeps emitting it there

    Migration

    raw-JSON callers reading these fields directly lose them; the AWS SDKs never modeled them, so SDK-based clients are unaffected

    ecs
  • Fixed

    Serverless cache responses (CreateServerlessCache/DescribeServerlessCaches/ModifyServerlessCache/DeleteServerlessCache) echoed a SnapshotArnsToRestore member on the ServerlessCache shape.

    • The ElastiCache model declares SnapshotArnsToRestore only on CreateServerlessCacheRequest; the ServerlessCache output shape has no such member. The field is no longer emitted on the wire, matching real AWS; the create call still accepts it as input.

    Migration

    SDK clients are unaffected — they never modeled a SnapshotArnsToRestore field on the ServerlessCache response shape, so nothing to change there. Callers that were parsing it off raw XML/JSON responses should stop relying on it.

    elasticache
  • Fixed

    GetDatabase/GetDatabases/GetTable/GetTables no longer echo a Tags member on Database/Table — the Glue model’s Database and Table shapes have no Tags member; it was leaking straight out of the persisted record

    Migration

    read tags via GetTags instead of Database.Tags/Table.Tags

    glue
  • Fixed

    CreateFunction and UpdateFunctionConfiguration reject function environment variables that use Lambda’s reserved runtime keys (AWS_REGION, AWS_LAMBDA_FUNCTION_NAME, _HANDLER, AWS_ACCESS_KEY_ID, AWS_LAMBDA_RUNTIME_API, LAMBDA_TASK_ROOT, …) with real Lambda’s InvalidParameterValueException (”… contains reserved keys that are currently not supported for modification”), instead of accepting them and silently overriding the values at container start.

    • The list is AWS’s documented reserved set; the unreserved runtime keys (PATH, LANG, LD_LIBRARY_PATH, NODE_PATH, NODE_OPTIONS, PYTHONPATH, GEM_PATH, TZ, …) stay settable, as on AWS

    Migration

    remove reserved keys from a function’s Environment.Variables — real AWS never accepted them, and the runtime always provided its own values for them inside the container, so deleting them changes nothing the function observes

    lambda
  • Fixed

    DescribeOrganization emitted a fabricated MasterUserEmail member on the Organization shape;

    • the real API has no such member. It now emits the modeled trio MasterAccountId, MasterAccountArn, and MasterAccountEmail, verified against the Organizations Smithy model.

    Migration

    Callers reading MasterUserEmail from DescribeOrganization must switch to MasterAccountEmail for the email address, and can now also read MasterAccountId and MasterAccountArn; MasterUserEmail is gone — it never existed in the AWS API.

    organizations
  • Fixed

    V2 GetEmailIdentity no longer returns an IdentityName member — the SESv2 API models that response without one; IdentityName belongs only to ListEmailIdentities’ IdentityInfo items, which are unaffected

    Migration

    stop reading IdentityName from GetEmailIdentity responses; the AWS SDKs never surfaced the member there, so SDK-based clients are unaffected — the identity name is already known to the caller (it’s the request’s EmailIdentity) or is available from ListEmailIdentities

    ses
  • Fixed

    ListProtections/DescribeProtection no longer echo a Tags member on Protection — the Shield model’s Protection shape has no Tags member; it was leaking straight out of the persisted record

    Migration

    read tags via ListTagsForResource instead of Protection.Tags

    shield
  • Fixed

    DescribeSubscription drops the hardcoded SubscriptionState member — that field belongs only to GetSubscriptionStateResponse, a separate operation Overcast does not implement, and was never part of Subscription

    Migration

    none available; GetSubscriptionState is not implemented

    shield
  • Fixed

    DescribeServer no longer returns a CreatedAt member on the described server — the AWS Transfer Family API has no server creation timestamp anywhere in its model, so the emulator was inventing wire data

    Migration

    stop reading Server.CreatedAt from raw DescribeServer responses; the AWS SDKs never surfaced the member, so SDK-based clients are unaffected

    transfer
Added 13
  • Monitor tab on the REST and HTTP API detail pages, charting requests, 4XX/5XX errors, and latency per API or per stage

    apigateway web
  • AWS::S3::Bucket gains BucketNamespace and BucketNamePrefix, both Replacement on update; BucketNamePrefix appends the account/region suffix the way the console does

    cloudformation s3
  • the s3:x-amz-bucket-namespace condition key is populated from the request header, so AWS’s published Deny+StringNotEquals account-regional enforcement pattern works

    iam
  • the Telemetry API subscription endpoint (PUT /2022-07-01/telemetry) — the surface modern observability extensions call — with schemaVersion validation, the documented cross-API exclusivity with the Logs API, and JSON-format function log records embedded as objects from schemaVersion 2022-12-13.

    • Deliveries are batched per the subscription’s buffering configuration — maxItems, maxBytes and timeoutMs with AWS’s defaults and limits — instead of one POST per record, and a subscriber whose batch was lost is told: the next batch opens with a platform.logsDropped event carrying the dropped counts, in AWS’s documented shape

    lambda
  • platform records now carry AWS’s documented depth.

    • platform.start, platform.runtimeDone and platform.report gain the tracing member with the X-Amzn-Trace-Id actually handed to the runtime; platform.runtimeDone metrics — including the responseLatency span, and producedBytes from the length the runtime declared — are measured by the in-container init, from the runtime being handed the event to its answer arriving back, with the host’s own measurement as the fallback when the runtime never answered (responseDuration and runtimeOverhead stay documented-out — the first ends only after the answer has streamed through, the second exists only at the runtime’s next poll); and platform.runtimeDone and platform.report carry errorType: Runtime.ExitError when the runtime exited, while timeout and handler-error records stay nameless, as AWS’s own examples do

    lambda
  • extensions’ own lifecycle is on the platform stream: platform.extension at registration (replayed to later subscribers) and platform.telemetrySubscription / platform.logsSubscription at subscribe, named for the API the subscription came through

    lambda
  • execution environments report how they were initialized (on-demand, proactive after a settled deploy, or provisioned) and why they were removed (idle TTL, configuration change, container died, …) — an origin badge and removal reason on the instances panel and system map, initOrigin/evictedReason on the instance SSE events, and an init_origin field on the cold-start server log line

    lambda web
  • 292 operations newly modeled by AWS are recognised, spanning 4 services new to the corpus (account-access, agent-registry, agent-registry-control, pricing-plan-manager). A signed request to one reaches a protocol-correct 501 marked x-emulator-unsupported, in that service’s own error envelope, instead of falling through to the S3 fallback and coming back as a bucket or object answer

    router
  • CreateBucket supports account regional namespaces: x-amz-bucket-namespace: account-regional with a <prefix>-<accountId>-<region>-an name creates the bucket in the account’s per-region namespace instead of the global one, and re-creating it returns BucketAlreadyOwnedByYou in every region, including us-east-1

    s3
  • Monitor charts gained labeled X/Y axes with aligned gridlines, an AWS-console-style auto-refresh interval picker (Off/10s/30s/1m/5m), and a bucket-width chip;

    • any card expands into a full-width dialog with drag-to-zoom into a time window, a y-axis scale, and per-series whole-range summaries in the legend; the time window holds still while auto-refresh is off or a chart is being inspected

    web
  • tick boxes on the bucket’s object listing, with a header box for everything currently listed, and a Download .zip button that fetches the whole selection as one archive — entries named relative to the folder they were ticked in, so a selection made in logs/2026/ unpacks as q1.csv rather than rebuilding the key path around it.

    • The archive is built and sent object by object rather than assembled first, and the console asks for it with a form submission so the browser streams it to disk: a multi-gigabyte selection costs neither the server nor the tab multi-gigabyte memory. An object that disappears between the listing and the download is named in a _download-errors.txt inside the archive, since a download that has already begun has no status code left to spend

    web s3
  • S3 folders and objects are addressable — the object browser keeps its place in the URL, so an object is linkable and reload, Back and Forward all work — and the object inspector gains a Versions tab: one object’s own history, with the details and preview of whichever revision you pick

    web s3
  • the create-bucket dialog gains an S3 Bucket Namespace choice — global (default) or account regional, which turns the name field into a prefix and shows a live <prefix>-<accountId>-<region>-an preview before submitting — and the bucket list badges a bucket “Account regional” when its name matches the reserved suffix, inferred from the name, since neither ListBuckets nor the AWS console reports a namespace for an existing bucket

    web s3
Changed 4
  • request metrics are recorded under every AWS-documented dimension combination — ApiName and ApiName+Stage (REST), ApiId and ApiId+Stage (HTTP) alongside the detailed per-route set — so CloudWatch queries at the API or stage level now return data

    apigateway
  • the 5-minute rollup tier is retained for 30 days (previously 7), and the 30-day Monitor view charts 15-minute buckets instead of 1-hour ones

    metrics
  • the pinned AWS API models moved to 2026-08-21:

    • 37 operations changed protocol traits, 5 changed an HTTP or target binding and 37 path bindings changed which services share them. A protocol-trait change moves that operation’s error envelope; a binding change moves which request shape reaches the operation at all; a binding shared by several services is answered without the credential-scope check an unshared one gets, because the models no longer name a single owner for it

    router
  • log rendering converged on the shared virtualized viewer:

    • its table mode gained level badges, click-to-expand rows, and hover-copy, and a new reusable LogPanel owns fetch + filter + auto-refresh for any log group or stream (declarative filter model with relative or fixed time windows); the Lambda Monitor tab now renders its logs through it, with a filter box and time-window picker

    web
Fixed 12
  • REST v1 create-deployment returned createdDate as epoch milliseconds;

    • the AWS CLI and SDKs parse the field as epoch seconds, so the call errored client-side even though the deployment was created. The response now uses the same seconds conversion as get-deployments and the stage responses

    apigateway
  • DescribeImages and BatchGetImage answer ServerException when the backing image registry could not be reached, instead of ImageNotFoundException.

    • An image record exists only because a sweep of the registry container put it there, so a registry that was merely unreachable — one still starting after a restart, say — left a store indistinguishable from a repository nobody had ever pushed to, and the emulator reported the image as absent. That is a fact it had not established: cdk-assets reads a missing image as “publish this again”, so every cdk deploy against an unreachable registry rebuilt and re-uploaded container assets that were already there. The sweep also retries its /tags/list request once, after a short pause, when the first attempt is inconclusive — waitRegistryReady only proves the registry container answers /v2/ before letting a caller past it, and a repository-scoped question can still stumble the instant the container’s listener starts accepting connections — and its give-up paths now log which one they took, where previously all five were silent

    ecr
  • negated condition operators (StringNotEquals, StringNotEqualsIgnoreCase, StringNotLike, NumericNotEquals, DateNotEquals, ArnNotEquals, ArnNotLike, NotIpAddress) match — so a Deny guarded by one applies — when their condition key is absent from the request, matching AWS instead of always evaluating unmet

    iam
  • s3:x-amz-bucket-namespace is populated only when the request carries the X-Amz-Bucket-Namespace header, instead of always defaulting an absent header to global

    iam
  • an extension subscribing to platform records mid-startup could be replayed its INIT phase out of order when a record arrived while the container’s address was being attached

    lambda
  • an invocation log could open with the handler’s first lines in front of START — in the X-Amz-Log-Result tail and CloudWatch both — when the host was loaded; START is now written before the runtime is handed the invocation, so the order no longer depends on scheduling

    lambda
  • a Telemetry API record could be lost to one failed POST — an extension subscribed to platform records could miss platform.initRuntimeDone on a loaded host; deliveries are retried and a genuinely dead destination is now reported at Warn instead of silently dropped

    lambda
  • the /2026-07-09/ dated prefix — which AWS moved GetResourcePolicy, PutResourcePolicy and DeleteResourcePolicy onto — is recognised by service detection, so a request under it is logged, traced and IAM-authorised as Lambda’s rather than as an S3 object write into a bucket named 2026-07-09, and a signed request reaches Lambda’s own protocol-correct 501 for these still-unimplemented operations instead of an S3 answer. Unsigned traffic without a bearer token remains S3’s, as on every REST path, by the router’s design

    lambda
  • x-amz-expected-bucket-owner (and the CopyObject/UploadPartCopy source variant) is honoured: a mismatched account ID returns 403 AccessDenied instead of silently proceeding

    s3
  • the console’s own proxy hop dropped two things the browser sent with an object request, and both fixes land in the object inspector:

    • the Range header, so the preview pulled a whole object into the browser to show its first megabyte and the “preview truncated” notice — which reads the 206 back — never appeared; and the versionId, so the preview and Download link on a version-history row served the current object’s bytes while describing the older version by its real metadata (size, ETag — those come from a HeadObject that did carry the version)

    web s3
  • a Monitor chart bucket with no adjacent data now renders as a visible dot; it previously painted nothing, leaving a chart that looked empty despite real data

    web
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.38

View on GitHub
v0.0.1-alpha.37 prerelease 11 assets
26 added · 15 changed · 63 fixed · 2 removed · 20 breaking athena backup cloudwatch-logs ci cloudformation +51 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.37

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.37

Downloads

Changelog

26 added · 15 changed · 63 fixed · 2 removed · 20 breaking

Breaking changes

  • Changed

    LAMBDA_PROACTIVE_INIT now defaults to true:

    • 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

    set LAMBDA_PROACTIVE_INIT=false to opt back out and restore the previous on-demand-only behaviour

    lambda
  • Changed

    the control plane is now created --internal whenever 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 set PubliclyAccessible (RDS) / assignPublicIp: ENABLED (ECS) per docs/networking.md, to restore reach

    networking ec2
  • Changed

    the Aurora writer endpoint is now {cluster}.cluster.{region}.rds.{base}, not {cluster}.cluster-rw.….

    • cluster-rw was 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 bare cluster. The reader endpoint is unchanged

    Migration

    nothing to do if you read the endpoint from DescribeDBClusters or 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

    rds
  • Fixed

    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 reserved aws: key prefix, an over-length key or value, or more than 50 tags on one resource is refused instead of silently stored, on every TagResource-style operation and every Create*’s inline Tags member 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 reserved aws: 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

    acm apigateway appregistry appsync cloudformation cloudfront cognito dynamodb ecr efs glue iam kinesis kms msk secretsmanager stepfunctions
  • Fixed

    BackupVaultTags/CreateBackupVault and BackupPlanTags/CreateBackupPlan are now validated and stored instead of being silently accepted and dropped, so a vault or plan tagged with a reserved aws: key prefix, an over-length key or value, or more than 50 tags is refused instead of succeeding with the tags discarded

    Migration

    fix any BackupVaultTags/BackupPlanTags that use a reserved aws: 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

    backup
  • Fixed

    ECR repository templates now get their ImageTagMutability, ImageScanningConfiguration, EncryptionConfiguration and Tags.

    • The repository handler’s create sent only repositoryName and applied RepositoryPolicyText/LifecyclePolicy as follow-ups, dropping the rest even though the ECR service already stored and echoed ImageTagMutability on both wire paths — a CDK repository with imageTagMutability: TagMutability.IMMUTABLE provisioned as mutable. ImageTagMutability and ImageScanningConfiguration now reach CreateRepository on create and are reconciled through two new operations, PutImageTagMutability and PutImageScanningConfiguration, on update; Tags reaches CreateRepository and is reconciled through TagResource/UntagResource, the same as the Lambda, LogGroup, Secrets Manager and SQS handlers. EncryptionConfiguration is 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. EmptyOnDelete is honoured on delete: force is only passed to DeleteRepository when the template says EmptyOnDelete: 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 set EmptyOnDelete: true on the repository to keep deleting it while it still holds images

    cloudformation ecr
  • Fixed

    AWS::ElastiCache::CacheCluster takes its cluster name from ClusterName, the resource’s own property.

    • It read CacheClusterId — 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

    CacheClusterId on this resource was never valid CloudFormation and is now ignored. Spell it ClusterName to 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

    cloudformation elasticache
  • Fixed

    Fn::GetAtt on a cache cluster resolves the endpoint pair the engine has — RedisEndpoint.Address/.Port for Redis and Valkey, ConfigurationEndpoint.Address/.Port for Memcached, as AWS populates them.

    • Only the ConfigurationEndpoint pair was exported, whatever the engine, and a GetAtt on an attribute a handler does not export does not fail: it falls back to the physical ID. CDK’s attrRedisEndpointAddress therefore handed out a bare cluster ID as a hostname, which no container advertises and nothing can resolve, so an ECS task with REDIS_HOST baked in at deploy time died on getaddrinfo rather 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’s ConfigurationEndpoint.Address is now empty rather than carrying the endpoint, which is what AWS answers for it. Read RedisEndpoint.Address — CDK’s attrRedisEndpointAddress — as code written against AWS already does

    cloudformation elasticache
  • Fixed

    a DELETE_COMPLETE stack is no longer resolvable by name on DescribeStacks, DescribeStackEvents, ListStackResources, DescribeStackResources, GetTemplate and GetTemplateSummary — each now answers ValidationError: 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_PROGRESS looked up by name; only the terminal record excludes the name path. Mutations already treated DELETE_COMPLETE as nonexistent; this closes the one read-side divergence that was deliberately left open

    Migration

    a client that polled DescribeStacks by name to observe a delete finishing (rather than by the stack ID CreateStack returned) now gets a ValidationError instead of a DELETE_COMPLETE body once the delete completes — the same as the AWS SDK’s stack-delete-complete waiter, 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

    cloudformation
  • Fixed

    AWS::CloudTrail::Trail now reads the trail name from TrailName, the real resource schema’s property, instead of Name

    Migration

    rename Name to TrailName in any template using this resource; a template still setting Name gets an auto-generated trail name instead of a validation error, since Overcast has no per-property unrecognised-property diagnostic

    cloudformation cloudtrail
  • Fixed

    AWS::KMS::Key used to provision successfully and quietly keep the AWS default whenever a template set Origin to anything other than AWS_KMS, MultiRegion: true, or EnableKeyRotation: true — the emulator models none of import/CloudHSM origins, the replica-key relationship multi-Region implies, or rotation state.

    • CreateKey now answers ValidationException for the first two (and both are replacement-required on update); EnableKeyRotation: true (CDK’s enableKeyRotation, a common setting) is now dispatched to the KMS service’s EnableKeyRotation operation, which does not exist, so the resource fails loudly instead of silently reporting rotation off forever — the same “dispatch to the gap” treatment AWS::IAM::User’s LoginProfile gets

    Migration

    drop Origin/MultiRegion from any AWS::KMS::Key template that relied on them being ignored, and drop EnableKeyRotation: true (enableKeyRotation: true in CDK) until key rotation is emulated

    cloudformation kms
  • Fixed

    AWS::Athena::WorkGroup now reads the workgroup configuration from WorkGroupConfiguration, the real resource schema’s property, instead of Configuration (the CreateWorkGroup API member it maps onto); a template’s result location, bytes cutoff and the rest were silently dropped before

    Migration

    rename Configuration to WorkGroupConfiguration in any template using this resource; a template still setting Configuration gets a workgroup with no configuration instead of a validation error, since Overcast has no per-property unrecognised-property diagnostic

    cloudformation athena
  • Fixed

    native overcast serve now defaults to binding 127.0.0.1 instead of 0.0.0.0;

    • the containerised default (Docker image, OVERCAST_DATA_DIR_SOURCE=image) is unchanged at 0.0.0.0. An explicit OVERCAST_LISTEN always wins over either default

    Migration

    native users reaching Overcast from another machine, a VM, or a phone on the same network must set OVERCAST_LISTEN=0.0.0.0 to restore the old reach

    config
  • Fixed

    OVERCAST_HOST is renamed to OVERCAST_LISTEN (LocalStack’s GATEWAY_LISTEN idiom) and removed rather than kept as an alias — a leftover OVERCAST_HOST now fails at startup naming OVERCAST_LISTEN as the replacement, instead of being silently ignored

    Migration

    rename OVERCAST_HOST to OVERCAST_LISTEN wherever it is set (env, .env files, docker-compose.yml, CI config) — the value format is unchanged, the same comma-separated address list

    config
  • Fixed

    DeleteSecurityGroup, DeleteSubnet and DeleteVpc now answer DependencyViolation while a dependent (instance, ENI, subnet, gateway, endpoint, peering connection) remains, matching AWS instead of deleting unconditionally; the VPC default security group answers CannotDelete

    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 as DELETE_FAILED instead of a silent dangling reference

    ec2
  • Fixed

    CreatePipe/UpdatePipe now refuses a stream source’s DeadLetterConfig naming 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

    point DeadLetterConfig.Arn at an SQS queue or SNS topic, or remove it

    pipes
  • Fixed

    CreateDBCluster and CreateDBInstance enforce 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

    rds
  • Fixed

    a DBSubnetGroupName naming no existing subnet group is refused by CreateDBCluster and CreateDBInstance, and DBSubnetGroupNotFoundFault carries 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

    rds
  • Fixed

    cluster BackupRetentionPeriod defaults to 1 and is held to AWS’s documented 1–35 on create and modify; Port is held to 1150–65535 on both

    Migration

    a cluster created without BackupRetentionPeriod now reports 1 rather than 0. BackupRetentionPeriod=0 was never a value AWS accepts for a cluster and is now refused; send 1 to mean the minimum

    rds
  • Fixed

    AppRegistry, EFS, EKS and Scheduler no longer answer POST / for an X-Amz-Target header (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 under docs/services/, the same way any AWS SDK or the aws CLI reaches them today

    router appregistry efs eks scheduler
Added 26
  • CreateWorkGroup persists Configuration and GetWorkGroup returns it; it was previously discarded

    athena
  • 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 forwards BackupVaultTags/BackupPlanTags and reconciles a tag-only stack update in place. CloudWatch Logs gains the modern TagResource/UntagResource/ListTagsForResource trio for log groups, resolving a resource ARN to the log group name and sharing the existing TagLogGroup/UntagLogGroup/ListTagsLogGroup spelling’s validation and storage rather than duplicating it

    backup cloudwatch-logs
  • a Lockfile freshness check fails a pull request whose web/pnpm-lock.yaml was generated against a main that has since changed its own copy, and a sweep on every lockfile push to main re-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

    ci
  • ContinueUpdateRollback is now supported, so a stack in UPDATE_ROLLBACK_FAILED is 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 later UpdateStack, ExecuteChangeSet and change set has nowhere to start from; previously the operation returned 501, and the only way forward was delete-stack and 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 reaches UPDATE_ROLLBACK_COMPLETE and is updatable again. A retry that meets the same blocker lands back in UPDATE_ROLLBACK_FAILED and can be tried again. Nested stacks are continued before their parent, so a wedged child is recovered rather than reported rolled back from above. ResourcesToSkip is honoured for a resource that cannot be cleaned up at all, AWS’s NestedStackName.ResourceLogicalID form included: the resource is left physically untouched, reported UPDATE_COMPLETE, and the rest of the stack rolls back around it. Any other stack status gets AWS’s Stack:<arn> is in <status> state and can not be updated., and every ResourcesToSkip member is validated before the operation is accepted

    cloudformation
  • AWS::CloudTrail::Trail, AWS::Transfer::Server, AWS::Transfer::User, AWS::IAM::ManagedPolicy and AWS::IAM::InstanceProfile now 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
  • a deployed stack now says which resources are stubs or backed by an inert/stub-tier service, on the same ResourceStatusReason CloudFormation already exposes and in the console’s stack view, instead of only a server log line nobody sees

    cloudformation web
  • CloudWatch answers Smithy RPC v2 CBOR.

    • The pinned model declares rpcv2Cbor for 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 answered 501 Not Implemented over POST /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, and PutMetricAlarm still 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

    cloudwatch
  • LambdaConfig triggers (PreSignUp, PostConfirmation, PreTokenGeneration, PostAuthentication, CustomMessage) are now invoked during SignUp, ConfirmSignUp, AdminCreateUser, AdminConfirmSignUp, ResendConfirmationCode, ForgotPassword, ConfirmForgotPassword, InitiateAuth, AdminInitiateAuth and RespondToAuthChallenge on the classic X-Amz-Target API, 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 with UserLambdaValidationException instead of being silently skipped, except PostConfirmation, which AWS documents as fire-and-forget since the account is already confirmed by the time it runs. CUSTOM_AUTH’s DefineAuthChallenge/CreateAuthChallenge/VerifyAuthChallengeResponse remain tracked separately (#88, #94, #101); PreAuthentication, UserMigration and the Smithy RPC v2 duplicate dispatch path’s non-token triggers are not yet wired

    cognito
  • four new registry groups.

    • rds-clusters covers the Aurora DB cluster lifecycle — create, describe, modify, stop, start, delete — and rds-cluster-members covers a cluster with an instance in it: DBClusterMembers, writer election, the settings a member inherits from its cluster, and the membership pruning DeleteDBInstance does. 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) and lambda-invoke-error (a handler that throws must come back as HTTP 200 with FunctionError set) are implemented in all seven SDK/CLI suites; eventbridge-patterns is the dotnet-sdk and rust-sdk suites’ first EventBridge coverage

    compat
  • the startup log names the resolved bind address(es), says that OVERCAST_LISTEN is 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 *.localhost the way Linux does) that otherwise surfaces far from its cause:

    • virtual-hosted-style S3 addressing breaks silently, and cdk deploy asset publishing fails with a bare getaddrinfo ENOTFOUND on 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_HOST maps to OVERCAST_HOSTNAME — LocalStack’s documented hostname[:port] format is parsed and a port part must match OVERCAST_PORT — and EDGE_PORT, GATEWAY_LISTEN, DEFAULT_REGION, DATA_DIR, DEBUG, PERSISTENCE, HOSTNAME_EXTERNAL and LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT map to the matching OVERCAST_*/LAMBDA_* setting, completing the drop-in-replacement audit. SERVICES, LOCALSTACK_API_KEY and LOCALSTACK_AUTH_TOKEN are 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 explicit OVERCAST_* setting fails startup naming both, rather than silently preferring one. docs/migration-from-localstack.md records 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

    config
  • 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

    devex
  • a service’s deploymentConfiguration now decides how many tasks its rollout runs at once.

    • maximumPercent and minimumHealthyPercent were stored and echoed back by DescribeServices but 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 a hostPort publishes it on the one host, so while both deployments are alive they contend for it and the replacement cannot start; on AWS each awsvpc task has its own ENI and two tasks never collide. The settings now resolve against desiredCount as AWS resolves them — maximumPercent a ceiling on tasks that have not stopped, across both deployments, rounded down; minimumHealthyPercent a floor on tasks reporting RUNNING, rounded up — and the superseded tasks are retired before the replacements are placed when the floor allows it. maximumPercent: 100 with minimumHealthyPercent: 0 (CDK’s maxHealthyPercent/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’s 200/100, so a service that sets neither deploys exactly as before. 100/100 cannot 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

    ecs
  • TestEventPattern is implemented.

    • It evaluates an event against a pattern with the same matcher PutEvents uses to select rules, so a pattern that passes here is one a rule would fire on; an unparseable pattern is the documented InvalidEventPatternException rather than a silent Result: false. It used to return 501

    eventbridge
  • state changes now publish AWS-shaped events onto the default bus, so a rule matching one actually fires.

    • EC2 Instance State-change Notification comes from RunInstances, StartInstances, StopInstances, TerminateInstances and the scheduler callbacks that fast-forward pending/stopping; ECS Task State Change from RunTask, StopTask and container-exit-driven stops; and Step Functions Execution Status Change from a standard workflow’s initial RUNNING record, its SUCCEEDED/FAILED/TIMED_OUT/ABORTED terminal write, and StopExecution’s direct ABORTED write — EXPRESS workflows are excluded, matching AWS

    eventbridge ec2 ecs stepfunctions
  • internal/inert/conformance turns 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

    inert
  • extensions now start for container-image functions too.

    • External extensions in /opt/extensions used 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 as extension records without the [overcast-extension:<name>] prefix Overcast used to write into the line

    lambda
  • the platform.initStart, platform.initRuntimeDone and platform.initReport Telemetry 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 first GET /next — and reports them on the same sequence-ordered stream as the container’s output. Under LogFormat: JSON they reach CloudWatch in the position AWS puts them in: initStart → the INIT phase’s own output → initRuntimeDone → initReport → START, filtered by SystemLogLevel at AWS’s documented levels (all three are DEBUG for a successful on-demand cold start, INFO for the report of a provisioned environment, WARN when 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.

    lambda
  • 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, Throttles and ConcurrentExecutions once per invocation at the shared outcome boundary every path — sync, async, function URLs, event source mappings — already funnels through, plus the asynchronous set: AsyncEventsReceived for every accepted Event invocation, AsyncEventAge per retry attempt, AsyncEventsDropped when an event outlives its MaximumEventAgeInSeconds, and DeadLetterErrors/DestinationDeliveryFailures when a failed invocation’s dead-letter or destination delivery itself fails. SQS records NumberOfMessagesSent/NumberOfMessagesReceived/NumberOfMessagesDeleted/NumberOfEmptyReceives/SentMessageSize per operation, plus a once-a-minute gauge sampler for ApproximateNumberOfMessagesVisible/NotVisible/Delayed and ApproximateAgeOfOldestMessage published for every active queue even with no traffic, as AWS does. SNS records NumberOfMessagesPublished/PublishSize on Publish/PublishBatch and NumberOfNotificationsDelivered/NumberOfNotificationsFailed per subscription delivery attempt. DynamoDB records SuccessfulRequestLatency, ConsumedReadCapacityUnits/ConsumedWriteCapacityUnits (with GlobalSecondaryIndexName where applicable) and UserErrors/SystemErrors across PutItem/GetItem/DeleteItem/UpdateItem/BatchGetItem/BatchWriteItem/Query/Scan/TransactWriteItems/TransactGetItems. API Gateway records Count/4XXError/5XXError/Latency/IntegrationLatency once 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, so GetMetricStatistics, GetMetricData and ListMetrics return the automatic series alongside custom PutMetricData data. 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 on OVERCAST_STATE=wal and every -tags nosqlite build, the one gap every earlier phase disclosed (OVERCAST_STATE=memory is unaffected: there is nothing for it to restart into). OVERCAST_SERVICE_METRICS=auto|enabled|disabled controls collection, default auto

    metrics lambda sqs sns dynamodb apigateway cloudwatch
  • policies are stored for real — CreatePolicy, DescribePolicy, UpdatePolicy, DeletePolicy and ListPolicies, with TagResource, UntagResource and ListTagsForResource over the same tags.

    • A policy’s ID and ARN are derived from its name and stable across restarts, ListPolicies filters by the required type and rejects an invalid NextToken rather than restarting at page one, and the modeled errors are returned by name. Nothing a policy describes takes effect: AttachPolicy still returns 501, so no policy is ever attached or enforced. Tagging accepts policy IDs only — a root, OU or account ID returns TargetNotFoundException, since none of those are stored yet

    organizations
  • 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. See docs/services/route53.md for the full behaviour and documented divergences

    route53
  • 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/QueryListState also gained isFiltered/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 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/

    web lambda sqs sns dynamodb
Changed 15
  • AWS::IAM::Role and AWS::IAM::User now also merge the stack’s own tags, instead of only their own resource-level Tags

    cloudformation
  • scripts/docker-go.sh, scripts/docker-go.ps1 and scripts/go.sh’s Docker fallback take their default image from .devcontainer/Dockerfile’s FROM line (golang:1.26-bookworm today) instead of a hardcoded golang:1.24-bookworm, which had stopped working once go.mod moved to Go 1.25 (go: go.mod requires go >= 1.25.0 (running go 1.24.13; GOTOOLCHAIN=local)); OVERCAST_GO_IMAGE still overrides

    dev
  • the Tier 0 / Tier 1 (inert) / Tier 2 operation-level vocabulary is documented in CONTRIBUTING.md alongside 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

    docs
  • 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 four Authorize/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/DeleteSecurityGroup run their dependency checks on both dispatch paths, so a DependencyViolation answer 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

    ec2
  • a task’s awslogs output 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

    ecs
  • a function’s X-Amz-Log-Result tail 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 on 127.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 logs on 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 container AWS_LAMBDA_RUNTIME_API now carries the AWS value 127.0.0.1:9001, which is the init’s proxy; the per-execution-environment endpoint on the host is passed separately as OVERCAST_RUNTIME_API and is read only by the init. Nothing outside the container changes: the Runtime API, the extensions API, the Telemetry/Logs API, LAMBDA_RUNTIME_API_PORT and 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 object record — instead of a "platform"-typed event whose record was the log line as a string. CloudWatch output and X-Amz-Log-Result tails are unchanged in both log formats.

    lambda
  • cleartext HTTP/2 (h2c) is served through the standard library’s http.Server.Protocols instead of the deprecated x/net/http2/h2c wrapper; prior-knowledge HTTP/2 and HTTP/1.1 behave as before, and the rarely used Upgrade: h2c handshake now continues on HTTP/1.1

    server
  • the console’s server-side TypeScript types are generated from the Go response structs by cmd/tsgen into web/src/types/api.gen.ts, and make check-ts fails CI when the two drift — a Go field rename can no longer ship a console reading undefined.

    • 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/Hop now marshal through package-level wire structs that cmd/tsgen renders — the generator will not render a type that has its own MarshalJSON, so a struct can no longer be described as a shape it never sends

    web ci
  • 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
  • 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 iam
  • 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’s q — 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 onto ResourceTable is 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

    web
Fixed 63
  • 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

    appsync
  • create-time tags are no longer dropped.

    • Ten services accepted a Tags member 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 no UserPoolTags member at all), CreateCluster/CreateService (ECS), CreateReplicationGroup (ElastiCache), CreateEventBus/PutRule (EventBridge, where PutRule merges with whatever the rule already carries, as AWS documents), CreateDeliveryStream (Firehose), CreateDBInstance/CreateDBCluster/CreateDBSubnetGroup (RDS), PutParameter (SSM) and CreateStateMachine (Step Functions, whose request modeled no tags member either). EC2’s TagSpecification.N was honoured only by RunInstances, CreateNatGateway and CreateVpnGateway, and now also by CreateVpc, CreateSubnet, CreateSecurityGroup, CreateInternetGateway, CreateRouteTable, CreateVpcEndpoint, CreateVpcPeeringConnection, CreateNetworkInterface and CreateKeyPair — 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, so ListTagsForResource sees them without a follow-up call; SSM applies them only when the call creates a new parameter, matching AWS’s rule that Tags cannot update an existing one. ECS’s RegisterTaskDefinition and ElastiCache’s CreateCacheCluster/CreateServerlessCache already 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

    athena cognito ec2 ecs elasticache eventbridge firehose rds ssm stepfunctions
  • API Gateway templates now get the stage variables, authorizers and models they declare.

    • AWS::ApiGateway::Stage’s Variables and AWS::ApiGatewayV2::Stage’s StageVariables used to be parsed into nothing on create — the service already read stage.Variables into a Lambda proxy integration’s stageVariables at 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’s AuthorizerId and AWS::ApiGatewayV2::Route’s AuthorizerId round-trip the same way — a method or route wired to a CfnAuthorizer no longer loses the association. Method.RequestParameters and MethodResponses (via PutMethodResponse) are forwarded on create, and AWS::ApiGateway::RestApi’s Policy, Tags, BinaryMediaTypes and DisableExecuteApiEndpoint round-trip on create (the two scalars also on update) — GetRestApi previously never surfaced Policy back either, even though CreateRestApi already stored it. AWS::ApiGateway::Authorizer, AWS::ApiGateway::Model and AWS::ApiGateway::RequestValidator are 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/BodyS3Location and ApiGatewayV2::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 equivalent ApiGatewayV2::Integration/Route/Stage/Api properties — 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 persisted UPDATE_FAILED stack 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 the DisableRollback failure 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 reason AWS::CloudFormation::Init and EC2 UserData carry the restriction:

    • a custom resource’s Lambda may log its own event payload. A plain {{resolve:ssm:...}} reference still resolves. OldResourceProperties is now actually threaded through to the Lambda (it was previously discarded and effectively sent empty), in the same form as ResourceProperties, 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_COMPLETE and the stack reached ROLLBACK_COMPLETE with the resource still standing; they now report the failure, and the stack reaches ROLLBACK_FAILED with 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 let DeleteStack stop swallowing genuine delete failures: a resource still standing leaves the stack DELETE_FAILED with 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’s DependencyViolation routes through the same deletion-blocked sentinel as IAM’s DeleteConflict, RDS’s deletion protection and a non-empty S3 bucket — DeleteInternetGateway, the main-table case of DeleteRouteTable and DeleteVpnGateway all 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, and RetainExceptOnCreate is honoured on CreateStack, UpdateStack and ExecuteChangeSet rather than only on RollbackStack.

    • 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. RetainExceptOnCreate on the operation opts back into deleting a retained resource, so a create rollback that asks for it deletes what create made — including resources marked DeletionPolicy: Retain — instead of orphaning them for the next deploy to collide with

    cloudformation
  • ELBv2 templates now get the target-group health check, TargetType and Scheme they declare.

    • AWS::ElasticLoadBalancingV2::TargetGroup’s TargetType was stored and echoed by the elbv2 service (instance vs ip vs lambda matters for the Fargate path specifically, since an awsvpc service registers ip targets) but CreateTargetGroup’s Query request never carried it, so every target group came back as the instance default regardless of what the template asked for. The whole HealthCheck* family (Protocol, Port, Path, Enabled, IntervalSeconds, TimeoutSeconds, HealthyThresholdCount, UnhealthyThresholdCount), Matcher, ProtocolVersion and IpAddressType had the same gap — parsed out of the template and dropped before the request left the provisioner. All of it now reaches CreateTargetGroup, defaults the way the real API documents when the template leaves it unset, and reconciles through a new ModifyTargetGroup on update; TargetType, Protocol, Port and VpcId still force replacement, matching AWS. TargetGroupAttributes (e.g. deregistration_delay.timeout_seconds) reaches a target group through two new operations, ModifyTargetGroupAttributes and DescribeTargetGroupAttributes — the real CreateTargetGroup has no field for attributes either, so the provisioner makes the follow-up call CDK’s synthesized template implies. AWS::ElasticLoadBalancingV2::LoadBalancer’s Scheme had the matching gap: stored and echoed by the service, never sent by CreateLoadBalancer, so internal vs internet-facing never round-tripped from a template; IpAddressType had the same fix. Tags on both CreateTargetGroup and CreateLoadBalancer are threaded directly, as the real API accepts them inline on create. AWS::ElasticLoadBalancingV2::Listener’s DefaultActions now forwards each action’s RedirectConfig and FixedResponseConfig wholesale instead of only Type and TargetGroupArn — 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: DescribeTargetHealth still always reports “healthy”. Remaining gaps — weighted ForwardConfig, Certificates/SslPolicy/AlpnPolicy/MutualAuthentication, the Cognito/OIDC auth actions, SubnetMappings, and AWS::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::Role used to send exactly three parameters to CreateRole and drop everything else, so a CDK role built with addToPolicy(...) or addManagedPolicy(...) provisioned successfully with no permissions at all. The role handler now dispatches AttachRolePolicy per ManagedPolicyArns entry and PutRolePolicy per Policies entry, passes Path, Description, MaxSessionDuration, PermissionsBoundary and Tags through to CreateRole, 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::User gains the same treatment for Groups, ManagedPolicyArns, Policies, PermissionsBoundary and Tags; AWS::IAM::ManagedPolicy honours its Description and attaches to the principals its Roles/Users/Groups lists name; AWS::IAM::Policy writes its document onto groups and users on create, not just roles; and AWS::IAM::Group is 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 enforces DeleteConflict — out-of-band relationships are never swept, and a refused delete restores the detachments it made. A LoginProfile fails the resource loudly instead of being silently dropped, since IAM does not emulate login profiles

    cloudformation
  • Ref of a CommaDelimitedList (or List<...>) 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 a CommaDelimitedList whose Ref feeds the toolkit role’s ManagedPolicyArns directly

    cloudformation
  • AWS::DynamoDB::Table now applies Tags on create and reconciles additions, changes and removals through TagResource/UntagResource on update, and reconciles GlobalSecondaryIndexes add, remove and throughput changes through GlobalSecondaryIndexUpdates on update instead of forwarding indexes at create only.

    • AWS::DynamoDB::GlobalTable is no longer a no-op stub: Overcast emulates a single region, so it provisions the Replicas entry naming the deploying stack’s own region as a real table, forcing NEW_AND_OLD_IMAGES streaming and defaulting to on-demand billing, and fails the stack loudly rather than completing silently when no Replicas entry names that region or PROVISIONED billing is requested

    cloudformation
  • AWS::Kinesis::Stream no longer drops RetentionPeriodHours, Tags, StreamEncryption or StreamModeDetails.

    • RetentionPeriodHours threads through IncreaseStreamRetentionPeriod/DecreaseStreamRetentionPeriod on create and reconciles on update, so a CDK stream with retentionPeriod: Duration.days(7) no longer reports Kinesis’ 24-hour default; Tags reconcile through AddTagsToStream/RemoveTagsFromStream; and StreamModeDetails and StreamEncryption are new Kinesis fields, threaded at create, reconciled through the new UpdateStreamMode/StartStreamEncryption/StopStreamEncryption operations, and round-tripping through DescribeStream/DescribeStreamSummary. ShardCount changes still force replacement, since UpdateShardCount is 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::Parameter no longer drops Description, Tags, Tier, DataType, AllowedPattern or Policies between CDK and the emulator.

    • The handler’s create and update used to send only Name, Type and Value to PutParameter; they now thread every scalar through and reconcile Tags through SSM’s own AddTagsToResource/RemoveTagsFromResource on create and update. Tier, DataType and Policies were dropped on the SSM service side too: PutParameter now stores them and GetParameter/DescribeParameters/GetParameterHistory echo the stored value instead of a hardcoded Standard/text/[]

    cloudformation
  • SQS queue templates now get their Tags and RedriveAllowPolicy.

    • The queue-attribute mapping forwarded twelve queue attributes and RedrivePolicy but silently dropped both — the service already supported each (TagQueue/ListQueueTags/UntagQueue, and RedriveAllowPolicy as a validated queue attribute), CloudFormation just never called them. Tags now reaches CreateQueue on create and is reconciled through TagQueue/UntagQueue on update — added, changed and removed keys, plus stack-level propagated tags, the same as the Lambda, LogGroup and Secrets Manager handlers. RedriveAllowPolicy is forwarded alongside RedrivePolicy on create and update and round-trips through GetQueueAttributes; its redrivePermission restriction is validated for shape but, as before, not enforced against StartMessageMoveTask or automatic DLQ redrive. AWS::SQS::QueuePolicy remains 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::Schedule templates no longer lose Description, ScheduleExpressionTimezone, StartDate, EndDate or KmsKeyArn.

    • CreateSchedule sent Name, GroupName, ScheduleExpression, State, FlexibleTimeWindow and Target while the scheduler service already stored and returned every one of the five — ScheduleExpressionTimezone and StartDate/EndDate are 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 calls UpdateSchedule in place, and only Name/GroupName changes still replace, matching the resource’s create-only properties on real AWS. AWS::Scheduler::ScheduleGroup (Name, Tags) had no gap

    cloudformation
  • AWS::Logs::LogGroup’s RetentionInDays is coerced from a JSON string to a number before it reaches the Logs service, matching real CloudFormation’s own type coercion.

    • A String-typed Ref or Parameter renders the property as "7" rather than 7 — 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 both CreateStack and an in-place UpdateStack. A value that still is not a valid retention number, string or not, continues to fail with the Logs service’s own InvalidParameterException rather than a decode error

    cloudformation
  • AWS::EC2::VPC’s EnableDnsSupport/EnableDnsHostnames and AWS::EC2::Subnet’s MapPublicIpOnLaunch are threaded through to the emulator instead of being parsed into nothing.

    • A CDK Vpc construct sets both DNS attributes on every VPC it synthesizes, and MapPublicIpOnLaunch on every public subnet — none of the three reached the emulator’s ModifyVpcAttribute/ModifySubnetAttribute calls, and even a direct call was metadata-only: DescribeVpcAttribute always answered true regardless of what was set, and DescribeSubnets always answered mapPublicIpOnLaunch=false. All three are now real store fields: CreateVpc defaults to EnableDnsSupport=true/EnableDnsHostnames=false (matching AWS), the seeded default VPC and its default subnets get the account-default-VPC values (both DNS attributes and MapPublicIpOnLaunch all true), ModifyVpcAttribute/ModifySubnetAttribute persist whatever is set, and DescribeVpcAttribute/DescribeSubnets report the stored value. The provisioner issues the follow-up Modify*Attribute call on create, and both AWS::EC2::VPC and AWS::EC2::Subnet gained update support so a template that only changes these properties (or tags) updates in place instead of replacing the resource — CidrBlock/InstanceTenancy on a VPC and VpcId/CidrBlock/AvailabilityZone on a subnet still force replacement, matching real CloudFormation. AWS::EC2::EIP also stops hardcoding Domain: "vpc" and reads it (and Tags) from the template; DescribeAddresses now returns the tags it is given. What reads these values downstream — ECS/RDS/Lambda-VPC routing behaviour keyed off EnableDnsHostnames and 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’s Ipv6CidrBlock/AssignIpv6AddressOnCreation/EnableDns64/PrivateDnsNameOptionsOnLaunch/OutpostArn, AWS::EC2::VPC’s Ipv4IpamPoolId/Ipv4NetmaskLength, AWS::EC2::NatGateway’s ConnectivityType/PrivateIpAddress, and AWS::EC2::Route’s IPv6/ENI/TGW/peering/carrier-gateway destinations

    cloudformation
  • ElastiCache replication groups now get the engine they declared.

    • AWS::ElastiCache::ReplicationGroup’s CreateReplicationGroup request dropped Engine and EngineVersion entirely — the service already read both and already treated an Engine change as replacement on update, but create never sent them, so the engine image fell through to its redis:7 default 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. SnapshotRetentionLimit and PrimaryClusterId are forwarded on create too, and DescribeReplicationGroups now echoes Engine, matching AWS. AWS::ElastiCache::CacheCluster’s PreferredAvailabilityZone and CacheParameterGroupName — both already stored fields — and inline Tags now 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
  • a filter value outside the enum AWS models is now rejected instead of silently matching nothing.

    • ListStacks rejects a StackStatusFilter value outside AWS’s full StackStatus enum with ValidationError — the complete 23-value enum is modelled, including the IMPORT_* states Overcast itself never produces, so a caller legitimately filtering on one of those is still accepted. ListSecrets and BatchGetSecretValue reject a Filters entry whose Key is outside AWS’s enum with InvalidParameterException, and ListExecutions rejects a statusFilter value outside the ExecutionStatus enum with ValidationException — each of them one of the operation’s own declared errors

    cloudformation secretsmanager stepfunctions
  • a stack-tag-only update now reconciles the tags it should.

    • AWS::DynamoDB::Table, AWS::SSM::Parameter and AWS::StepFunctions::StateMachine left 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::Server no longer forces replacement when only its Tags change either; it reconciles them through TagResource/UntagResource in 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, ReadAttributes and WriteAttributes.

    • The user pool handler forwarded only a core subset of AWS::Cognito::UserPool properties 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 through UpdateUserPool/UpdateUserPoolClient, replacing the pool only when AliasAttributes or UsernameAttributes change, since neither is a member of real Cognito’s UpdateUserPoolRequest. LambdaConfig round-trips through DescribeUserPool, so a pool’s configured triggers are visible — and, as of this release, invoked

    cloudformation
  • AWS::KMS::Key stack deletion carries the template’s PendingWindowInDays through to ScheduleKeyDeletion instead of the hardcoded 7 days it used before, falling back to KMS’s own documented 30-day default when the property is absent.

    • Tags round-trips too: CreateKey accepts and stores them, merged with any stack-level tags, where it previously dropped the property entirely, and update reconciles changes through TagResource/UntagResource, the same add/remove diff every other tag-aware handler here uses. KeyPolicy and Enabled were already threaded on create; AWS::KMS::Alias has no properties beyond AliasName/TargetKeyId and had no gap

    cloudformation
  • AWS::StepFunctions::StateMachine templates no longer lose DefinitionSubstitutions, LoggingConfiguration, TracingConfiguration or Tags.

    • The handler’s create sent only name, definition, roleArn and type, so a CDK state machine built from DefinitionBody.fromString/fromFile with definitionSubstitutions provisioned 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. DefinitionSubstitutions is 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. LoggingConfiguration and TracingConfiguration are threaded on create as well as update, and Tags are applied through TagResource on create and reconciled on update. A StateMachineType change on update was silently ignored instead of forcing replacement — AWS documents it Update requires: Replacement and does not support changing it in place — so it now returns the same replacement-required path StateMachineName already took

    cloudformation
  • GetMetricData now 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 got UnknownOperationException

    cloudwatch
  • 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 RunTask and 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

    docker ecs
  • 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.md lists 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 the OVERCAST_MCP_REMOTE_EXPOSURE/OVERCAST_MCP_AUTH_TOKEN remote-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 403 InvalidSignatureException, 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-dev test fallback), and validation behaviour itself is unchanged. docs/operation-manifest.md no 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, and OVERCAST_UI_PORT=0 / --ui-port 0 is how to turn it off. A code comment that wrongly equated OVERCAST_HOST with LocalStack’s LOCALSTACK_HOST is corrected: the true analogue is OVERCAST_HOSTNAME

    docs
  • table responses carry the identity and the shape AWS documents.

    • CreateTable, DescribeTable, UpdateTable and DeleteTable now include TableDescription/Table.TableId — a UUID minted once at CreateTable time 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’s Fn::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 under TableDescription, with TableStatus reported as DELETING, matching the other three and AWS’s documented shape; it previously reused the DescribeTable-shaped Table wrapper, which strict SDK decoders would silently drop since the member name did not match

    dynamodb
  • 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, and AllocateAddress’s typed body no longer ignores Domain and always allocates a vpc-domain address — found by the new differential test rather than by a caller, since the typed path had never been reachable

    ec2
  • CreateSecurityGroup returns the tagSet its 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 to groupId, and Overcast omitted it, so a caller that tagged a group at create had to issue a DescribeSecurityGroups to read back what it had just set.

    ec2
  • AllocateAddress honours create-time TagSpecification.N on both dispatch paths.

    • Neither ever parsed it — the call succeeded and the Elastic IP came back untagged, invisible to DescribeTags and to the tagSet DescribeAddresses renders — and a tag the model refuses, such as a reserved aws: key, now fails the allocation instead of allocating and dropping the tag. Per the AWS model, AllocateAddressResult itself carries no tag set; the tags surface through the describes.

    ec2
  • CreateRepository now marks its response with x-overcast-emulation-limitation whenever the repositoryUri it 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 a Warn log line only — CreateRepository still returned 200 with a repositoryUri that a later docker push answers with 405 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’s ResourceStatusReason, next to the AWS::ECR::Repository it is about. Any typed operation’s response type can now opt into this channel without new plumbing per service

    ecr
  • containers in an awsvpc task now share one network namespace, so 127.0.0.1 reaches 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 address DescribeTasks reports is the one every container answers on

    ecs
  • StopTask over 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 as EssentialContainerExited instead of UserInitiated, 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

    ecs
  • UpdateRole is now supported (Description, MaxSessionDuration; an empty description clears it, an omitted one is left alone), CreateRole/GetRole round-trip Description and MaxSessionDuration, CreatePolicy/GetPolicy round-trip Description, and CreatePolicyVersion with SetAsDefault=true replaces the operative document and bumps DefaultVersionId — superseded versions are not retained

    iam
  • a hook script’s timeout on Windows now kills its whole process tree, not just the shell.

    • TerminateProcess previously reached only the shell, so a backgrounded descendant (sleep 5 &, say) was orphaned and kept running, still holding whatever handle it had inherited — including, under go 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 with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE

    inithooks
  • a LogType: Tail invocation 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
  • 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 print regularly lost to a sys.stderr write that landed first, and the line went missing from both CloudWatch Logs and the X-Amz-Log-Result tail. 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’s awslogs output 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 ecs
  • 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 with batchItemFailures has 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-string itemIdentifier, 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 pipes
  • 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 be Handled unless the runtime reported Runtime.ExitError — a value no modern runtime produces for a thrown error, and one that broke SDK callers branching on the documented value. The new compat InvokeWithError test 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 create and its first GET /next belonged to nothing the pool could see — not the warm set, not the reservation, not the instance tracker — so DeleteFunction walked 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.js in place left the identity untouched, the warm container was reused, and Node’s require cache (or Python’s sys.modules) kept serving the module that container had loaded. Saving under a new name, deleting a file, or cdk watch all 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 path UpdateFunctionCode uses 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_cache and .pytest_cache are 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.

    lambda
  • 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 once Stop has run, so a still-running health check stops rescheduling instead of racing shutdown; EKS’s CreateCluster live-mode bootstrap called liveWg.Add(1) without first checking the stopping flag under its lifecycle mutex, so a create racing Stop could leave a k3s container starting after shutdown, and a create that lands after Stop has begun is now refused, leaving the cluster record FAILED with a reason instead of a half-started container; and Step Functions’ startExecution — the single choke point StartExecution, StartSyncExecution and a nested states:startExecution all funnel through — registered its run and called wg.Add with nothing synchronising either against Stop’s wg.Wait, so a stopping flag guarded by the same lock now fences the reservation: Stop sets it before it ever waits, and an execution arriving concurrently either reserves its slot strictly before that happens or gets a defined ServiceUnavailableException with no goroutine launched and no wg touched, its already-persisted RUNNING record unwound to ABORTED rather than left stuck. All three could surface under -race on unrelated PRs

    lifecycle eks stepfunctions
  • a Kinesis source’s MaximumRetryAttempts and DeadLetterConfig are 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

    pipes
  • a request member typed as an open document (CloudWatch’s PutMetricAlarm Metrics and EvaluationCriteria are 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 bare 500 InternalError on 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

    protocol
  • Aurora cluster endpoints now resolve from other containers, and carry a port the caller can dial.

    • Endpoint and ReaderEndpoint were 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 reading cluster.clusterEndpoint.hostname from 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, and DescribeDBClusters renders the address and port from that instance on the same per-caller rules DescribeDBInstances has 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.

    • DBClusterMembers was only ever appended to, so DeleteDBInstance dropped the instance record and left the membership entry behind: DescribeDBClusters went 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
  • StopDBCluster and StartDBCluster answer in their own response envelopes instead of CreateDBClusterResponse, so an AWS SDK can deserialize them. The state change was always correct, so nothing that read the stored record noticed

    rds
  • a /tags/{resourceArn} request whose ARN belongs to a service whose tag operations are not implemented answers the generated 501 with the x-emulator-unsupported marker.

    • API Gateway’s ARN-keyed tag store was the dispatcher’s fallback for every ARN no other service claimed, so aws backup list-tags read 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’s servicecatalog ones, whose SDK shares the endpoint

    router backup apigateway
  • 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 — InvalidSignatureException naming 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 nonsensical NoSuchBucket/NoSuchKey. Unsigned traffic, S3’s own traffic, and a scope that is not a real AWS signing name are unaffected

    router
  • S3 bucket notifications now deliver to SNS topics.

    • TopicConfigurations was 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’s Message string with subject “Amazon S3 Notification”, so a queue subscribed to the topic receives the standard SNS envelope whose Message parses back into the S3 event

    s3 sns
  • KmsKeyArn is now accepted on CreateSchedule/UpdateSchedule and round-trips through GetSchedule, matching FlexibleTimeWindow and ScheduleExpressionTimezone: stored and returned, but nothing is actually encrypted with it

    scheduler
  • Publish matches AWS’s documented behaviour on several fronts reviewed against the API reference.

    • It accepts exactly one of TopicArn, PhoneNumber or TargetArn as the destination, where TopicArn was previously required even for a PhoneNumber/TargetArn call; PhoneNumber delivers directly to the Inbox as SMS with no topic involved; TargetArn (mobile platform endpoints) returns an explicit 400 InvalidParameter in place of a misleading “missing TopicArn” error; MessageStructure=json is implemented, selecting each subscriber’s own protocol-keyed payload (falling back to default) for the envelope, raw delivery, email and SMS bodies alike; Message (256 KB) and Subject (under 100 characters, no line breaks or control characters) are validated on both Publish and each PublishBatch entry; and a topic whose name ends in .fifo gets its MessageGroupId/MessageDeduplicationId (or ContentBasedDeduplication) parameters checked on Publish — a call that was never valid against real AWS now fails the same way, while full FIFO ordering, deduplication and SequenceNumber generation 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: NumberOfNotificationsFailed is recorded, the subscription’s dead-letter queue receives it when a RedrivePolicy is set, and a one-time warning per topic and protocol is logged naming the missing wiring

    sns
  • DeleteQueue, PurgeQueue, SetQueueAttributes, TagQueue and UntagQueue no 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

    sqs
  • HybridStore.Set/Delete/DeletePrefix no 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

    state
  • UpdateStateMachine is implemented — the CloudFormation update path called it unconditionally whenever a mutable property was present, which is effectively always since DefinitionString is rarely omitted, so every in-place stack update to a state machine’s mutable properties was quietly falling back to full replacement (AWSStepFunctions.UpdateStateMachine had no handler). DescribeStateMachine now echoes loggingConfiguration/tracingConfiguration, defaulting to AWS’s documented OFF/disabled shape when unset, on both the legacy JSON and typed CBOR paths

    stepfunctions
  • AssumeRole and AssumeRoleWithWebIdentity no longer repeat RoleSessionName in both segments of AssumedRoleUser.Arn. The first segment is now the role name parsed out of the request’s RoleArn, matching AWS’s arn:aws:sts::<account>:assumed-role/<RoleName>/<SessionName> shape — fixed identically on the legacy Query XML and the typed JSON/CBOR paths

    sts
  • 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

    web
Removed 2
  • the dead Node BFF — the standalone entry point for the dev Hono server (web/api/src/node.ts), the @overcast/api package manifest around it (web/api/package.json, its own pnpm-lock.yaml, the dev/build/start scripts 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/src is typechecked, tested and resolved from web/ as plain source files

    web ci
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.37

View on GitHub
v0.0.1-alpha.36 prerelease 11 assets
6 added · 3 changed · 54 fixed · 1 removed · 17 breaking mcp web tls events ec2 +23 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.36

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.36

Downloads

Changelog

6 added · 3 changed · 54 fixed · 1 removed · 17 breaking

Breaking changes

  • Changed

    the log level is stated per request instead of set once for a connection, and a request that names none is sent no notifications/message at all.

    • logging/setLevel set a threshold that every client on the server then shared, which MCP revision 2026-07-28 replaces with a level named in each request’s _meta — so one client can ask for debug without making another listen to it. Silence is the default rather than a threshold nobody chose: the revision requires that a server not emit log notifications for a request that did not ask for them. Deciding this before the notification is built also means an emit with no audience costs nothing, which matters because these fire from the emulator’s own paths whether or not MCP is in use

    Migration

    put io.modelcontextprotocol/logLevel in a request’s _meta to receive notifications/message for it; requests that omit it are answered without any

    mcp
  • Changed

    every EC2 describe now refuses a filter name it does not implement, with AWS’s InvalidParameterValue: The filter '<name>' is invalid.

    • It used to answer one of two ways, and which one depended on which helper the handler happened to be written with: the parseFilterValues describes (VPCs, subnets, security groups, route tables, internet gateways, VPC endpoints, peering connections) looked each filter up by name, never saw the rest, and returned everything; the matchFilters ones (NAT gateways, network interfaces) compared every supplied filter against an attribute map and returned nothing. Both are confidently wrong, in opposite directions, in one service, so a caller could not even work around them consistently. This is the root of the tag:Name bug fixed in #1033 — a find-or-create script’s describe-vpcs --filters Name=tag:Name,... was ignored, so Vpcs[0] was the seeded default VPC and the script adopted it instead of creating its own private one; implementing that one filter fixed that one filter, while the next unimplemented name failed in exactly the same silent way. Which names each operation implements is now declared in one place per operation, and that declaration is what matches the filter, writes the error and is checked against the capability tables — which the same change makes accurate, several having claimed filters that were never applied. The set is also wider than it was: DescribeAddresses, DescribeImages, DescribeKeyPairs, DescribeRegions, DescribeAvailabilityZones, DescribeInstanceTypes and DescribeTags implement filters they previously accepted and ignored, and DescribeInstances gained instance-id, which its capability note had claimed for some time. CDK’s VPC context provider sends only names Overcast implements, on all four describes it calls, so Vpc.fromLookup and cdk deploy are unaffected

    Migration

    a call passing a filter Overcast does not implement now fails instead of returning a wrong answer quietly. The error names every filter that operation does support, so the fix is usually to drop or rename the filter; docs/services/ec2.md lists the same sets. This is about filter names only: filter values gained AWS’s * and ? wildcards later in this same release, so a MachineImage.lookup whose name filter is a wildcard resolves rather than returning nothing — see the entry below

    ec2
  • Changed

    a filter name is matched exactly, as AWS matches it.

    • Name=VPC-ID and Name=isdefault are refused with the InvalidParameterValue any unrecognised name gets, where the pre-#1038 parseFilterValues describes folded case and accepted them. The other two filter idioms it replaced were already case-sensitive, so this settles a split rather than tightening a uniform behaviour, and it is what real EC2 does

    Migration

    spell the filter name the way the AWS docs spell it — lower-case and hyphenated for all but isDefault, and the error message names every spelling the operation accepts

    ec2
  • Changed

    DescribeTags refuses a filter name it does not implement with AWS’s ValidationError, and implements all four names AWS documents — auto-scaling-group, key, value and propagate-at-launch.

    • Only auto-scaling-group was read before and the rest were ignored, so a filter on key or value returned every tag in the account. It also read only the first value of auto-scaling-group, so a caller asking about three groups was answered about one of them and told nothing about the other two

    Migration

    a call passing a filter name outside those four now fails instead of returning everything

    autoscaling
  • Changed

    DescribeParameters refuses a ParameterFilters key or option it does not implement, with the InvalidFilterKey and InvalidFilterOption errors AWS declares on the operation.

    • It applied a filter only when the caller had spelled Key=Name, Option=BeginsWith exactly, and ignored everything else — so a filter on Type, KeyId, Path, Label or Tier, or Option=Equals/Contains on Name, answered with every parameter in the account presented as a filtered result. The same eight lines were written twice, in the legacy handler and the typed path, so the two could also disagree; there is now one declaration behind both. Type is implemented alongside Name, and Contains alongside BeginsWith

    Migration

    a call passing a filter key or option Overcast does not implement now fails instead of silently returning everything; the error names what is accepted

    ssm
  • Changed

    tag keys and values are checked against the character set AWS documents for them, ^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$ — letters, digits, spaces, and _ . : / = + - @ — which nothing enforced anywhere.

    • It applies to every service that shares the validator. Keys AWS itself stamps are unaffected: aws: is reserved from callers, not from AWS, so the aws:autoscaling:groupName that Auto Scaling puts on the instances it launches is still accepted

    Migration

    a tag carrying a character outside that set — !, %, (, a tab or a newline are the common ones — is now refused by every service that validates, where it was previously stored. Rewrite the tag, or drop the offending character. Nothing already stored is re-validated or removed

    tagging
  • Fixed

    tag filters work, and a resource’s tags are one thing rather than two.

    • DescribeVpcs did not implement tag:<key> and ignored it silently, so describe-vpcs --filters Name=tag:Name,Values=... returned every VPC in the region — a find-or-create script read Vpcs[0], got the seeded default VPC, and adopted a public VPC it never meant to use. tag:<key>, tag-key and tag-value are now implemented on every describe that returns a taggable resource — VPCs, subnets, instances, NAT gateways, VPN gateways, security groups, route tables, internet gateways and network interfaces, the last four of which returned no tagSet at all — and an unmatched filter returns nothing rather than everything. Underneath, create-time TagSpecification.N tags were written to the resource’s own record while CreateTags wrote to the tag store, and no describe read both: tagging an existing NAT gateway succeeded and stayed invisible, and an instance’s create-time tags never reached DescribeTags. There is now one tag store behind both. DescribeVpcs also returns the tags a caller set — it previously replaced them with Overcast’s own overcast:network-status diagnostic, which is now merged in alongside. Tag rendering order and the lifetime of a tag are no longer EC2’s own business either — see the [tagging] entries below, which took both repo-wide in this same release

    Migration

    two changes can move data you already have. A tag filter that was previously ignored now selects, so a find-or-create script that had been silently taking its “found” branch will start creating the resource it was written to create — which is the fix, but it will run. And create-time tags on instances, NAT gateways and VPN gateways that were written to the resource record by an older Overcast are not carried into the tag store, so re-apply them with create-tags if you are reusing a persisted state file; state created from this release on is unaffected

    ec2
  • Fixed

    tag keys and values are validated.

    • EC2 stored tags without ever reaching for the shared validator, so it accepted a 300-character key, an empty key, and the reserved aws: prefix, all of which AWS refuses. Both wire paths validate now, and a create call carrying TagSpecification.N is refused before it makes the resource rather than after

    Migration

    a tag EC2 previously accepted and AWS would not is now refused, so a script or template setting one starts failing where it used to appear to work. Tag keys must be 1–128 characters, values at most 256, and neither may begin with aws:. Nothing already stored is re-validated or removed

    ec2
  • Fixed

    DeleteRepository refuses a repository that still holds images unless force is set, answering RepositoryNotEmptyException (HTTP 400) as AWS does.

    • It previously ignored force entirely and answered 200, taking every image in the repository with it — the guard is the only signal a caller gets, so a teardown that meant to be stopped was told it had done the right thing instead. An image that arrived by docker push counts too: the registry is swept before the check, the same way ListImages sweeps it

    Migration

    a script that deletes a repository holding images has to pass --force (force: true), which is what the same script already needs against real AWS

    ecr
  • Fixed

    a cache that never comes up now says so instead of reporting itself available or sitting in creating forever.

    • Two of the three things that can happen to a create reached a wrong answer. A replication group whose primary node never answered was transitioned to available once its retries ran out (#881), so DescribeReplicationGroups reported a working cache with nothing behind its configuration endpoint — and unlike every other instance of this bug it did not even need a container to have existed, since a group whose start never reached Docker took the same path. A container the Docker daemon refused outright fell between the owners entirely: the start goroutine logged a warning and returned, and the readiness watch that would have owned the record from there is only scheduled once the start succeeds, so nothing was left that would ever move it. Both now settle a replication group and a serverless cache in create-failed and a cache cluster in incompatible-network, the terminal statuses AWS documents for each of those three shapes — ElastiCache does not give them the same vocabulary, and CacheClusterStatus has no create-failed at all — with the engine’s silence or the daemon’s own error recorded on the record as the reason why. Both the form-encoded and the typed create paths are fixed, so the status no longer depends on which wire protocol the caller’s SDK negotiated. incompatible-network is one of the failure acceptors in the CacheClusterAvailable waiter, so aws elasticache wait cache-cluster-available fails and reports rather than spinning out its attempts

    Migration

    a caller that treated anything other than available as “still creating” and kept polling will now see a terminal status; treat create-failed and incompatible-network as failures, which is what they mean on AWS

    elasticache
  • Fixed

    an operation whose URI carries an ARN is authorized rather than passed through unauthorized.

    • Every AWS SDK percent-encodes an ARN into a single path segment, because a Smithy non-greedy label binds one segment, and the action lookup read the decoded path — so the ARN’s %2F slashes reappeared as extra path segments, no modeled binding matched, and requestIAMAction named nothing. That is IAMEnforce’s one deliberately fail-open branch, so with OVERCAST_ENFORCE_IAM=true every such call was served with no policy evaluated at all, including one a policy explicitly denied. MSK’s per-cluster and per-configuration surface was the visible case; it held for any service that binds an ARN into a URI. This is the fault PR #1000 fixed in the router’s 501 fallback, on the second of the two callers that walk the same generated trie

    Migration

    with OVERCAST_ENFORCE_IAM=true (off by default, and nothing changes with it off), a policy that was never consulted for these operations is now consulted — grant the principal the actions it needs on ARN-addressed paths, such as kafka:DescribeCluster, kafka:ListNodes and kafka:ListClusterOperations for MSK’s per-cluster calls

    iam
  • Fixed

    CreateCluster, CreateClusterV2 and CreateConfiguration refuse a name already in use in the same region, answering ConflictException (HTTP 409) as AWS does.

    • Each previously minted a second resource beside the first and answered 200, leaving two clusters — or two configurations — sharing one name for every later lookup by name to guess between. v1 and v2 are two bindings onto one cluster-name namespace, so the guard reads the same records whichever one the create arrived through, and it is scoped to the request’s region, since the same name in two regions is two resources on AWS

    Migration

    a script that creates the same cluster or configuration name twice has to delete the first or pick another name, which is what the same script already needs against real AWS

    msk
  • Fixed

    UpdateClusterConfiguration takes the configuration reference from the modeled configurationInfo object and answers NotFoundException (HTTP 404) when the ARN it names does not exist.

    • It read a flat configurationArn that no AWS client sends, so the ARN was always empty, the existence check below it never ran, and an update naming a configuration that was never created was answered 200 with the cluster’s version spent on it — the caller told a reference had been resolved that nothing had resolved. The stale-currentVersion check is unchanged, and both checks still run before the cluster is touched, so a rejected update does not spend the version it was refused on

    Migration

    a caller that hand-rolls this request body has to send configurationInfo: {"arn": …, "revision": …} in place of a flat configurationArn, and a body carrying neither is a BadRequestException because the model makes the member required; the AWS CLI and every AWS SDK already send exactly that

    msk
  • Fixed

    CreateCluster and CreateClusterV2 keep the tags in the create request where the tag operations look, so ListTagsForResource returns them, UntagResource removes them, and every describe and list shows them alongside the ones TagResource added afterwards.

    • Creation-time tags went onto the cluster record instead, which no tag operation reads: the call was accepted and the cluster reported itself untagged

    Migration

    a cluster created before this upgrade keeps working, but its creation-time tags are not carried over — re-apply them with aws kafka tag-resource

    msk
  • Fixed

    an omitted ParameterFilters Option means Equals, as it does on AWS. It previously meant “apply no filter at all”, so the comparison a caller gets by default — {Key: "Name", Values: ["/app/db"]} — matched every parameter rather than the one named

    Migration

    a caller relying on an unfiltered result from a filter with no Option will now get the exact match they asked for

    ssm
  • Fixed

    tag keys and values are validated, through the shared validator EventBridge and the other services on it already use.

    • Scheduler reached its tag store directly and ran none of those checks, so it accepted a reserved aws: key prefix, keys over 128 characters, values over 256, and characters outside the set AWS documents — tags real AWS refuses, stored here without complaint, so a template that worked against Overcast failed on deploy. TagResource and the inline Tags on CreateScheduleGroup now both run serviceutil.ValidateTags, the validator EventBridge next door has always used. The count limit is applied to the merged result rather than to each request, so fifty tags added one call at a time is still refused at fifty-one, and a create refused for its tags no longer leaves the schedule group behind

    Migration

    a tag Scheduler previously accepted and AWS would not is now refused with ValidationException, so a script or template setting one starts failing where it used to appear to work. Tag keys must be 1-128 characters, values at most 256, neither may begin with aws:, and both are limited to letters, digits, spaces and _ . : / = + - @. Nothing already stored is re-validated or removed

    scheduler
  • Removed

    the 2025-11-25 half of the MCP surface is gone:

    • the initialize handshake and notifications/initialized, protocol sessions and the Mcp-Session-Id header, the DELETE that ended one, the GET /_overcast/mcp stream and the legacy /_overcast/mcp/sse bridge, stream resumability with Last-Event-ID and the OVERCAST_MCP_REPLAY_LIMIT that bounded it, ping, logging/setLevel, and resources/subscribe / unsubscribe. MCP revision 2026-07-28 replaces the whole connection model rather than extending it, and Overcast now serves that revision and nothing else: POST /_overcast/mcp is the only endpoint, and every request declares its own protocol version in _meta rather than negotiating one. Nothing is lost with the surface: server/discover reports what initialize did, subscriptions/listen carries what the GET stream and resources/subscribe carried, a request’s own notifications travel on its response stream, and the keep-alive on a long-lived stream replaces ping as the liveness check

    Migration

    point the client at a 2026-07-28 MCP implementation — a client that opens with initialize has no fall-forward mechanism, which includes the current TypeScript SDK unless ClientOptions.versionNegotiation is set. Remove OVERCAST_MCP_REPLAY_LIMIT from any environment that sets it; it is no longer read

    mcp
Added 6
  • a request that asks for a streamed response now gets one:

    • the notifications belonging to that request arrive as they happen, followed by its result. Progress, log messages and cancellation are scoped to a single request — MCP revision 2026-07-28 says such notifications “flow only on the response stream of the request they relate to” — so a long tool call reports its progress on its own response instead of on a separate connection, and a client reading in order sees the work reported before the answer to it. Cancelling a call delivers notifications/cancelled on the cancelled call’s stream, which then ends without a result. A request that did not ask for a stream is answered exactly as before, and a request refused before it reaches a handler is still an HTTP error rather than a stream carrying one

    mcp
  • a Settings page in the console, whose first section drives HTTPS setup end to end:

    • it reports the serving mode, certificate and trust-store state, mints the CA and server certificate on a click, and installs the CA into the system trust store behind the OS approval prompt. A containerized daemon still prepares the certificate material, so the CA is downloadable at /api/ca.pem before the restart, and hands back the host-side overcast https enable --endpoint ... one-liner. The one step no UI can take — restarting with OVERCAST_TLS=auto — is shown verbatim in per-shell tabs, and a “Switch to HTTPS” button appears once the TLS listener answers

    web tls
  • a header gear opening that Settings page, in every build including bundled ones; the connection is editable there as well as from the existing header plug, both sharing the first-run dialog’s form and its live reachability probe

    web
  • an empty CloudFormation, SQS or Lambda list page in the console now says when the resources exist in a different region — “No stacks in us-east-1.

    • There are 3 in ap-southeast-2.” — with a button that switches to it. A developer whose AWS_REGION points somewhere the console does not sees a working emulator and an empty list, and nothing on that screen is about regions, so it reads as a lost deploy or a broken emulator. The notice appears only when the selected region is genuinely empty and another genuinely is not: an account with nothing in it anywhere says nothing at all

    web
  • OVERCAST_CA_DIR puts the local CA somewhere other than $OVERCAST_DATA_DIR/ca, so a container can mint certificates from a CA that outlives it.

    • State and a trust anchor have opposite lifetimes — state is disposable, while installing a root certificate is a per-machine, permanent act — and tying them to one directory is what made every docker compose down cost another root-certificate approval. Create the CA once on the host with overcast https enable, mount it read-only (-e OVERCAST_CA_DIR=/ca -v ~/.overcast/data/ca:/ca:ro), and recreation, down -v and image upgrades all keep working with no further prompts; this is the anchor-durable/leaf-ephemeral split mkcert, Caddy and step-ca use. Read-only mounts are supported directly: leaf caching becomes best-effort (a re-mint costs about a millisecond) rather than a startup failure, and because a bind-mounted CA carries the host’s ownership and 0600 keys that the unprivileged container user usually cannot read, the entrypoint serves from a private in-container copy when that happens — the mount is never written to. docker-compose.yml now gives the CA its own volume

    tls
  • every event on the stream now carries the request id of the API call that caused it, so a PutObject and the notification, queue write and delivery it set off are all attributable to one call instead of appearing as unrelated activity;

    • the bus derives it from the context a publisher already passes, which covers asynchronous work — an SNS fan-out delivering after its Publish returned is attributed to that Publish — and leaves it off entirely for events no request caused, such as container and timer activity, rather than inventing an id that resolves to nothing; the Events page shows the id in the expanded event and in what the copy button writes, matches it in the filter box so pasting one narrows the console to that call’s events, and links each row to the request’s trace when OVERCAST_DEBUG is set; a trace’s own Events tab correspondingly lists everything its request caused, where it previously showed only the single row summarising the request itself

    events web
Changed 3
  • a client is told the tool, prompt or resource list changed without having to complete a handshake first.

    • Emission used to be gated on the initialize lifecycle, which MCP revision 2026-07-28 removes: a stateless client never performs one, so a provider registering after startup would have gone unannounced to it. A client that opened a subscriptions/listen stream and named the notification type has already said it is ready to receive, which is what the gate existed to establish

    mcp
  • the Metrics & Health page is reordered — health and runtime facts in one summary band, then a uniform runtime card grid, then advisories and per-subsystem diagnostics; the advisories section collapses to a single line when there is nothing to report, and the Docker table moved to the foot of the page and now shows each service its own socket

    web
  • tag output is key-sorted everywhere rather than in Go map order.

    • Tags are held in a map and Go randomises map iteration, so 19 render sites handed a client a different order on every call — churn for anything diffing two responses. Rendering now goes through one shared helper that sorts, so a service cannot render an unordered map by accident; this also replaces the eight hand-rolled sorts that each solved it locally. DescribeTags is ordered by resource ID and then by key

    tagging
Fixed 54
  • GetBackupPlan is reachable from an AWS SDK.

    • AWS binds it to GET /backup/plans/{backupPlanId}/ with a trailing slash — unlike UpdateBackupPlan and DeleteBackupPlan on the same resource, which carry none — and only the slash-less spelling was registered, so a signed client got a 501 and an unsigned one fell through to S3’s wildcard and got a NoSuchKey. Both spellings are now registered, as they already were for the three collection operations

    backup
  • failed ECS service deployments preserve the reason-bearing scheduler failure, create rollback honours Retain and RetainExceptOnCreate deletion policies, and the stack Events tab automatically loads older paginated history from its nested scroll region

    cloudformation ecs web
  • no-op stack updates no longer retrieve dynamic secret references for unchanged resources, matching AWS’s intentionally stale resource behavior

    cloudformation
  • stacks no longer complete around resources that are still being created:

    • ElastiCache caches, MSK and EKS clusters, EFS file systems, mount targets and access points, and Lambda functions are now held open until the service reports them ready, and a resource that reaches a failure status fails the stack with that status rather than a timeout. A stack whose ElastiCache or MSK resource cannot get a container therefore fails on the reason the moment it happens, rather than after the full stabilization budget — fifteen minutes for a cache and forty-five for a cluster, spent waiting on a record nothing was ever going to move. Those two waits now run in every deployment: they were skipped where no Docker socket was configured for the service, because a resource stuck in creating would have held the stack open for the whole budget and then rolled it back, and with both services now settling a metadata-only resource immediately there is nothing left to skip

    cloudformation
  • a failed deploy’s evidence is captured before rollback deletes it and served from GET /_overcast/cloudformation/stacks/{stackName}/diagnostics — the stopped ECS task’s exit code, the container’s own output, and the ECS scheduler’s events, each tagged with whether real AWS would have given you the same thing; the AWS API surface is unchanged, and a successful deploy clears the entry

    cloudformation
  • Docker-backed services reconcile one shared daemon snapshot at startup and after event-stream reconnects, immediately replace failed managed runtimes, and avoid retaining stale container state; ECS also preserves a missed container’s exit code and Docker stop time before replacing the task with current launch-time secrets

    docker ecs ecr efs eks elasticache msk rds
  • a filter value is matched as the pattern AWS treats it as, so * stands for any run of characters, ? for exactly one, and a backslash asks for a literal one.

    • Overcast compared values exactly, which quietly narrowed every wildcard filter to nothing: --filters 'Name=tag:Name,Values=overcast-*' selected no VPC, and CDK’s MachineImage.lookup — whose name filter is a wildcard in every documented example — found no AMI and failed the synth with AmiNotFound. Tag selectors get the same treatment, since tag:Name is where a wildcard is most often reached for

    ec2
  • forced ECS deployments launch replacement tasks with current Secrets Manager and SSM values, including CDK nonce-driven deployments

    ecs cloudformation
  • container logs read through the shared Docker de-framer, so a task started with a TTY no longer has the head of its output eaten and the rest cut at an arbitrary offset

    ecs
  • a task has its containers’ final output captured before they are removed rather than racing the removal:

    • the container reaper now takes the logs first, whichever teardown scheduled it, so the logs of a deployment that failed survive the rollback that deletes it and a task stopped by hand with StopTask can still be asked why it stopped. The retained copy is bounded to 16 KiB, carries the time it was taken, and is deleted with the task record instead of accumulating for the life of the process

    ecs
  • an ECS deployment now reaches its steady state only once its tasks have stayed up, so a CloudFormation stack no longer reports CREATE_COMPLETE around a service whose containers crash-loop; a service that fails to stabilize also reports the failure rather than “deployment in progress”

    ecs cloudformation
  • readiness is proven with the engine’s own protocol rather than a TCP dial.

    • Docker’s published port accepts connections as soon as the port proxy is up, well before the engine inside the container has bound anything, so a cache could be reported available while the first command a client sent went to a socket Redis was not on yet. Redis and Valkey are asked to PING and must answer +PONG, and Memcached is asked for version — the same bytes redis-cli and a version line put on the wire. A -LOADING or -BUSY reply counts as not ready rather than as an answer

    elasticache
  • a cache with no container behind it now reports available instead of creating forever.

    • Without a Docker socket configured nothing is started, so no readiness check was ever scheduled and nothing existed that could move the record on — aws elasticache wait cache-cluster-available spun until it gave up over a cache that was already as ready as it would ever be. A cache cluster, a replication group and a serverless cache created with no container runtime now reach available as soon as they are recorded, which is what RDS and Lambda have always done for a metadata-only resource. A daemon that appears later still builds a container for the record and runs a real PING against it

    elasticache
  • a resource update with nobody subscribed to it no longer builds a notification body before discovering there is no audience. Providers are handed this emitter directly, so it runs on the emulator’s own paths on every resource change whether or not MCP is in use

    mcp
  • a client may now send requests that carry their own protocol metadata, with no initialize handshake first.

    • MCP revision 2026-07-28 makes the protocol stateless: instead of negotiating once and remembering the answer for the connection, every request declares its protocol version, client identity and client capabilities in _meta. A request that does so is served on its own terms — the lifecycle gate has nothing left to check, because the request supplies what the handshake used to establish. It is now the only way in, since the handshake it replaces is removed elsewhere in this release. Version disagreements are reported the way the revision specifies — an unsupported version returns -32022 listing the versions the server does support, and a MCP-Protocol-Version header contradicting the body returns -32020 — each with the 400 Bad Request status the revision pins to it. A modern client also repeats its method in Mcp-Method, and the thing it names in Mcp-Name on tools/call, resources/read and prompts/get, so that load balancers and gateways can route and inspect a request without parsing it — and both are now checked against the body, because the value of that mirroring depends on the two agreeing: a header saying one thing while the body says another means an intermediary and the server are handling different requests, which is exactly the confusion the rule exists to prevent, so a disagreement is refused with -32020 and 400 Bad Request. A name that cannot be sent as plain ASCII is decoded from its base64 form before comparison, so a correct request is not read as a mismatch, and the stdio transport is exempt deliberately: header mirroring is a property of the HTTP binding rather than of the protocol, and stdio has no envelope to mirror into and no intermediaries to serve. The rest of the migration, including retiring the handshake, sessions and stream resumability, is tracked in docs/plans/mcp-2026-07-28-migration.md

    mcp
  • every result now declares its type, and the five cacheable ones say how long they may be reused.

    • MCP revision 2026-07-28 requires a resultType on every result — complete for an ordinary answer, with other values reserved for answers that are not final — and requires tools/list, prompts/list, resources/list, resources/read and resources/templates/list to carry ttlMs and cacheScope. Both are stamped centrally where results are written rather than at each of the dozens of sites that build one, so a new handler cannot omit them. The lists advertise a one-minute reuse window, short because those answers genuinely change and the list_changed notifications are the real mechanism for saying so; server/discover advertises an hour, because its answer derives from what is compiled into the binary and cannot change under a running server. All are scoped public, since none varies by caller or by credential. One result is not stamped: a tools/call answer is a typed structure with no field to put a resultType in, and the revision defines an absent one as complete

    mcp
  • the server answers server/discover, so a client can ask what it speaks before committing to how it talks.

    • MCP revision 2026-07-28 requires the method and uses it as the era probe: a client that does not yet know whether a server negotiates with initialize or serves each request statelessly sends this first and reads the answer. It reports the protocol versions the server supports — one, 2026-07-28 — along with its capabilities and identity, and answering it confers no state, which is the point of the revision. It is an ordinary request for all that: the protocol version and client capabilities are required in _meta on every request and this method is granted no exemption, so a discover carrying neither is malformed and refused -32602 with 400 Bad Request like any other. A client that does not know which version to name declares the one it speaks and reads the supported list off the -32022 refusal — the same information this method would have returned. That list, which this method and the workspace repo_workspace_info tool both read from one constant, no longer names the retired 2025-11-25: a server with no handshake, no session and no GET stream was inviting clients into an era it could not serve, and the -32022 refusal handed the same misleading list back to retry with

    mcp
  • a long-lived MCP stream now says it is alive, and stops holding the emulator open.

    • Nothing flows on a subscriptions/listen stream between notifications, and a stream that says nothing for minutes at a time is what proxies and client idle timeouts close underneath you — and what stopped the emulator noticing a client had gone, because it never attempted a write to find out; with the ping utility method removed by MCP revision 2026-07-28, a keep-alive is the only liveness mechanism the stream has left. One is now sent every 15 seconds while the stream is idle, as an SSE comment line — the shape the specification points at for a long-lived stream, a line beginning with a colon carrying no event data, which clients ignore rather than treating as a message — so nothing is added to the protocol and no JSON-RPC message is invented, and the interval matches the emulator’s two other SSE endpoints. Shutting down no longer waits on the client either: the stream used to select on the request context alone, so the only thing that could end it was the client disconnecting and an attached client that had simply gone quiet never did, which meant one of them was enough to hold overcast open until it was killed, a graceful shutdown waiting for in-flight handlers. It now takes the same pre-shutdown channel the emulator’s other long-lived handlers have always taken, and says so before it goes: the stream is closed with the empty result that marks an orderly end, so a client can tell a shutdown from a dropped connection and decide whether to reconnect

    mcp
  • a client can open one long-lived stream with subscriptions/listen and say on it exactly which notifications it wants.

    • MCP revision 2026-07-28 replaces both the standalone GET stream and resources/subscribe with this: the request carries a filter naming the notification types and the resource URIs of interest, the server acknowledges with the subscription’s id before sending anything else, and every notification on the stream carries that id so a client running several can tell them apart. Types the client did not ask for are never sent, and request-scoped notifications — progress and log messages — never appear here, because those belong to the response stream of the request they relate to. The stream sends a periodic keep-alive comment and ends cleanly when the emulator shuts down, so a client can tell an orderly close from a dropped connection. It is the only way to receive these notifications: the GET stream and resources/subscribe it replaces are removed elsewhere in this release

    mcp
  • Cancelling an in-flight request over HTTP could panic the connection serving it.

    • The notifications/cancelled event was written to the cancelled request’s response stream only after the request had been released, so the response could finish — and net/http reclaim the connection — while the write was still on its way. The notification now goes out first, while the response provably cannot end; a request that finishes of its own accord in the same instant has the late write dropped rather than made

    mcp
  • a cluster whose broker never comes up reaches FAILED carrying why, instead of sitting in CREATING forever — whether the broker started and never answered, or its container could not be built at all.

    • aws kafka wait cluster-active used to spin until it gave up with nothing to say, because the only record of what went wrong was a log line no API caller reads; the reason, or the daemon’s own error, is now in stateInfo, which DescribeCluster and DescribeClusterV2 both return. Readiness is proven with a Kafka ApiVersions request rather than a TCP dial, for the same reason as ElastiCache above. A cluster that is already ACTIVE when a reconcile or Docker-event start fails is left alone

    msk
  • a cluster with no broker behind it now reports ACTIVE instead of CREATING forever, on the same rule and for the same reason — and matching what CreateClusterV2 already did for a serverless cluster, which has no brokers to provision either. aws kafka wait cluster-active no longer spins out its attempts against a metadata-only cluster

    msk
  • DescribeCluster and ListClusters report a cluster in the modeled ClusterInfo shape:

    • the Kafka version it was created with arrives in currentBrokerSoftwareInfo.kafkaVersion, the only member AWS binds it to, so a client can confirm what it provisioned. Both used to marshal the stored record straight onto the wire, which spelled the version as a top-level member no generated client reads and put the emulator internal Docker bookkeeping in the response beside it. DescribeClusterV2 and ListClustersV2 already carried it under provisioned

    msk
  • cluster and configuration sub-resources answer a protocol-correct 501 rather than a fabricated “not found”.

    • The /v1/clusters/* dispatch took the whole path tail as the cluster ARN, so ListNodes, ListClusterOperations, ListScramSecrets and GetClusterPolicy were told the cluster arn:aws:kafka:…:cluster/my-cluster/<uuid>/nodes does not exist, and ListConfigurationRevisions the same on /v1/configurations/*. Where the ARN ends and a sub-resource begins is now read from the pinned model instead of guessed, so both spellings of the ARN — percent-encoded into one segment, and with literal slashes — keep resolving to the right operation

    msk
  • log lines, debug traces and operation metrics name an operation whose URI carries an ARN. The same decoded-path lookup left the label empty, so MSK’s per-cluster calls went unlabelled and unmetered, as did every other service that puts an ARN in a URI

    observability
  • ListTags is reachable from an AWS SDK or the AWS CLI.

    • AWS binds it to the collection URI with a trailing slash — GET /2021-01-01/tags/, the only trailing-slash URI in the OpenSearch model — which is what every client sends and what the route did not match, so a signed client got a 501 and an unsigned one fell through to S3’s wildcard object route and got a 404 NoSuchKey for an OpenSearch call. Both spellings are now registered; the slash-less form keeps working for callers already using it

    opensearch
  • CreateDBParameterGroup accepts the aurora-mysql8.0 family used by Aurora MySQL 3 instances

    rds
  • master accounts now receive version-specific AWS bootstrap privileges and restart-safe credentials instead of stock-image database-scoped grants; Aurora members inherit cluster-owned settings

    rds
  • the retry policy, the deadline and the terminal-failure decision behind every readiness poll come from one shared helper, which RDS and EFS now share with ElastiCache and MSK.

    • Their probes are unchanged — the container inspect, connection and pg_isready/mysqladmin ping exec for a database, the NFSv4 NULL call for an export — and so is what each of them already did when a resource never answered; what they no longer do is each spell out how long to wait and what “gave up” means

    rds
  • startup defers event queues, MCP metadata, diagnostic histories, login templates, and Lambda validation patterns until first use

    router
  • a modeled operation whose URI carries an ARN now reaches its 501 instead of falling through to S3.

    • Every AWS client percent-encodes an ARN into a single path segment, because a Smithy non-greedy label binds one segment, and the REST fallback classified the decoded path — so the ARN’s %2F slashes reappeared as extra path segments, no modeled binding matched, and S3’s bucket/object wildcard answered. aws kafka list-cluster-operations-v2, correctly signed, came back <Error><Code>NoSuchKey</Code>…v2/clusters/arn:aws:kafka:…/operations</Error>. That is the fault #963 records, reached by a second route, and it held for every service that puts an ARN in the path

    router
  • tags die with the resource they describe.

    • Tags live in a namespace keyed by resource ID, so nothing tied them to the record’s lifetime and four services never removed them: ListTagsForResource kept answering for a rule, bus, cache, DB instance, cluster, subnet group, parameter group, schedule or schedule group that had been deleted, and the blob stayed in the store for the rest of the session. Deleting any of them now takes its tags with it. EventBridge also keyed a rule’s tags by whichever ARN spelling the caller used, so tagging a default-bus rule through rule/<name> hid the tags from rule/default/<name> and from the delete path; both spellings now reach the same tags. EventBridge’s two wire paths minted different ARNs for the same rule or bus — one honouring the request’s region, one always the configured default — and now agree

    eventbridge elasticache rds scheduler
  • Settings → HTTPS says when a containerized daemon’s CA will die with the container, and shows how to hand it one that will not — the console’s own status endpoint reports caEphemeral, so this is a fact about the running daemon rather than general advice

    tls
  • the daemon no longer logs http: TLS handshake error ...: client sent an HTTP request to an HTTPS server once every health-check interval under OVERCAST_TLS=auto.

    • The image’s probe tried plain HTTP first and fell back to https, so a perfectly healthy TLS container produced a handshake error every 5 seconds and buried the ones that meant something; the probe now tries https first, which a plain-HTTP daemon rejects client-side without logging anything. The compose files carried an http-only probe that replaced the image’s, so docker compose up with TLS on marked the container unhealthy for the life of the run — both now mirror the image

    tls
  • the API and the web UI no longer share one *tls.Config.

    • ServeTLS mutates the config it is handed (it appends to NextProtos to advertise HTTP/2), so passing the same pointer to both servers was a data race, reported by go test -race, on the ALPN list each listener offers; each server now gets its own copy

    tls
  • overcast https enable --endpoint ... is spelled https:// wherever a TLS daemon is the target — the startup hint a containerized daemon logs, the console’s host-side command once TLS is on, the CLI help, and the docs. The http:// spelling still works (the fetch retries over TLS), it just made the daemon log a handshake error for a command Overcast itself had handed the user

    tls
  • Settings → HTTPS now has a final step for the thing that breaks after the switch:

    • repointing SDKs and the CLI at the https endpoint. It gives the exact AWS_ENDPOINT_URL / --endpoint-url value for this daemon’s port, and names the CA bundle for AWS_CA_BUNDLE and NODE_EXTRA_CA_CERTS — which the AWS CLI and Node.js SDK need even after the CA is in the system trust store, since both ship their own root bundles and never consult it. The step is also shown when TLS is already on, which is when people find their CLI broken

    web tls
  • syntax-highlighted log rows render as a single text node where the CSS Custom Highlight API exists (Chrome 105+, Safari 17.2+, Firefox 140+) — token colours paint as static ranges, so there are no per-token spans for extensions and the accessibility tree to walk, large documents tokenize in a persistent worker, and the cached Prism markup renders unchanged everywhere else.

    • The highlight sets themselves are only ever rebuilt and swapped in wholesale, never mutated range-by-range — the one bulk path Firefox’s engine affords — so scrolling any distance in either direction, and a busy live tail, perform no per-range highlight work at all: this closes a Firefox lockup a 31-second profile showed spending 15 seconds deleting ranges one at a time, and its sequel where re-adding them cost a 21-second stall on a jump back to the top

    web
  • code comments in syntax-highlighted previews (S3 objects, HTTP bodies, the code browser) now render muted instead of full-strength foreground — the token theme gained a comment colour it never had

    web
  • a missing log group or stream no longer renders as an empty one.

    • Opening a stream URL while the region selector points at a region where the group does not exist showed the ordinary “No log events” empty state — the server’s ResourceNotFoundException was swallowed, so a wrong-region visit read as an application that never logged. The stream viewer and the log group page now show a not-found state naming the group (or stream) and the region, other load failures surface with the server’s message instead of reading as an empty stream, and a failed cross-stream search (an invalid filter pattern, say) says the search failed rather than claiming “No matching events”

    web logs
  • TagResource and CreateScheduleGroup read Tags as the list of {Key, Value} structures AWS models, and ListTagsForResource answers with the same list, ordered by key. The member was decoded as a map, so every AWS SDK and aws scheduler tag-resource sent the modeled array and got a SerializationException back — nothing outside the emulator could put a tag on a schedule or schedule group, inline Tags on CreateScheduleGroup were dropped without a word, and an untagged resource rendered an empty object where a TagList was modeled

    scheduler
  • starting a persistent-store daemon while another is opening the same database file no longer fails on the lock.

    • PRAGMA user_version is the first statement the migration runner issues, and switching a new database to WAL takes an exclusive lock, so connections racing to initialise one path collided — eleven of twelve, in a reproduction — and the loser died with migrate: state: read user_version: database is locked before serving anything. That read now waits the lock out, using the same transient-error classifier and retry delay every other read in the store already uses; a lock nobody releases still surfaces rather than hanging, and an error that is not the busy class still fails immediately

    state
  • the store’s transient-SQLite classifier now recognises extended result codes, which the driver enables on every connection.

    • database is locked (261) — SQLITE_BUSY_RECOVERY, the busy a reader gets while another connection recovers the WAL of the same fresh file — compared unequal to the primary SQLITE_BUSY constant, so the busy-class retries (including the migration runner’s new user_version wait) never engaged for it and concurrent fresh opens could still fail on slow disks. The classifier now compares the primary code in the low byte, covering SQLITE_BUSY_RECOVERY, SQLITE_BUSY_SNAPSHOT, and SQLITE_LOCKED_SHAREDCACHE; genuinely fatal codes such as file is not a database still fail fast so an unusable database degrades the store promptly instead of sitting out the retry budget

    state
  • Live Tail no longer drops events when a producer writes many small PutLogEvents calls — the session buffer is now counted in events (5,000, matching AWS) rather than write batches (10), and an update that follows an overflow reports sampled: true instead of losing lines silently

    cloudwatch
  • the log views stay responsive at tens of thousands of lines:

    • live tail arrivals batch per frame into a bounded 10,000-event buffer that reports what it drops, the map’s log peek is virtualized with stable row identity, syntax highlighting and row metadata are cached instead of recomputed per scroll frame, and the stream viewer pages past FilterLogEvents’ 10,000-event cap (marking the count “N+” while more remain) instead of silently truncating; the Lambda monitor tab’s long-claimed 5-second auto-refresh now actually happens

    cloudwatch web
  • log search and history navigation follow the AWS console’s patterns.

    • The log group page’s cross-stream search results are virtualized and render through the shared log-row pipeline — level tints and badges, ANSI colour, the search’s matches marked, the stream column on the right — where they were previously a plain table with none of these; clicking a result opens its stream centred on that exact event, scrolled into view and persistently highlighted, with the preceding fifteen minutes of context above (a copyable per-row link reopens the same view, and the anchor survives a fresh fetch in another tab — identity is the event’s timestamp plus a content signature). The stream viewer loads history older than its window on demand: nearing the oldest edge extends the time window backward in doubling chunks — FilterLogEvents pages forward only, so “older” is time-window expansion — prepending with no viewport jump and stopping honestly: “Start of logs” when history is exhausted (bounded by the streams’ firstEventTimestamp metadata, no empty-window probing), “Start of range” when a user-set start time is the floor. A jump-to-timestamp control repositions the view on the first event at or after a chosen time via the same anchor machinery

    cloudwatch web
  • a live tail says what it is doing, and survives the ways a session actually dies. The list’s edge states its condition — “End of logs”, “Loading more events…”, “Live tail — watching for new events”, or “Live tail disconnected” — instead of empty space that read the same in every case, and a session-health dot on the Tail button and the map peek’s Logs tab pulses green while the StartLiveTail stream is open and turns red, still, when it is gone.

    • A dead session is detected even when nothing errors: a staleness watchdog reads the emulator’s once-a-second heartbeat and marks the session dead after ~10 s of silence (machine sleep, NAT timeout — the cases that used to look identical to a quiet stream forever). After a tail death the map’s log peek keeps the newest edge reachable by walking GetLogEvents’ forward tokens, reconciled against what the dead session already delivered so nothing shows twice. (@tanstack/react-virtual bumped to 3.14.9 for its iOS-aware scroll adjustments around the peek’s prepend anchoring)

    cloudwatch web
  • the log stream viewer’s quality-of-life set, mirroring the AWS console where it has the feature and the browser devtools where it doesn’t:

    • a UTC/local timestamp toggle; opt-in inter-row time deltas beside the Time column; view preferences that survive revisits (mode, Format, Syntax, Wrap, sort, UTC, Deltas, Collapse — localStorage, falling back to defaults silently on corrupt state); an opt-in Collapse mode rendering every row as one fixed-height truncated line with per-row click-to-expand, badges, tints and ANSI intact — which also removes the virtualizer’s row-measurement work entirely; an Export control downloading the loaded events in display order as CSV (the console’s search-results shape — ISO 8601 timestamps, raw message bytes) or JSON, saying so explicitly when more events remain on the server; a hover affordance on any row carrying a Lambda request id (START/END/REPORT lines, JSON requestId fields, the Node runtime’s tab-separated UUID column) that filters the view to that invocation, as the quoted FilterLogEvents term typing it would run; and keyboard row navigation — arrows or j/k move a visible cursor, Enter expands the focused row in Collapse mode, c copies its message — announced via aria-activedescendant, never touching keys typed into inputs, and costing no re-render of row content on cursor moves

    cloudwatch web
  • reading and writing log events now costs what the page costs, not what the history costs, in both storage backends (memory and SQLite).

    • Paging deep into a busy stream — the log peek walking back 200 events at a time, or the viewer expanding its time window into history — used to slow down linearly with how much newer history the page had to skip (a page 400k events deep in a 500k-event stream answered ~84× slower than one near the tail in memory mode, ~166× over SQLite); token pages now resume by index seek and stay flat at any depth. A FilterLogEvents scoped to one stream reads only that stream’s slice of the window instead of the whole group’s, and PutLogEvents no longer rescans the stream’s persisted history per write batch over SQLite (~129ms per batch at 500k stored events, now sub-millisecond) nor re-sorts the whole stream in memory mode

    cloudwatch
  • the log viewer’s filter placeholder and footer notices no longer render a double-encoded em dash as garbage characters — the strings, along with comments in the Lambda service doc and two test files, had been written back through the wrong encoding; a new CI check (scripts/check_encoding.py) now fails on mojibake, stray BOMs, and replacement characters so it cannot recur

    cloudwatch web
  • the Format pass is memoised: pretty-printing a log document now happens once per distinct message instead of on every row mount and every Format toggle, so scrolling back over heavy documents and flipping Format stop re-paying multi-millisecond parses per row

    cloudwatch web
  • scrolling a large log stream no longer locks up or leaves blank regions — rows mounted mid-scroll defer their syntax-highlight markup and hover-action buttons until the scroll settles, cutting the DOM churn that fed browser extensions’ form scanners and the accessibility tree (a profile showed 44% of the main thread in those walkers and ~148k node removals in ten seconds)

    cloudwatch web
  • a single-token log line — a stack dump with no whitespace — no longer forces the whole stream view onto a tens-of-thousands-of-pixels horizontal scrollbar while Wrap is on, leaving the column headers behind: the message block can now shrink to the viewport so break-word has something to break against

    cloudwatch web
  • the bucket object browser scrolls and pages large listings without re-rendering the world:

    • rows are keyed by their S3 identity (prefix, key, key + versionId) instead of their index, so a sort flip or a filter keystroke moves rows rather than remounting and re-measuring every visible one; the next listing page is fetched off the continuation token as the user nears the end of loaded rows, closing a stall where a fast response never re-armed the fetch chain; and visible rows are memoized so scroll frames and unrelated state changes (dialogs, the search box) stop re-rendering them. The object preview dialog now caches its syntax highlighting and declines to pretty-print or highlight documents past the existing 256 KiB formatting cap — saying so in the preview header — instead of freezing at dialog-open on a large JSON or JavaScript object. What is fetched and shown is unchanged: delimiter/prefix grouping, version interleave, scan caps and their “partial” labels all behave as before. The preview also highlights through the same zero-DOM kernel as the log views — one text node with token colours painted as Custom Highlight ranges where the browser supports it, unchanged Prism markup elsewhere

    s3 web
Removed 1
View on GitHub
v0.0.1-alpha.35 prerelease 11 assets
20 added · 7 changed · 83 fixed · 1 removed · 1 security · 36 breaking appconfig appconfigdata backup console dns +33 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.35

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.35

Downloads

Changelog

20 added · 7 changed · 83 fixed · 1 removed · 1 security · 36 breaking

Breaking changes

  • Added

    the create operations require the members AWS marks required — Name on an environment, Name and LocationUri on a configuration profile, Content-Type on a hosted version — and creating an environment or a profile under an application that does not exist answers ResourceNotFoundException in place of storing an unreachable record

    Migration

    pass the required member, and create the application first

    appconfig
  • Added

    StartConfigurationSession accepts RequiredMinimumPollIntervalInSeconds, reports it back in Next-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 drop RequiredMinimumPollIntervalInSeconds from StartConfigurationSession

    appconfigdata
  • Added

    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 same overcast.service=ecr labels every other managed resource does; the container itself stays disposable. The gap this closes is cdk deploy: cdk-assets asks DescribeImages for 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, default 4510) 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 with docker volume rm overcast-ecr-registry-data-4510 while Overcast is down, or set OVERCAST_ECR_REGISTRY_PERSIST=false to keep the old container-lifetime storage

    ecr
  • Added

    ListTags, AddTags, RemoveTags and DescribeDomains require the members AWS marks required, answering ValidationException where an empty ARN or an empty domain list was accepted

    Migration

    pass the required member — ?arn= on ListTags, ARN on AddTags and RemoveTags, and at least one name in DescribeDomains

    opensearch
  • Changed

    CreateHostedConfigurationVersion and GetHostedConfigurationVersion answer with the configuration content as the response payload and the metadata in the Application-Id, Configuration-Profile-Id, Version-Number, Description, Content-Type and VersionLabel headers, as AWS binds them. The create used to answer a JSON envelope, and both used invented AppConfig-* header names

    Migration

    read the version number from the Version-Number header rather than from a JSON body

    appconfig
  • Changed

    Application, Environment and ConfigurationProfile responses carry the members AWS models and no others; the invented Arn and Tags members are gone, and ListConfigurationProfiles answers profile summaries with ValidatorTypes rather than whole profiles

    Migration

    read tags through ListTagsForResource; an AppConfig ARN is arn:aws:appconfig:{region}:{account}:application/{id}[/environment/{id}|/configurationprofile/{id}]

    appconfig
  • Changed

    configuration content over 1 MB comes back as PayloadTooLargeException with HTTP 413, the error AWS binds to that operation, in place of BadRequestException with HTTP 400

    Migration

    match on PayloadTooLargeException where a handler tested for BadRequestException

    appconfig
  • Changed

    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 reading CreationDate or DeletionDate as a string must read a number

    backup
  • Changed

    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

    backup
  • Changed

    DeleteBackupVault answers an empty 200, the Unit output AWS models, in place of an invented document

    Migration

    read the vault’s ARN and name from the CreateBackupVault or DescribeBackupVault response instead of the delete’s

    backup
  • Changed

    ResourceNotFoundException, AlreadyExistsException, MissingParameterValueException and InvalidParameterValueException replace the unmodeled ValidationException and the 404 status; AWS Backup models no ValidationException and 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

    backup
  • Changed

    the JWT issuer and OIDC discovery drop their region path segment:

    • iss is now {endpoint}/{poolId} and discovery is served at /{poolId}/.well-known/openid-configuration, matching the path portion of AWS’s https://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 reads issuer out of the discovery document needs no change. Overcast’s own validation reads the pool ID from the issuer path and is unaffected either way

    cognito
  • Changed

    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: true on an RDS instance, assignPublicIp: ENABLED on an ECS task. There is no Overcast-specific override, on purpose — the fix that works here is the fix that works deployed.

    ec2 lambda ecs rds elasticache
  • Changed

    DescribeIdentityProviderConfig is a POST to /clusters/{name}/identity-provider-configs/describe naming the config in the body, and answers the modeled shape with the config nested under identityProviderConfig.oidc alongside its ARN, status and tags

    Migration

    replace GET /clusters/{name}/identity-provider-configs/{type}/{name} with the POST, sending {"identityProviderConfig":{"type":"oidc","name":"…"}}, and read the result from identityProviderConfig.oidc

    eks
  • Changed

    ListInsights is a POST to /clusters/{name}/insights carrying filter, maxResults and nextToken in the body

    Migration

    replace the GET with the POST; an empty body {} returns what the GET used to

    eks
  • Changed

    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=true only; 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:

    iam
  • Changed

    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
  • Changed

    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, /_events and /_internal/domains/watch become /_overcast/health, /_overcast/metrics, /_overcast/topology, /_overcast/info, /_overcast/events and /_overcast/domains/watch, and overcast-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 whole pprof family — and /_mcp is now /_overcast/mcp. So do the per-service admin endpoints: /_lambda/instances, /_lambda/runtimes and /_lambda/layers/… under /_overcast/lambda/, /_ecs/… under /_overcast/ecs/, /_rds/… under /_overcast/rds/, EKS’s POST /clusters/{name}/kubeconfig as POST /_overcast/eks/clusters/{name}/kubeconfig, and the mail inbox from /_overcast/inbox to /_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} and POST /2015-03-31/functions/{name}/invoke-with-progress are 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/…, /_elb is /_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 and overcast CLI 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/health in place of /_health is the common one — the shipped Dockerfile and both compose files are already updated, but a docker-compose.yml you maintain yourself is not); MCP clients, which point at /_overcast/mcp; anything curling the debug endpoints by hand, where /_overcast/debug/state, /_overcast/debug/traces and /_overcast/debug/metrics are the common ones and go tool pprof http://localhost:4566/_overcast/debug/pprof/heap replaces the /_debug form; a direct caller of the admin or emulator-only Lambda endpoints (aws eks update-kubeconfig is 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 reading authorization_endpoint needs no change; and a caller posting to /@connections/… by path rather than through the API Gateway Management API endpoint. OVERCAST_DEBUG still 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

    router web cognito
  • Fixed

    POST /applications signed for appconfig answers with an AppConfig application;

    • it used to be answered by Service Catalog AppRegistry, returning a servicecatalog ARN with HTTP 200 and writing into AppRegistry’s store, with no error raised anywhere. GET, PATCH and DELETE /applications/{id} behaved the same way

    Migration

    sign AppConfig calls with an appconfig SigV4 credential scope, which every AWS SDK does. Unsigned callers and servicecatalog-scoped callers still reach AppRegistry

    appconfig appregistry
  • Fixed

    StartConfigurationSession validates that its three identifiers are present and within the modeled 128 characters, and that RequiredMinimumPollIntervalInSeconds is between 15 and 86400, answering BadRequestException

    Migration

    supply ApplicationIdentifier, EnvironmentIdentifier and ConfigurationProfileIdentifier, and keep any requested poll interval inside 15-86400 seconds

    appconfigdata
  • Fixed

    CreateBackupPlan mints a UUID for BackupPlanId, 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

    treat BackupPlanId as opaque; anything deriving a plan id from the creation time, or parsing the old plan-<nanoseconds> form, must read it from the create response instead

    backup
  • Fixed

    a template over either of AWS’s size quotas is refused with a ValidationError, rather than accepted.

    • An inline TemplateBody is capped at 51,200 bytes, and a template fetched from a TemplateURL at 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, so CreateStack, UpdateStack, CreateChangeSet, ValidateTemplate and GetTemplateSummary all 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 pass TemplateURL instead of TemplateBody — what the same template already requires against real AWS. aws cloudformation deploy and cdk deploy do this for you; a hand-rolled create-stack is 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

    cloudformation
  • Fixed

    CreateDomain answers ResourceAlreadyExistsException for 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
  • Removed

    the emulator-only /_appconfig/* path prefix and the invented AppConfig. X-Amz-Target namespace, which duplicated every operation on a wire contract the pinned model gives AppConfig no trace of

    Migration

    use AWS’s own bindings — POST and GET /applications, GET, PATCH and DELETE /applications/{ApplicationId}, …/environments, …/configurationprofiles, …/hostedconfigurationversions and GET, POST and DELETE /tags/{ResourceArn}

    appconfig
  • Removed

    the emulator-only /_appconfigdata/* path prefix, and the invented AppConfigData. X-Amz-Target namespace with it

    Migration

    use AWS’s bindings — POST /configurationsessions, and GET /configuration?configuration_token=<token>

    appconfigdata
  • Removed

    the non-AWS AppConfig-Configuration-Version response header on GetLatestConfiguration

    Migration

    the configuration payload and the Next-Poll-Configuration-Token, Next-Poll-Interval-In-Seconds and Content-Type headers are the operation’s whole modeled response; AWS never sent a version header here

    appconfigdata
  • Removed

    the invented AWSBackup. X-Amz-Target namespace 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, GET and DELETE /backup-vaults/{BackupVaultName}, GET /backup-vaults, PUT and GET /backup/plans, and GET, POST and DELETE /backup/plans/{BackupPlanId}

    backup
  • Removed

    the emulator-only /_bedrock/* path prefix, the invented Bedrock. X-Amz-Target dispatch, and the Smithy RPC v2 endpoint that came with it

    Migration

    call AWS’s own paths — bedrock-runtime is modelled restJson1 and sends no target header, so an AWS SDK was never able to use any of the three

    bedrock
  • Removed

    the undocumented singular /2020-05-31/distribution/{id}/monitoring-subscription alias. 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

    cloudfront
  • Removed

    the emulator-only GET /addons/{addonName}/versions and GET /addons/{addonName}/configuration routes

    Migration

    use AWS’s paths — GET /addons/supported-versions?addonName=… and GET /addons/configuration-schemas?addonName=…&addonVersion=…, both of which take every input as a query parameter

    eks addons
  • Removed

    the emulator-only POST /clusters/{name}/node-groups/{nodegroupName}/updates and POST /clusters/{name}/addons/{addonName}/updates routes

    Migration

    drop the trailing s and use AWS’s paths — .../node-groups/{nodegroupName}/update-version and .../addons/{addonName}/update

    eks nodegroups addons
  • Removed

    the DescribeAccessPolicy and UpdateIdentityProviderConfig capability 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 from ListAccessPolicies, which returns the whole policy; replace an identity-provider-config update with DisassociateIdentityProviderConfig followed by AssociateIdentityProviderConfig, which is what AWS models

    eks
  • Removed

    the emulator-only /v2/clusters path 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

    msk
  • Removed

    MSK’s X-Amz-Target dispatch and its typed JSON/CBOR surface, a second copy of the REST handlers for a protocol AWS does not model for kafka; AWS::MSK::Cluster and AWS::MSK::Configuration provisioning now dispatches over MSK’s REST routes

    Migration

    call MSK over HTTP the way the AWS SDKs do — no MSK operation carries an X-Amz-Target header or a Smithy RPC v2 binding in the pinned model, so nothing an SDK sends is affected

    msk cloudformation
  • Removed

    the emulator-only /_opensearch/* path prefix and the invented OpenSearch. X-Amz-Target namespace, 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, GET and DELETE /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=… and POST /2021-01-01/tags-removal

    opensearch
  • Security

    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 with OVERCAST_ENFORCE_IAM=true an S3 bucket named api was 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

    with OVERCAST_ENFORCE_IAM=true an unsigned request under /api is now refused rather than served, and a signed one is evaluated wherever Overcast can name the operation — grant the principal the s3: actions it needs on a bucket named api, and kafka:ListClustersV2 / kafka:CreateClusterV2 for MSK’s v2 cluster API. Note the prefix: the [iam] entry above renames MSK’s actions from Overcast’s msk: to AWS’s kafka: in this same release, so a policy written against msk: will not grant

    s3 msk
Added 20
  • UpdateApplication is implemented, at PATCH /applications/{ApplicationId}; an omitted Name or Description leaves the stored value alone

    appconfig
  • the create operations apply an inline Tags map, CreateHostedConfigurationVersion honours the Latest-Version-Number header with a ConflictException, ListConfigurationProfiles honours type and ListHostedConfigurationVersions honours version_label

    appconfig
  • every list operation paginates on max_results and next_token and answers a NextToken; a token that cannot be decoded gets a BadRequestException in place of a silent restart at the first page

    appconfig
  • the vault and plan operations honour the members AWS models:

    • ListBackupVaults and ListBackupPlans page on maxResults and nextToken, with ListBackupVaults also filtering on vaultType and shared and an undecodable token getting an InvalidParameterValueException in place of a silent restart at the first page; CreateBackupVault validates the vault name against the pattern AWS documents; DescribeBackupVault honours backupVaultAccountId; and GetBackupPlan answers not-found for a versionId other than the plan’s current one, rather than returning the current version under the requested version’s name

    backup
  • 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

    console
  • 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.rds is addressed virtual-hosted as my.rds.localhost, and refusing it would break the bucket

    dns
  • docs/networking.md gains 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 in DescribeVpcs)

    docs
  • assignPublicIp: ENABLED on an awsvpcConfiguration keeps the task reachable from outside its VPC, matching what the field buys on AWS. It was stored and ignored before

    ec2 ecs
  • OVERCAST_ECR_REGISTRY_PERSIST (default true) 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

    ecr
  • DescribeAddonVersions filters on addonName, kubernetesVersion, types, publishers and owners and pages with maxResults/nextToken; omitting addonName returns the whole add-on catalog rather than nothing, and each entry now carries its type, publisher and owner

    eks
  • ListInsights honours the modeled filter on category, Kubernetes version and status, and pages with maxResults/nextToken; an undecodable nextToken gets an InvalidParameterException in place of a silent restart at the first page

    eks
  • 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 MaximumRetryAttempts and 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 a DeadLetterConfig configured, 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
  • DeadLetterConfig on a function is honoured:

    • an asynchronous invocation that fails is delivered to the SQS queue or SNS topic TargetArn names, carrying the original event as the message body and the RequestID, ErrorCode and ErrorMessage message attributes AWS documents. It is stored and echoed by CreateFunction, UpdateFunctionConfiguration, GetFunction and GetFunctionConfiguration, and an explicitly empty TargetArn removes 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. PutFunctionEventInvokeConfig on-failure destinations — a different feature, carrying the invocation record rather than the event — remain unimplemented outside event source mappings

    lambda
  • PutFunctionEventInvokeConfig and its family — Get, Update, Delete and ListFunctionEventInvokeConfigs — 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. Put overwrites and removes members the request omits, Update leaves 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 XML NoSuchBucket a 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, with condition reading Success or RetriesExhausted and approximateInvokeCount naming the attempt that settled it. SQS, SNS, Lambda and EventBridge destinations are delivered to, with an EventBridge entry carrying the lambda source and AWS’s Lambda Function Invocation Result - Success/- Failure detail-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 returns 501 rather than accepting a configuration whose records would never be written, and an S3 on-success destination is rejected exactly as AWS rejects it

    lambda
  • ListClustersV2, with clusterNameFilter, clusterTypeFilter, maxResults and nextToken read from the query string where AWS binds them; a nextToken that cannot be decoded gets a BadRequestException in place of a silent restart at the first page

    msk
  • ListDomainNames honours the engineType query parameter, and a domain’s EngineType follows from its EngineVersion instead of being reported as OpenSearch whatever engine it runs; and CreateDomain applies an inline TagList at creation, so ListTags sees a tagged create without a second call

    opensearch
  • PubliclyAccessible on DB instances — CreateDBInstance accepts it, ModifyDBInstance changes it, and CreateDBInstance, ModifyDBInstance and DescribeDBInstances all 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 — true without a DBSubnetGroupName, because that instance lands in the region’s default VPC and Overcast seeds that VPC with an internet gateway, and false with 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 no PubliclyAccessible, and CreateDBCluster does not accept one

    rds
  • 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 at PutFunctionEventInvokeConfig for the settings this page does not edit, so nobody looks for a field that is not there

    web lambda
Changed 7
  • Converse returns a complete ConverseResponse: usage.totalTokens is present, as the AWS model requires, and the token counts and metrics.latencyMs now read zero because no inference is performed

    bedrock
  • InvokeModel treats its request and response bodies as the opaque model-specific payloads AWS models, rather than reading {"prompt":…} and answering with Converse’s shape; the canned reply is a single overcastEmulator field, and an absent payload gets a ValidationException

    bedrock
  • every suite’s lambda-crud:UpdateFunctionConfiguration now sends a DeadLetterConfig and 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 broke cdk deploy for 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

    compat
  • CreateFunction and UpdateFunctionConfiguration no longer answer 501 when the request carries DeadLetterConfig.

    • Under CloudFormation that refusal failed the resource, and with it the stack and the whole cdk 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.Destination may 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 same internal/eventtarget dispatcher EventBridge and Pipes use, so a destination that does not exist reports the sink’s own error instead of silently going nowhere

    lambda
  • a malformed request is answered with BadRequestException, the only client-error shape the kafka model declares, in place of ValidationException

    msk
  • ResourceNotFoundException carries HTTP 409, the status AWS documents for it, in place of 404, and a store failure is reported as OpenSearch’s own InternalException

    opensearch
Fixed 83
  • every AppConfig operation is served at the binding AWS models — /applications and everything beneath it, and /tags/{ResourceArn} — so an unmodified SDK, CDK construct or aws appconfig … call reaches it instead of a bare 404

    appconfig
  • ListTagsForResource, TagResource and UntagResource reach 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 answers ResourceNotFoundException

    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
  • AWS::AppConfig::Application, ::Environment and ::ConfigurationProfile provision over the modeled REST binding, signed as appconfig so the dispatcher does not hand them to AppRegistry

    appconfig cloudformation
  • AppConfig requests are classified as appconfig rather than appregistry, 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

    appconfig router
  • both operations are served at AWS’s own bindings — POST /configurationsessions and GET /configuration — so an unmodified SDK, CDK construct or aws appconfigdata … call reaches them instead of answering 501, and GetLatestConfiguration reads the session token from the configuration_token query 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 gets BadRequestException, and a store failure while reading a session is reported as InternalError in place of an invalid-token error, with a single undecodable session record skipped and logged

    appconfigdata
  • detectService claims /configuration and /configurationsessions, so these requests are logged and IAM-authorised as appconfigdata rather than falling through to S3’s bucket routes

    appconfigdata router
  • EvaluateCode and EvaluateMappingTemplate are served at the bindings AWS models — POST /v1/dataplane-evaluatecode and POST /v1/dataplane-evaluatetemplate — instead of under /v1/apis/{apiId}/…, so an unmodified SDK, CDK construct or aws appsync evaluate-code call reaches them for the first time

    appsync
  • EvaluateCode reads context as the JSON string AWS models rather than an object, so a spec-shaped request no longer answers SerializationException; both operations now validate runtime, code, template and context against the model, and both answer the modeled response envelope with evaluationResult, error, stash and logs

    appsync
  • an unimplemented path under /v1/apis/{apiId}/ answers a NotImplemented AWS error instead of a bare 404 from the sub-router

    appsync
  • every AWS Backup operation is served at the binding AWS models — vaults under /backup-vaults and plans under /backup/plans — so an unmodified SDK, CDK construct or aws 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 — ListBackupVaults at GET /backup-vaults/, and ListBackupPlans and CreateBackupPlan at /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

    backup
  • UpdateBackupPlan mints a new VersionId per update and returns the modeled CreationDate, 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
  • AWS::Backup::BackupVault and AWS::Backup::BackupPlan provision 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 cloudformation
  • AWS Backup requests are classified from their path prefixes, so they are logged and authorised as backup rather than falling through to S3 when unsigned

    backup router
  • InvokeModel and Converse are served at the paths AWS binds them to — POST /model/{modelId}/invoke and POST /model/{modelId}/converse — so an unmodified SDK reaches them instead of getting a 501; modelId may be an ID or an ARN

    bedrock
  • 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’s Console-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 exceeded against AWS::CloudFormation::Stack, naming no resource because no resource was involved: every resource had been created and the stack had reached CREATE_COMPLETE before 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’s persistent.pendingWrites and /_overcast/debug/metrics’ flushHistory are 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

    cloudformation
  • 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’s Metric.createAlarm on a MathExpression emits one of these per function, and a 501 failed 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’s StateReason, an x-overcast-emulation-limitation response header, and the CloudFormation ResourceStatusReason, carried on the CREATE_COMPLETE event 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-level Namespace/MetricName still gets the 400 ValidationError AWS itself answers with

    cloudwatch
  • 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 semantic warning tokens, which follow the theme

    console
  • 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’s service field, and put in front of IAM enforcement as an s3: action. Four separate causes fed that one fallback. Query-protocol requests were the largest: detectService takes 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 and body()’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 declares awsQuery alongside awsJson1_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 the Smithy-Protocol header, because without it the router really does hand the same path to S3, where service is 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-events and eventbridge are one service under two modeled identities, so all 51 shared X-Amz-Target collisions were never collisions, and DocumentDB, Neptune and RDS share the 2014-10-31 API version and most of its action names across 71 query collisions where Overcast implements only RDS, which each Query service’s OwnsVersion already 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 documented s3 fallback, as the X-Amz-Target step 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 old requestBodyTruncated / responseBodyTruncated booleans 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 reads persistent state flush failed is a log line, and the ECS pull failure that reads ImageNotFoundException is 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 “the RunTask that returned AccessDenied” 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 DescribeStacks polls 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 was OVERCAST_DEBUG_TRACE_PINNED traces 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 to OVERCAST_DEBUG_TRACE_BUFFER and 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 the hopRequest and hopResponse match 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. And GET /_overcast/debug/traces/count carries 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, with count and capacity keeping their previous meanings

    debug
  • the request that explains a failure is still there when you go looking.

    • A cdk deploy pushes 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 the 400 that 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 to OVERCAST_DEBUG_TRACE_PINNED (default 1000). A burst is no longer capped at the buffer size: retention grows past OVERCAST_DEBUG_TRACE_BUFFER (default 1000) up to OVERCAST_DEBUG_TRACE_CEILING (default 10000) and holds the overflow for OVERCAST_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.recordAWSError finds its recorder by type-asserting whichever ResponseWriter the 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. RequestEvents builds a second responseWriter around Logger’s, which cost every service its recorded error; DynamoDB wraps again for the X-Amz-Crc32 header 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 outside Logger’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 its aws_error_code field, 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

    debug
  • 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

    dns
  • 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 watch is the right answer, how Lambda and ECS hot reload differ, the shared OVERCAST_HOT_RELOAD flag family, and a full Laravel-on-Fargate walkthrough. It leads with the mistakes that make a correctly configured mount look broken — OPcache’s validate_timestamps=0 ignoring your edits, a bind-mounted vendor/ crossing a VM boundary on every request, and php artisan queue:work caching the application in memory.

    docs
  • DescribeImages raises ImageNotFoundException for an imageIds entry 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-throwing DescribeImages as “already published”, so cdk deploy skipped 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 with CannotPullContainerError … status 404 … not found, naming a registry that had never received the image. A call that names no imageIds still returns an empty list, because only a requested identifier can be missing

    ecr
  • repositoryUri names the registry this run is serving, re-minted on every read instead of frozen into the repository at CreateRepository.

    • The address is the registry container’s published port; repositories are persisted and the registry is not, so a cdk bootstrap that ran before the registry was up sent every later deploy’s docker push at 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 :4510 in 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
  • repositoryUri and proxyEndpoint name localhost rather than OVERCAST_HOSTNAME whenever 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 to localhost and bypasses proxies for it, while a hostname like localhost.overcast.sh is an ordinary domain, so a machine with a proxy configured sent docker push to the proxy — proxyconnect tcp: dial tcp 192.168.65.1:3128: i/o timeout on 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 its insecure-registries needs

    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, ListImages and BatchGetImage described 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 by PutImage is left alone, having never been in the registry

    ecr
  • a task that fails to start records the reason on the container that failed, not on every container in the task.

    • A launch failure — CannotPullContainerError for 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 are STOPPED with none; the task’s own stoppedReason is 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 with OVERCAST_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 bare overcast:hot-reload-path works too, the same key Lambda takes.

    ecs
  • RegisterTaskDefinition accepts tags and 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 stoppedReason naming 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.sourcePath becomes a bind mount from the container instance, and dockerVolumeConfiguration a 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.sourcePath and dockerVolumeConfiguration on a Fargate task definition, autoprovision on 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 dockerVolumeConfiguration with scope: shared and autoprovision: false adopts 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=true now keeps volumes too.

    ecs
  • ECS_KEEP_CONTAINERS, RDS_KEEP_CONTAINERS and LAMBDA_KEEP_CONTAINERS now 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

    ecs rds lambda
  • the export-container and materialization-helper sweep is scoped to this instance’s own resources too, on the same identity labels.

    efs
  • 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, UpdateAddon and UpdateNodegroupVersion are served at the paths and methods AWS models, so an unmodified SDK, CDK construct or aws 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: DescribeAddonConfiguration answers the schema for the add-on version that was asked for and rejects a missing addonName or addonVersion, where it used to ignore the requested version and answer from a table that held only one; and UpdateNodegroupVersion accepts releaseVersion and launchTemplate and no longer rejects a request that omits version, the model marking none of them required, so aws eks update-nodegroup-version --release-version … was refused

    eks
  • the support matrix marks UpdateKubeconfig as an Overcast extension rather than an AWS operation, and links the aws eks update-kubeconfig CLI 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.subnetIds name, instead of always landing on the default network. A cluster placed into a VPC that cannot take containers goes to FAILED with a ConfigurationConflict issue rather than starting a control plane nothing in the VPC can resolve

    eks
  • 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
  • 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.

    elasticache cloudformation
  • 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 the L, LW, <day>W, <day>L and <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 written cron(15 10 ? * MON-FRI *) was stored happily and then never fired, “MON-FRI” having matched no number and nothing having said so

    eventbridge scheduler
  • PutRule refuses 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 answered cron expression has no next fire within search window, after walking 2.6 million candidate minutes to get there

    eventbridge
  • IAM enforcement no longer lets signed requests through unauthorized because it could not work out which service they were for.

    • detectService returned 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, and IAMEnforce’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: with OVERCAST_ENFORCE_IAM=true every 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

    iam msk
  • 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_RELOAD enables bind-mount source reload for every compute service at once, with OVERCAST_LAMBDA_HOT_RELOAD and OVERCAST_ECS_HOT_RELOAD overriding it per service in either direction — so one service can be opted out of an umbrella true. OVERCAST_LAMBDA_HOT_RELOAD on its own behaves exactly as before.

    lambda
  • GetFunctionConcurrency returns 200 with an empty body for a function that has no reserved concurrency, as AWS does.

    • It answered 404 ResourceNotFoundException instead, 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’s ResourceNotFoundException actually models. A reservation of 0 is 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
  • 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 empty ResponseMetadata for 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 through protocol.WriteAWSJSON, as did SESv2’s GetEmailIdentity and ListTagsForResource. 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

    lambda ecs ses
  • SES’s admin identity routes are logged as ses rather than internal. /_overcast/ses/identities was already under that prefix but only the inbox was named, so the identity routes fell through to the catch-all

    logging
  • 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, so CreateClusterV2 and DescribeClusterV2 are reachable from an unmodified SDK instead of answering 501; they were registered at the invented /v2/clusters in every release since v0.0.1-alpha.0

    msk
  • CreateClusterV2 rejects a request naming both provisioned and serverless, or neither, in place of silently creating a serverless cluster the caller never asked for

    msk
  • ListClusters’ clusterNameFilter matches 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.clientSubnets name, 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

    msk
  • every OpenSearch operation is served at the binding AWS models — the domain surface under /2021-01-01/opensearch/, and ListDomainNames, AddTags, ListTags and RemoveTags directly under /2021-01-01/ — so an unmodified SDK, CDK construct or aws opensearch … call reaches it instead of answering 501

    opensearch
  • DeleteDomain removes 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
  • AWS::OpenSearchService::Domain dispatches 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 cloudformation
  • OpenSearch requests are classified from the /2021-01-01 path prefix, so they are logged and authorised as opensearch rather than falling through to S3 when unsigned

    opensearch router
  • x-overcast-emulation-limitation is 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 from x-emulator-unsupported, which marks a 501 — the difference being whether the request was carried out at all

    protocol
  • 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
  • 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 FileSystemConfigs and ECS efsVolumeConfiguration mounts read and write. Matching was the other half: RDS, ElastiCache, MSK and ECS all paired a stored record with a container on overcast.resource-id alone, 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 a mydb or a sessions — and StopDBInstance stopped 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 default memory backend 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 with docker rm $(docker ps -aq --filter label=overcast.managed=true) and docker volume prune --filter label=overcast.managed=true. Leaking is the safe direction to err. Found by running two instances against one daemon.

    rds ecs efs elasticache lambda msk
  • a Query-protocol request whose body is too large to parse as a form is answered 413 RequestEntityTooLarge instead of 501 NotImplemented.

    • The 501 came from the shared POST / dispatcher treating a failed form parse as “no service claims this”, so an oversized CreateStack came back carrying x-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 new protocol.MaxQueryRequestBody states the ceiling net/http was 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

    router
  • 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, so cron(0 12 ? * 2 *) fired on Tuesday where AWS fires it on Monday, and 7, being outside the range it allowed, never fired at all

    scheduler
  • 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

    state
  • 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 create fails — 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

    tests
Removed 1
  • the invented AppSync. X-Amz-Target namespace 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

    appsync
Security 1
View on GitHub
v0.0.1-alpha.34 prerelease 11 assets
15 added · 3 changed · 44 fixed · 1 removed · 6 breaking acm cloudtrail iam kinesis scheduler +17 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.34

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.34

Downloads

Changelog

15 added · 3 changed · 44 fixed · 1 removed · 6 breaking

Breaking changes

  • Changed

    overcast-slim has no SQLite, so a volume mounted at /data no longer gives it persistent storage:

    • auto resolves to memory, and hybrid and persistent refuse to start. This is what the image was always documented to do — it only appeared to persist because the wrong binary was shipped

    Migration

    use wal, which is durable and needs no SQLite, or switch to the full overcast image

    docker
  • Changed

    UpdateSchedule replaces the whole schedule, as AWS does.

    • An optional member the caller leaves out of the request — Description, ScheduleExpressionTimezone, State, StartDate, EndDate, or anything inside Target — ends up unset, where it used to keep its stored value; the schedule keeps its name, group, ARN and CreationDate

    Migration

    send the whole schedule, not just the parts you are changing — read it with GetSchedule, change what you mean to change, and send the result back

    scheduler
  • Changed

    LAMBDA_NETWORK, ECS_NETWORK, RDS_NETWORK, ELASTICACHE_NETWORK, MSK_NETWORK, EKS_NETWORK and EFS_NETWORK are replaced by a single OVERCAST_NETWORK (default overcast).

    • Every container Overcast starts now shares one data plane instead of one network per emulator service, which is what made cross-service reachability a per-service bug; the seven per-service networks are removed at startup once nothing is attached to them

    Migration

    unset the old variables. If you set one to a custom value, set OVERCAST_NETWORK to it instead; if you joined a compose service to overcast_lambda or another per-service network, join overcast instead.

    config
  • Fixed

    a stack operation is now checked against the stack’s state: a CREATE change set naming a stack that already exists is AlreadyExistsException, at CreateChangeSet and at ExecuteChangeSet, and UpdateStack (with CreateChangeSet for ChangeSetType: UPDATE) is ValidationError from anything but a last known stable state — or a CREATE_FAILED / UPDATE_FAILED stack the update resumes with DisableRollback

    Migration

    delete a ROLLBACK_COMPLETE stack before deploying over it, as the AWS CLI and CDK already do; resume a CREATE_FAILED or UPDATE_FAILED stack with --disable-rollback, or recover it with RollbackStack

    cloudformation
  • Fixed

    CreateSchedule and UpdateSchedule validate the schedule name, the schedule expression, FlexibleTimeWindow.Mode and State, answering ValidationException where a schedule that could never fire used to be stored

    Migration

    correct the offending field — a name is 1-64 characters of [0-9a-zA-Z-_.], an expression must be rate(...), cron(...) or at(...), FlexibleTimeWindow.Mode must be OFF or FLEXIBLE, and State must be ENABLED or DISABLED

    scheduler
  • Removed

    the emulator-only /_scheduler/* path prefix

    Migration

    use AWS’s paths — /schedules/{Name} with GroupName in the body on create/update and ?groupName on get/delete, /schedules, /schedule-groups/{Name}, /schedule-groups, and /tags/{ResourceArn}

    scheduler
Added 15
  • TagResource, UntagResource and ListTagsForResource — the modern aliases of the *Certificate tag operations, addressing the certificate by ResourceArn. RequestCertificate applies inline Tags at creation

    acm
  • Trails are taggable: AddTags, RemoveTags and ListTags, plus an inline TagsList on CreateTrail

    cloudtrail
  • Managed policies and instance profiles are taggable:

    • TagPolicy/UntagPolicy/ListPolicyTags and TagInstanceProfile/UntagInstanceProfile/ListInstanceProfileTags; and CreateUser, CreateRole, CreatePolicy and CreateInstanceProfile apply inline Tags at creation

    iam
  • TagResource, UntagResource and ListTagsForResource — the ARN-addressed tag operations the AWS CLI’s kinesis tag-resource uses, reading and writing the same tag set as AddTagsToStream — and CreateStream applies inline Tags at creation

    kinesis
  • ListSchedules and ListScheduleGroups honour MaxResults, NextToken and NamePrefix, and ListSchedules also filters on State; a NextToken that cannot be decoded gets a ValidationException in place of a silent restart at the first page

    scheduler
  • SESv2 resource tagging:

    • TagResource, UntagResource and ListTagsForResource on /v2/email/tags, for email identities. CreateEmailIdentity applies inline Tags at creation, GetEmailIdentity reports them, and deleting an identity drops them

    ses
  • CreateTopic applies inline Tags at creation, as AWS does; a repeat idempotent call leaves an existing topic’s tags untouched

    sns
  • Transfer Family servers and users are taggable:

    • TagResource, UntagResource and ListTagsForResource, plus inline Tags on CreateServer and CreateUser. DescribeServer and DescribeUser report them

    transfer
  • the object inspector opens on a named version: it shows that version’s metadata and body, its copy-URL carries ?versionId=, and a version that cannot be read is explained rather than left blank — a delete marker as the tombstone it is, an id that no longer exists as a listing that has gone stale

    web s3
  • a Clear button on the log events viewer empties the buffer on screen without stopping the tail, so a live stream can be read from a known point instead of scrolling past everything that came before. The events are hidden, not lost — “Show N earlier” puts them back, and so does Refresh

    web cloudwatch
  • an expiry hint on noncurrent version rows, counted the way the sweeper counts it — from the moment the version’s successor was written, and skipping the newest versions a rule’s NewerNoncurrentVersions retains

    web s3
  • search and sort controls on the bucket object browser: filter by name or prefix, switch between this-folder and fully recursive listings, and sort by name, size or last modified

    web s3
  • a Versioning panel on the bucket Configuration tab, which is the first way to enable or suspend versioning from the console rather than through the API.

    • The state was already reported there; only the control was missing. Suspending says what it actually does — new writes are stored with version id null, and every version already stored is kept, which is not the same as turning versioning off

    web s3
  • each region seeds a default VPC on first use, with a default subnet per availability zone, an attached internet gateway, a main route table carrying the default route, and a default security group — DescribeVpcs --filters Name=isDefault,Values=true and CDK’s Vpc.fromLookup(isDefault: true) previously found nothing.

    • Its backing network is the shared data plane, so “no VPC” and “the default VPC” are the same place; DeleteVpc on it removes the record and leaves the network, and an internet-gateway change on it is ignored rather than recreating the network under every running container

    ec2
  • a live-mode cluster’s API server carries DNS aliases on the shared data plane. The endpoint DescribeCluster returns is still the published host port, so this is groundwork rather than a complete path

    eks
Changed 3
  • the registry asks for a fixed host port (OVERCAST_ECR_REGISTRY_PORT, default 4510 — LocalStack’s registry port) so repositoryUri is stable across restarts, falling back to an ephemeral port when it is taken; the port repositoryUri then advertises is selected by the Docker daemon itself proving it can dial it — a dual-stack ephemeral publish can split IPv4 and IPv6 onto different host ports, and only the daemon knows which family its localhost reaches — and a path nothing answers on is one actionable warning instead of a CannotPullContainerError in a later task launch

    ecr
  • CreateSchedule and CreateScheduleGroup answer 200, the status code their AWS models bind, in place of 201

    scheduler
  • containers are created on a control plane that carries the Runtime API and the emulator endpoint, separately from the data plane they reach other resources on — the split a VPC needs in order to restrict one without severing the other

    lambda ecs
Fixed 44
  • tagging a certificate that does not exist is a ResourceNotFoundException instead of succeeding and stranding the tags under an ARN nothing owns, and ListTagsForCertificate returns tags in a stable key order instead of a different order per call

    acm
  • a create that fails with rollback disabled leaves StackStatusReason set to AWS’s summary of which resources failed, rather than to the underlying service error — which stays on the resource and its event, where AWS keeps it

    cloudformation
  • stack operations accept the stack ARN wherever AWS does — DescribeStacks, DescribeStackEvents, ListStackResources, DescribeStackResources, GetTemplate, GetTemplateSummary, UpdateStack, DeleteStack, CreateChangeSet, and DeleteChangeSet resolved StackName by name only, so clients that poll by the stack ID (CDK’s deploy monitor among them) got ValidationError: Stack [arn:…] does not exist for a stack that was right there. Conversely, a CREATE-type CreateChangeSet naming an unknown stack ARN no longer mints a placeholder stack literally named the ARN — an ARN is a handle to an existing stack, and now answers “does not exist”

    cloudformation
  • mutating a deleted stack now behaves as on AWS — UpdateStack on a DELETE_COMPLETE stack reports it does not exist instead of resurrecting it, and a repeat DeleteStack is a no-op success instead of re-running the delete and appending a second wave of delete events

    cloudformation
  • re-deploying a stack that failed replaces the previous attempt’s resource records instead of adding to them — a second cdk deploy over a ROLLBACK_COMPLETE stack left two records per logical ID, so DescribeStackResources answered with the first run’s failure reason rather than the reason this run actually failed for

    cloudformation
  • an update re-provisions a resource the stack no longer has behind a record — one whose create failed before naming anything, or one a rollback deleted — instead of matching it by logical ID, reading the missing property hash as “unchanged” and skipping it

    cloudformation
  • a stack update that changes an AWS::CloudWatch::Alarm’s tags now applies them, through TagResource/UntagResource as real CloudFormation does. PutMetricAlarm applies Tags only when it creates an alarm, so the update reached UPDATE_COMPLETE having changed nothing

    cloudformation cloudwatch
  • alarm tagging works over the JSON protocol the AWS CLI and the SDKs speak, not only the Query form — ListTagsForResource, TagResource and UntagResource were absent from the JSON dispatch, so alarm tags were unreadable with aws cloudwatch list-tags-for-resource. The same operations now reject an unknown or non-CloudWatch ResourceARN with AWS’s ResourceNotFoundException/InvalidParameterValue instead of quietly tagging a resource that does not exist, and hold tag sets to AWS’s rules — 50 tags per resource, no aws: key prefix, key and value length limits — on TagResource and on the Tags a PutMetricAlarm applies at creation

    cloudwatch
  • PutMetricData aggregates datapoints that share a timestamp instead of keeping only the last one — two datums in one call with no Timestamp take the same clock reading on a coarse platform timer, and were recorded as one value rather than being summed into the period

    cloudwatch
  • the published overcast-slim image is a slim build.

    • Every caller that selected the slim stage had to pass a matching NOSQLITE=1 build arg as well, and the release workflow did not, so -slim shipped the console binary — embedded web console, /_mcp, SQLite and all, the same size as the console image. The build target now decides the flavour on its own, and each builder asserts what it produced

    docker
  • container start failures now carry the daemon’s error message instead of a bare status code

    docker
  • the emulated registry is reachable from the Docker daemon in the shapes that previously failed outright.

    • Its port is published dual-stack instead of IPv4-only — the v4-only binding was invisible to Docker Desktop’s port forwarding, leaving the daemon unable to reach its own registry at localhost, so every docker push and every ECS pull of an ECR image died on a dial. Its htpasswd file is copied into the container rather than bind-mounted from a path only Overcast’s own filesystem has, so docker push no longer refuses on every containerised deployment. And ListImages/DescribeImages/BatchGetImage reconcile pushed images when Overcast itself runs in a container, probing the addresses the registry can actually be at rather than assuming Overcast’s own loopback

    ecr
  • two Overcast instances sharing one Docker daemon no longer interfere with each other.

    • The registry container’s name is per-claim — port-derived for a fixed port, random for an ephemeral one — instead of a shared singleton, so startup removes nothing but a legacy singleton-named leftover and shutdown removes only the instance’s own container; and the port repositoryUri advertises is proved to be this instance’s registry rather than merely a registry, because the daemon-vantage startup probe carries the instance’s own credentials. Interleaved ephemeral dual-stack publishes could previously leave one instance advertising a port serving the other’s registry, and every token it then issued failed authentication against that registry’s htpasswd

    ecr
  • the registry’s startup is no longer raced or silent: GetAuthorizationToken and repositoryUri wait for it to actually answer before they are served — a token handed out ahead of a still-booting registry made docker login fail with EOF on loaded machines — a container name still held by a predecessor being removed is waited out rather than treated as fatal, and every startup step that fails says so in the log

    ecr
  • a task definition whose image is a CDK container asset now starts: the {account}.dkr.ecr.{region}.amazonaws.com/… reference CDK synthesises is resolved to the registry Overcast serves and pulled with the credentials GetAuthorizationToken issues, instead of reaching real AWS and failing with CannotPullContainerError: … no basic auth credentials

    ecs ecr
  • awslogs log lines longer than 16 KiB are reassembled the same way, having had the same stamps spliced into them

    ecs
  • an execution environment that fails to initialise is now explained: the container, the Runtime API endpoint it was handed, whether anything ever connected there, and what it printed

    lambda
  • a PackageType=Image function whose image is a CDK container asset now runs:

    • the {account}.dkr.ecr.{region}.amazonaws.com/… reference CDK synthesises into Code.ImageUri is resolved to the registry Overcast serves and pulled with the credentials GetAuthorizationToken issues, and the container is created from that reference, instead of the pull reaching real AWS and failing with no basic auth credentials. The function still reports Code.ImageUri exactly as it was deployed

    lambda ecr
  • a log line longer than 16 KiB reaches CloudWatch as the function wrote it, instead of with an RFC3339Nano timestamp spliced through the middle of it at every chunk boundary — Docker stamps each 16 KiB frame rather than each message, and reassembling the frames carried the continuation stamps into the line, cutting tokens of a serialized error object in half

    lambda
  • a log stream is named with the 32 hex characters AWS uses for the execution environment GUID (2026/08/10/[$LATEST]312c2d81e2e64af58dbe557754f9aa13) rather than 26, which failed a caller matching stream names against AWS’s shape

    lambda
  • an invoke that asks for a log tail (X-Amz-Log-Type: Tail) carries the function’s own output, not only the START / END / REPORT lines. A warm invocation arriving while the log reader was still working through the previous one’s output was reported as a function that had printed nothing, and answered with a tail missing its console.log

    lambda
  • every EventBridge Scheduler operation is served at AWS’s own path, so an unmodified SDK, CDK construct or aws scheduler … call reaches it instead of answering 501

    scheduler
  • DeleteScheduleGroup deletes the schedules inside the group, as AWS does; they used to be left behind, unreachable through the API and still firing on every engine tick

    scheduler
  • StartDate and EndDate survive a CreateSchedule made over the JSON/CBOR protocols, the path CloudFormation takes; the REST routes and that dispatch now run one implementation rather than two copies that had drifted

    scheduler
  • a store failure while reading a schedule or a group is reported as InternalError in place of ResourceNotFoundException, and a single undecodable record is skipped and logged instead of disappearing from a listing without trace

    scheduler
  • a schedule whose target is slow, unreachable or working through its RetryPolicy no longer holds up every other schedule in the emulator:

    • the engine hands each due firing to a pool of delivery workers instead of delivering them one after another on the tick itself. A schedule is still never fired twice at once, so its own firings stay in order

    scheduler
  • a sparse cron(...) schedule no longer burns CPU on every engine tick.

    • The evaluator advanced a minute at a time until something matched, so it cost one iteration per minute between now and the next firing — around 525,000 of them, once a second, for a yearly schedule. It now advances field by field, and parsing a cron expression allocates nothing. That rewrite also fixed a step over a range: cron(0 9-17/4 * * ? *) now fires at 09:00, 13:00 and 17:00, where the range was unreadable to the old parser, which fell back to stepping the whole field from zero and fired at 00:00, 04:00, 08:00, 12:00, 16:00 and 20:00

    scheduler
  • deleting a schedule sticks even when an UpdateSchedule for the same schedule is in flight. The update read the record, built its replacement and wrote it back without holding a lock, so a DeleteSchedule that landed in between was written over — the caller was told the delete had succeeded and the schedule stayed stored and kept firing

    scheduler
  • the notification envelope’s UnsubscribeURL names the port the publisher reached Overcast on, rather than the configured one — on an instance published on a remapped port the link pointed at a port nothing was listening on

    sns
  • the version-history view addresses each row by its version id, so downloading or inspecting a revision returns that revision. Every row resolved by key alone, which S3 answers with whichever version is current — a key with three stored revisions handed out the newest one whichever row was clicked

    web s3
  • the stack page’s failure banner reports the most recent failure rather than the first resource listed

    web
  • the log viewer’s level badge labels every row whose level it detects, including a runtime’s plain-text console.* lines — previously only a JSON document was labelled and a text line got the row tint alone — and the badges are legible in light mode, where the warning badge used to be lighter than the row behind it. A long log stream name no longer overflows its column into the message text in the all-streams view

    web cloudwatch
  • a log event carried by both a Live Tail session and a refresh of the events list is shown once, in place of a duplicate row

    web
  • lifecycle rules that act on version history are no longer drawn as rules with no actions. NoncurrentVersionExpiration, NoncurrentVersionTransition and ExpiredObjectDeleteMarker were dropped on the way into the console, so a versioned bucket whose entire retention policy is one of them showed a rule that appeared to do nothing

    web s3
  • the version column shows the end of a version id rather than the start, so two versions of one key can be told apart. Overcast mints ids with the timestamp leading, so versions written moments apart share their first characters — three versions of one key all rendered as the same eight characters followed by an ellipsis

    web s3
  • the support matrix no longer advertises DescribeCacheEngineVersions and RebootCacheCluster as supported — both are routed to a NotImplemented stub and always answer 501, so they are marked Unsupported until they are emulated

    elasticache
  • a cache node is reachable from an ECS task, and under every hostname its endpoint could have been minted with — previously it was attached only to the Lambda network and registered only the configured name, so an ECS task resolving it reached Overcast on the Redis port and hung rather than failing (#872)

    elasticache
  • CreateEmailIdentity is served at POST /v2/email/identities, the binding AWS models, so the SDKs and aws sesv2 create-email-identity reach it instead of an error.

    • It was registered under PUT, which no client sends, leaving the operation — and the inline Tags it applies at creation — unreachable. The PUT binding is gone; AWS never had one.

    ses
  • DescribeVpcs honours VpcId.N and the vpc-id and isDefault filters, and reports isDefault from the record instead of always false — CDK’s VPC lookup treats the response as already filtered and needs exactly one VPC back, so an unfiltered response would break Vpc.fromLookup for anyone holding more than one VPC

    ec2
  • NFS mount-target exports are resolvable by name from sibling containers, not only by container address

    efs
  • a live-mode cluster’s endpoint aliases use the cluster’s own region rather than the configured default, so a cluster created in another region resolves

    eks
  • live mode pulls the k3s image before it creates a control plane, so a cluster can be created on a machine that has never run one. Docker answered No such image to the create and the cluster then reported CREATING indefinitely, with the reason only in a log line — a start that fails now reaches FAILED and carries the Docker error in cluster.health.issues, so a waiter gets an answer instead of hanging

    eks
  • a containerised Overcast can bring up a live-mode cluster at all.

    • It probed the k3s API on 127.0.0.1:<published port>, which inside its own network namespace is itself, so a healthy control plane never left CREATING; it now dials the container address on the control plane when it runs beside the daemon, the same lookup the RDS, ElastiCache, MSK and EFS health checks use. DescribeCluster still returns the published host port

    eks
  • GetBootstrapBrokers returns an address the caller can actually dial:

    • a resolvable broker hostname on 9092 for a sibling container, the published port for the host. It previously returned OVERCAST_HOSTNAME:hostPort to everyone, which inside any container resolves to Overcast — a process that serves no Kafka

    msk
Removed 1
View on GitHub
v0.0.1-alpha.33 prerelease 11 assets
2 added · 1 changed · 14 fixed · 1 breaking cloudwatch iam web debug cloudformation +4 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.33

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.33

Downloads

Changelog

2 added · 1 changed · 14 fixed · 1 breaking

Breaking changes

  • Changed

    PutMetricAlarm requires Statistic, ComparisonOperator, Period, EvaluationPeriods and Threshold for an alarm on a metric

    Migration

    pass them explicitly. They are Required: No only because a PromQL alarm supplies them inside EvaluationCriteria; an alarm on a metric that omitted one used to be created with Average / GreaterThanThreshold / 60s / 1 period / 0.0 substituted, which arms an alarm nobody configured

    cloudwatch
Added 2
  • PutMetricAlarm carries more of the alarm:

    • Tags are applied at creation as on AWS, and Unit selects which datapoints an alarm evaluates, so a metric published under several units evaluates separately per unit. PromQL alarms (EvaluationCriteria) join metric math and anomaly detection in returning 501 rather than being created un-evaluated

    cloudwatch
  • permissions boundaries — PutUserPermissionsBoundary, DeleteUserPermissionsBoundary, PutRolePermissionsBoundary and DeleteRolePermissionsBoundary, plus the PermissionsBoundary parameter on CreateUser/CreateRole and the PermissionsBoundary member on the user and role responses

    iam
Changed 1
  • the Request Traces status and method filters are multi-select, so “all errors” is 4xx and 5xx ticked together rather than two separate looks, and 3xx joins the status list; the filters live in the URL, so they survive Back from a trace’s detail page and can be shared as a link

    web debug
Fixed 14
  • an AWS::CloudWatch::Alarm the template does not name deploys — CloudFormation mints {Stack}-{Logical}-{RANDOM} for an omitted AlarmName, which is what CDK’s Alarm construct and every metric.createAlarm() helper rely on, where the empty name used to reach PutMetricAlarm and come back as AWS’s own Value null at 'alarmName'.

    • Every property now reaches the alarm too: DatapointsToAlarm and TreatMissingData were dropped, so an “M out of N” alarm was evaluated as “N out of N” and notBreaching reverted to missing. A property the template omits is left out of the request rather than sent empty

    cloudformation cloudwatch
  • AWS::Events::Rule, AWS::StepFunctions::StateMachine, AWS::ApiGateway::RestApi, AWS::Scheduler::ScheduleGroup, AWS::ECR::Repository and AWS::IAM::User are named by CloudFormation when the template omits their name. Each carries the logical ID, so two unnamed resources of one type in a stack are two resources — the rule and the schedule group used to share one empty name and quietly become a single resource, and the rest failed the stack

    cloudformation
  • a stack that rolls an update back is handed its previous template and parameters again, so GetTemplate describes what is deployed rather than the attempt that failed and the next update resolves parameters from it;

    • a nested stack whose own update fails restores its child template and parameters as part of rolling its resources back, instead of keeping the failed attempt’s metadata because the parent never recorded the update as a success; and a rollback that cannot persist the metadata it restored reports UPDATE_ROLLBACK_FAILED rather than claiming the rollback completed

    cloudformation
  • UpdateStack honours the DisableRollback member of the request it was given, rather than the value the stack was created with, and an update that fails with rollback disabled keeps the resource records the attempt reached — so DescribeStackResources no longer reports pre-update properties for a resource the update already changed, and no longer drops the resources it updated before the failure

    cloudformation
  • request traces record every internal hop, including the work that continues after the response is sent — a CloudFormation or CDK deploy runs for minutes past its HTTP reply, and every hop after the first second used to be dropped.

    • DeleteStack and RollbackStack record the internal calls they make, as CreateStack and UpdateStack already did, where teardown hops were previously never recorded at all; and a stack created from a TemplateURL records the template fetch as a hop, with the S3 request it makes linking back to the CloudFormation request that triggered it

    debug cloudformation
  • hops are named and linked whatever protocol answered them:

    • a hop links reliably to the request it triggered, where S3 hops in particular used to link to nothing; hops for S3 calls name the service instead of showing a blank one, S3’s REST paths being bare bucket paths that no prefix rule can attribute; and a Query-protocol request is named by its operation however the client ordered the form parameters, where one with a large parameter ahead of Action — a CreateStack carrying a real template, typically — used to show a blank operation

    debug s3
  • permissions boundaries attached to a user or role are applied by SimulatePrincipalPolicy and by opt-in request-time enforcement, so a bounded principal no longer gets permissions AWS would deny through the boundary intersection

    iam
  • a failed CreateFunction or UpdateFunctionCode no longer leaves the stored deployment package ahead of the function’s CodeSha256, RevisionId and source metadata, so a later read or cold start cannot run code the configuration does not describe

    lambda
  • a PutObject landing the bytes a function is already running is no longer treated as a new deployment. Re-uploading an unchanged asset — or the very upload the function read at create time — leaves RevisionId, LastModified and the warm execution environment alone, instead of rotating the revision under callers using it for optimistic concurrency and forcing a needless cold start

    lambda
  • schedules fire SNS, Step Functions, Kinesis, Firehose, ECS and EventBridge event bus targets, not just Lambda and SQS — delivery goes through the same dispatcher EventBridge rules and Pipes use, so one target ARN behaves identically on a schedule and on a rule — and SqsParameters.MessageGroupId, KinesisParameters.PartitionKey, EventBridgeParameters and EcsParameters are honoured when a schedule fires. CreateSchedule and UpdateSchedule reject a target type Overcast cannot fire with a ValidationException, instead of accepting a schedule that would silently never fire

    scheduler
  • RetryPolicy and DeadLetterConfig are honoured: a failed firing is retried up to MaximumRetryAttempts (capped at 6 attempts) while MaximumEventAgeInSeconds allows, then sent to the configured SQS dead-letter queue

    scheduler
  • Lambda deployment packages carry an explicit storage-tier classification, pinning them to the SQLite-backed tier so a future change cannot silently make every stored zip memory-resident at startup

    state
  • long request paths no longer stretch the traces table off-screen — the Path column is bounded against the page’s own width and carries the full path as a tooltip — and the table’s horizontal scrollbar is reachable without scrolling to the bottom of an infinitely growing list

    web debug
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.33

View on GitHub
v0.0.1-alpha.32 prerelease 11 assets
21 added · 9 changed · 35 fixed · 4 security · 8 breaking cloudwatch-logs config lambda release s3 +14 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.32

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.32

Downloads

Changelog

21 added · 9 changed · 35 fixed · 4 security · 8 breaking

Breaking changes

  • Added

    object version history.

    • A versioning-enabled bucket mints a version id for every write, keeps the previous version as noncurrent, and answers a delete by adding a delete marker instead of removing anything; versionId addresses a specific version on GetObject, HeadObject, DeleteObject, DeleteObjects and CopyObject’s source. Suspended keeps the versions it already has and writes the single null version, and objects stored before a bucket was versioned — including objects persisted by an earlier build — are that key’s null version with nothing to migrate by hand. ListObjectVersions returns versions and delete markers in AWS’s order, keys ascending then most recently stored first, with prefix, delimiter, max-keys and resumable key-marker/version-id-marker pagination. A bucket that never enables versioning behaves exactly as before, down to the absent x-amz-version-id

    Migration

    a bucket you had already set VersioningConfiguration Enabled or Suspended on now behaves as AWS does — DeleteObject writes a delete marker rather than removing the object, and DeleteBucket reports BucketNotEmpty until the markers and the versions under them are deleted by version id. Empty such a bucket with ListObjectVersions followed by version-targeted DeleteObjects, which is what the AWS SDKs’ own cleanup loop does. Set the bucket back to unversioned if you did not mean to enable it — there is no such transition on AWS or here, so delete the bucket and recreate it

    s3
  • Changed

    a resource that refuses to be deleted now fails the stack.

    • DeleteStack stops at that resource, emits DELETE_FAILED carrying the service’s own message, and leaves the stack DELETE_FAILED with the resource still listed — as AWS does — instead of logging the refusal and reporting DELETE_COMPLETE over something still standing. IAM’s DeleteConflict (409) from DeleteRole, DeleteUser and DeletePolicy joins the non-empty S3 bucket and the deletion-protected RDS cluster in reaching the stack; an entity that is already gone, a call that could not be made, and a stub resource type stay non-fatal

    Migration

    a stack whose teardown is blocked by a dependency something outside the stack holds — an attached managed policy, an access key, a group membership — now ends DELETE_FAILED rather than DELETE_COMPLETE. Clear the dependency and run DeleteStack again; resources deleted before the failure stay deleted. A stack that owns all of its own dependencies is unaffected

    cloudformation
  • Changed

    DeleteUser, DeleteRole, DeleteGroup and DeletePolicy refuse with AWS’s DeleteConflict (HTTP 409) while the entity still has dependencies — access keys, group membership, inline or attached policies, an instance profile association, or a policy attachment — instead of deleting it and orphaning them. The message names what to clear, verbatim from AWS

    Migration

    teardown scripts must unwind an IAM entity before deleting it, exactly as they must against AWS — DeleteAccessKey, DeleteUserPolicy, DetachUserPolicy, RemoveUserFromGroup, RemoveRoleFromInstanceProfile, DeleteRolePolicy, DetachRolePolicy, DeleteGroupPolicy, DetachGroupPolicy. CloudFormation stack teardown needs no change: AWS::IAM::Policy now removes its inline document from the entities it named, and reverse dependency order already puts instance profiles before their roles

    iam
  • Fixed

    CreateTable owns billing-mode defaulting and the throughput rules AWS documents:

    • an omitted BillingMode is PROVISIONED, provisioned tables require ReadCapacityUnits and WriteCapacityUnits — per global secondary index too — and PAY_PER_REQUEST tables reject them, all before anything is stored. CloudFormation no longer rewrites a table without ProvisionedThroughput to PAY_PER_REQUEST, so an AWS::DynamoDB::Table template surfaces DynamoDB’s own ValidationException through stack failure

    Migration

    add ProvisionedThroughput, or an explicit BillingMode: PAY_PER_REQUEST, to any CreateTable call or AWS::DynamoDB::Table template that relied on the old implicit on-demand default

    dynamodb cloudformation
  • Fixed

    tables are region-scoped end to end:

    • items, GSI index entries and stream records are keyed by region as well as table name, so the same table name in two regions now holds entirely separate data with its own ItemCount, index results and stream. ListTables, ListStreams, DescribeStream and GetShardIterator answer for the request’s region only, a shard iterator names the region it was issued for, and every stream record carries awsRegion

    Migration

    two same-named tables in different regions used to share one set of items, index entries and stream records. A startup migration rewrites existing rows to the region their table was created in — single-region data carries over untouched — but where the same name existed in several regions the rows cannot be split faithfully, so they are assigned to the alphabetically first of those regions and the others start empty. Anything relying on writing in one region and reading in another must now use one region for both

    dynamodb dynamodbstreams
  • Fixed

    DeleteFunction honours Qualifier instead of ignoring it.

    • A qualified request now deletes only that published version, along with its qualified resource policy and provisioned concurrency, and leaves $LATEST, the function record, other versions, aliases and unqualified policies alone. $LATEST is refused with InvalidParameterValueException, a version an alias still points at with ResourceConflictException, and an unknown version or alias with ResourceNotFoundException; an unqualified request still deletes the function. The qualifier may also travel in the function name, as my-function:2

    Migration

    any caller that passed Qualifier to DeleteFunction expecting the whole function to go must drop the qualifier — it used to be ignored, so those calls deleted more than they asked for

    lambda
  • Fixed

    CreateFunction fails when its Code.S3Bucket/Code.S3Key deployment package cannot be fetched, returning AWS’s InvalidParameterValueException GetObject translation and persisting nothing.

    • It used to log a warning and create a function with no usable code, which then failed at invoke time with an unrelated message. AWS::Lambda::Function surfaces the same failure through ordinary stack rollback

    Migration

    a stack or script that relied on the function being created before its package was uploaded must upload the object first — CloudFormation now rolls the stack back instead

    lambda
  • Fixed

    the tag operations take a TaggableResource ARN, as AWS models them.

    • A bare function name, a partial ARN or another service’s ARN is refused with InvalidParameterValueException rather than treated as a function name, and a version- or alias-qualified ARN is refused too because Lambda tags only the unqualified function. TagResource requires Tags and UntagResource requires tagKeys; a resource in another region or account reports ResourceNotFoundException

    Migration

    callers passing a bare function name to TagResource, UntagResource or ListTags must pass the function ARN

    lambda
Added 21
  • CreateLogGroup accepts AWS’s tags field and applies it as part of creating the log group, over AWS JSON and RPC v2 CBOR alike; AWS::Logs::LogGroup now passes its Tags straight to the create instead of following up with TagLogGroup, so a rejected tag map leaves nothing behind

    cloudwatch-logs
  • OVERCAST_HOST accepts a comma-separated list of addresses to bind, so one emulator can be on loopback and on a host-local bridge address without being on any network the machine is attached to. A wildcard combined with a specific address is refused rather than silently failing the second bind

    config
  • TagResource, UntagResource and ListTags work on event source mappings.

    • Their tags are stored separately from the mapping and are deleted with it, so they never appear in EventSourceMappingConfiguration, which has no Tags member. Code signing configurations, capacity providers and network connectors still return 501

    lambda
  • Code.S3ObjectVersion on CreateFunction and UpdateFunctionCode fetches that version of the deployment package, which is what CDK emits once the asset bucket has versioning enabled

    lambda
  • JSON logging — LogFormat: JSON is honoured on CreateFunction and UpdateFunctionConfiguration, which used to answer 501 for it.

    • The plain-text START/END/REPORT lines are replaced by Telemetry-API-shaped platform.start, platform.runtimeDone and platform.report events, one {"time","type","record"} object per log line; SystemLogLevel filters those by AWS’s system log level event mapping and ApplicationLogLevel filters function output by the "level" member of each record, with anything unparseable treated as INFO. Both default to INFO, and filtering covers CloudWatch Logs and the X-Amz-Log-Result tail — Telemetry and Logs API subscribers still receive every record. AWS_LAMBDA_LOG_FORMAT, and AWS_LAMBDA_LOG_LEVEL under JSON, reach the execution environment so managed and custom runtimes can structure their own output, and a change to the logging configuration retires warm environments. Text output is unchanged; the init-phase platform.initStart and platform.initReport records are not emitted yet, and both are DEBUG at the default system log level

    lambda
  • execution images for every runtime AWS still accepts on CreateFunction: Python 3.14, Java 25 and the Amazon Linux 2023 Java runtimes, .NET 10, Ruby 3.4 and 4.0, plus the Node.js, Python, .NET, Ruby and provided.al2 runtimes that are past end of support but still deployable

    lambda
  • TracingConfig, EphemeralStorage and KMSKeyArn are validated against AWS’s own constraints, stored, and returned by CreateFunction, GetFunction, GetFunctionConfiguration and UpdateFunctionConfiguration.

    • TracingConfig and EphemeralStorage are always present in a function’s configuration, defaulting to PassThrough and 512 MB as on AWS. None of the three changes what a function does: X-Ray is not emulated, the ephemeral storage size is not enforced on the container, and environment variables are not encrypted at rest

    lambda
  • Docker line tags :1 and :1.2, so a user can pin a release line rather than a single version or the moving :latest

    release
  • a maintenance-release path for patching an older minor after 1.0: support/<major>.<minor> branches cut from their tag, backports cherry-picked out of main, and the full test and compat suites running on them

    release
  • PutBucketLifecycleConfiguration accepts x-amz-transition-default-minimum-object-size, and both it and GetBucketLifecycleConfiguration report the behaviour in force.

    • The sweeper applies it: under the default all_storage_classes_128K an object below 128 KB does not transition, under varies_by_storage_class it still transitions to GLACIER or DEEP_ARCHIVE, and a rule with its own ObjectSizeGreaterThan/ObjectSizeLessThan filter sets its own floor. AWS::S3::Bucket.LifecycleConfiguration.TransitionDefaultMinimumObjectSize now dispatches through it instead of failing the resource

    s3
  • PutBucketWebsite and GetBucketWebsite carry the whole WebsiteConfiguration:

    • RedirectAllRequestsTo and RoutingRules are preserved alongside the index and error documents, with AWS’s mutual exclusion, required fields and Protocol enum enforced rather than silently dropped. AWS::S3::Bucket.WebsiteConfiguration dispatches both new elements through S3, translating CloudFormation’s RedirectRule/RoutingRuleCondition spellings. Overcast still serves no S3 website endpoint, so nothing is actually redirected

    s3
  • buckets can send their object events to EventBridge.

    • NotificationConfiguration now carries EventBridgeConfiguration, and while it is set every object mutation is published to the default event bus as AWS’s aws.s3 / Object Created / Object Deleted event, through EventBridge’s own delivery path so rule patterns, input transformers, retries and DLQs all apply. The detail omits request-id, requester, source-ip-address, sequencer and version-id rather than fabricating them. AWS::S3::Bucket.NotificationConfiguration.EventBridgeConfiguration dispatches through S3 instead of failing the resource

    s3
  • the lifecycle sweeper executes NoncurrentVersionExpiration, NoncurrentVersionTransition and ExpiredObjectDeleteMarker, which were previously refused for want of any versions to act on.

    • A version’s noncurrent clock starts when the version that replaced it was written, NewerNoncurrentVersions retains that many newer versions first, and noncurrent transitions honour the bucket’s x-amz-transition-default-minimum-object-size exactly as current-version transitions do. Expiring the current version of a versioned object adds a delete marker rather than deleting it, and ExpiredObjectDeleteMarker clears a marker once it is the key’s only version. AWS::S3::Bucket.LifecycleConfiguration translates all three instead of failing the resource

    s3
  • object event notifications carry versionId and sequencer, in both the Records[].s3.object payload delivered to SQS and Lambda and the EventBridge detail, and an EventBridge delete now reports "deletion-type": "Delete Marker Created" when a versioned bucket wrote a tombstone

    s3
  • the stack detail page and the stack list say why a deploy failed.

    • The banner carries StackStatusReason, falls back to the ResourceStatusReason of the resource that actually failed once a terminal rollback has cleared the stack-level reason, and spells out what the status means: a ROLLBACK_COMPLETE stack has no stable state to update onto and can only be deleted, where an UPDATE_ROLLBACK_COMPLETE one is back on its previous template and can be updated again. It is also raised while a rollback is still running, not only after it lands

    web cloudformation
  • group rows on the IAM page expand to list their members

    web iam
  • the Lambda function page has a Tags card that reads and writes through ListTags/TagResource/UntagResource, and the Versions tab can delete a single published version. The delete-function confirmation now says that versions and aliases go with it

    web
  • the bucket Config tab shows the website configuration, the EventBridge destination and the lifecycle default minimum transition size

    web s3
  • the bucket browser shows a bucket’s versioning state and can list every version and delete marker under a prefix, with the current version marked, noncurrent ones dimmed, delete markers called out, and per-version deletion. Deleting a bucket from the UI drains it by version id, which is the only way to empty a versioned one

    web s3
  • a Logging configuration section on the function’s Configuration tab:

    • log group, log format, and the application and system log levels. JSON is selectable now that Lambda honours it, the two levels are settable only under JSON — the format AWS allows them for — and both start at INFO, the level a JSON function reports back

    web lambda
  • the create wizard offers the same log format and levels, so a function can be created with JSON logging rather than having to be edited afterwards

    web lambda
Changed 9
  • OVERCAST_DEBUG_TRACE_BUFFER is read through the typed configuration rather than directly from the environment, and is now documented alongside the other environment variables. Name and default (1000) are unchanged.

    config
  • scripts/docker-go.sh, scripts/docker-go.ps1 and scripts/go.sh’s Docker fallback bound their CPU use instead of taking the whole machine — a go test through the wrapper peaked at 2269% CPU on a 24-core host and held it there.

    • --cpus and GOMAXPROCS now default to half the detected cores and go test gets a matching -p. All three are needed: --cpus alone leaves the Go 1.24 runtime seeing every host core (container-aware GOMAXPROCS arrived in Go 1.25), and -p defaults to GOMAXPROCS, so leaving it squares the parallelism. OVERCAST_GO_CPUS and OVERCAST_GO_TEST_P override the defaults, OVERCAST_GO_CPUS=0 restores the old uncapped behaviour, and a -p the caller passed is never overridden

    dev
  • docker-compose.dev.yml’s test service bounds its CPU use instead of taking the whole machine, closing the gap left by the Go-in-Docker wrappers’ cap.

    • A Compose file cannot derive the bound itself — --cpus=N is rejected when N exceeds the CPUs the daemon reports, so it cannot be hardcoded, and docker compose run has no --cpus flag to pass one through — so make container-test, container-test-unit and container-test-integration (and their task equivalents) now route through scripts/container-test.sh / .ps1, which reuses the wrappers’ detection ladder and exports OVERCAST_GO_CPUS and OVERCAST_GO_TEST_P for Compose to substitute into cpus:, GOMAXPROCS and GOFLAGS. GOMAXPROCS is set explicitly because the image is Go 1.24 and container-aware GOMAXPROCS only arrived in Go 1.25, so the cap alone leaves the runtime seeing every host core; -p travels via GOFLAGS so it survives a command override. Calling docker compose by hand is unchanged and stays unbounded

    dev
  • the service reference gains a log format and log levels section covering the record shapes, the filtering rules and the container environment variables, and names the two gaps: the missing init-phase records, and UpdateFunctionConfiguration with an explicitly empty LoggingConfig object, which still returns 501

    docs lambda
  • the runtime catalog is single-sourced. Request validation, the runtime-to-image mapping, the deprecation set and the web UI’s runtime picker are all generated from one table derived from the pinned AWS Smithy model, so the four lists cannot drift apart — the picker now offers exactly what CreateFunction accepts.

    • Deprecation follows AWS’s three phases against the current date (end of support, then block function create, then block function update) rather than a single hardcoded set: a runtime past end of support stays deployable until AWS’s block-create date, and one past it answers AWS’s own InvalidParameterValueException naming the recommended successor. GET /_lambda/runtimes is served from that table rather than discovered from ECR Public on the request path, and reports createBlocked and updateBlocked alongside deprecated and supported; LAMBDA_SEED_RUNTIME_IMAGES pre-pulls currently-supported runtimes only, leaving a deprecated-but-deployable one to its first cold start

    lambda
  • the floating image tags (:latest, :alpha, the new line tags) only ever move forward — a maintenance release of an older line no longer takes :latest back with it

    release
  • a trace now captures goroutine stacks for its first 20 internal hops and for the first 20 hops that failed, rather than for every hop.

    • A CloudFormation or CDK deploy dispatches hundreds of hops through one trace, and a multi-KiB stack apiece cost more than it told anyone. Hops past the budget show “Stack trace not captured” in the console.

    router
  • a refused delete names the blocking dependency in the failure toast

    web iam
  • Lambda log viewers, and the Test tab’s log output, render a JSON system log record as the START / END / REPORT line it replaced, with the status and response size the plain-text line has nowhere to put. Ticking “Format” still shows the record itself, and a failed invocation’s records now tint their rows at the level AWS assigns them

    web lambda
Fixed 35
  • ListStacks summaries carry StackStatusReason, as AWS’s StackSummary does, so a list of stacks can say why one failed without a DescribeStacks per row

    cloudformation
  • UpdateStack’s cleanup phase deletes resources the new template dropped in reverse dependency order. It walked a map, so the order was random and removing a role together with the instance profile holding it left the role behind about half the time

    cloudformation
  • a nested stack that cannot be torn down fails its parent instead of being reported as deleted

    cloudformation
  • DescribeStackResources returns ResourceStatusReason, so aws cloudformation describe-stack-resources says why a resource is DELETE_FAILED rather than only that it is

    cloudformation
  • a failed IAM role or user delete during stack teardown is logged instead of being discarded, so a leftover dependency is visible rather than leaving the resource standing behind a DELETE_COMPLETE

    cloudformation
  • ListStacks honours StackStatusFilter instead of ignoring it.

    • A call asking for the CREATE_COMPLETE stacks got every stack in the region back — deleted ones included — so anything trusting the filter, rather than re-checking StackStatus itself, read a wrong answer. The capability table had advertised “Filter by status” the whole time. With no filter the response is unchanged: still every stack, including DELETE_COMPLETE, as AWS documents

    cloudformation
  • a stack’s last-updated time reaches the SDKs.

    • AWS declares it as LastUpdatedTime on both Stack and StackSummary, but DescribeStacks emitted it as LastUpdatedTimestamp — which is StackResourceSummary’s spelling, and one the generated deserialisers do not match on this shape — while ListStacks left it out of its summaries entirely. Every SDK therefore saw an absent field rather than an error, on a stack updated any number of times

    cloudformation
  • ListStacks summaries carry DeletionTime, so a caller listing deleted stacks can tell when each one went

    cloudformation
  • CreateLogGroup and TagLogGroup apply AWS’s documented tag constraints — at most 50 tags, keys of 1–128 characters, values of up to 256, and no key beginning with the reserved aws: prefix — returning InvalidParameterException before any state changes, so a bad tag map no longer half-applies. Tag maps real AWS would have refused are refused here too

    cloudwatch-logs
  • those same container test targets run at all again — the test service set CGO_ENABLED=0 while its command used -race, so every one of them failed immediately with “go:

    • -race requires cgo; enable cgo by setting CGO_ENABLED=1”

    dev
  • docs search now weights a term by the field it appears in. A title, a heading or an inline code operation name outranks the same word repeated in body prose, and repeated mentions count sub-linearly, so searching “live tail” finds CloudWatch Logs’ StartLiveTail, and a query naming a service finds that service’s page rather than the generated operation manifest.

    docs web
  • the storage documentation no longer promises that auto can resolve to a persistent backend in every build.

    • The overcast-slim image and the released overcastd binaries are compiled without SQLite, so hybrid and persistent do not exist in them and auto always lands on memory — mounting a volume at the data directory buys no durability there, and nothing says so at the time beyond the startup log. A new “Builds without SQLite” section in docs/storage.md lists which artifact ships which backends, and every page that described the auto rule now links to it. OVERCAST_STATE=wal is the durable backend those two artifacts do have.

    docs
  • a modeled DynamoDB operation Overcast has not implemented — CreateGlobalTable, DescribeGlobalTable, backups, exports, PartiQL — answers 501 with x-emulator-unsupported: true, where it used to answer 400 UnknownOperationException. That error is now kept for an X-Amz-Target naming no AWS operation, and ownership comes from the generated AWS operation registry so it cannot drift from the models

    dynamodb
  • GetGroup returns the group’s members.

    • Membership recorded by AddUserToGroup was stored but never surfaced, so every GetGroup answered with an empty Users collection; it now resolves each member’s user record and pages over them with Marker/MaxItems. A membership entry whose user record has gone or cannot be decoded is skipped rather than failing the call

    iam
  • a Lambda event source mapping or an EventBridge pipe sourced from a DynamoDB stream matches the source ARN’s region as well as its table name, so it is no longer triggered by writes to a same-named table in another region

    lambda pipes
  • cdk deploy --tags no longer fails an SQS-to-Lambda stack: CreateEventSourceMapping stores the Tags it is sent instead of answering 501, and they are readable through ListTags like a function’s

    lambda cloudformation
  • an explicit null, false, empty string, empty list or empty object for a modeled-but-unimplemented request member now means do nothing and is accepted, rather than returning a 501; a value that genuinely asks for the feature still does

    lambda
  • Docker is no longer misreported as unavailable when it is running. Two faults, both of which made Lambda look broken on a working machine.

    • The stub runtime is registered synchronously at startup while the container runtime arrives on a background goroutine, so anything reaching the emulator in the first moments of its life was answered Runtime.DockerUnavailable — invocations, and PutProvisionedConcurrencyConfig, which reported FAILED and never allocated (Get/List/DeleteProvisionedConcurrencyConfig read the pool the same way and are fixed with it). Both now wait for the verdict. And that verdict used to be latched for the life of the process, so starting the emulator before Docker Desktop had finished booting left Lambda permanently unable to invoke, with an error telling the user to restart the emulator because that was the only recovery there was; the probe now retries and picks Docker up when it appears.

    lambda
  • Lambda invocations now work with a natively-installed Overcast on Windows and macOS.

    • The Runtime API identified a calling container by the source address of its requests, which Docker Desktop rewrites, so every invocation failed at initialisation with Runtime.InitError and nothing in the logs to say why. Each execution environment is now given a Runtime API endpoint of its own, and a call Overcast cannot attribute is logged.

    lambda
  • CreateFunction and UpdateFunctionConfiguration no longer answer 501 NotImplemented when the request carries TracingConfig, EphemeralStorage or KMSKeyArn. CDK emits TracingConfig whenever a function sets tracing, and CloudFormation forwards all three from the template, so a stack that enabled tracing failed its Lambda resource outright with Create Failed lambda CreateFunction: HTTP 501 NotImplemented

    lambda
  • REST requests on a path more than one AWS service declares now resolve to the calling service’s own operation. Where the models bind the same method and URI to differently named operations, the generated registry kept only the first service’s name, so an AppSync-signed GET /v2/apis, an App Registry GET /configuration and an API Gateway or Backup GET /tags/{arn} resolved to no operation at all — leaving them unlabelled in logs, traces and metrics, and ungated by IAM enforcement, which cannot evaluate a policy without an action.

    middleware
  • request logs, debug traces and operation metrics name the right AWS operation for every REST-routed service.

    • Lambda writes were counted as S3 PutObject/DeleteObject, and AppSync, CloudFront, API Gateway and EFS were left unlabelled; all now resolve from the Smithy @http bindings already generated into internal/awsapi. A path no service claims is reported with no operation instead of borrowing S3’s.

    middleware
  • Smithy RPC v2 requests now resolve their service from the {service} path segment written as an absolute Smithy shape ID, the spelling the operation registry already accepts when it matches the request.

    • Resolution also stops consulting the registry’s attribution when the pinned models decline to make one: several services declaring the same protocol, service shape and operation leaves that attribution blank, which read as a lookup for the empty service key and could have answered 501 for an implemented service.

    router
  • request bodies are no longer read into memory on every request.

    • The Logger middleware buffered the whole body up front so that a 5xx could log it — including a multi-megabyte S3 upload, and a second time on top of that when OVERCAST_DEBUG was on. Capture is now lazy and shared: with debug off only what the handler itself reads is kept, bounded at 64 KiB, and a request rejected before its body is read buffers nothing; with debug on the trace capture serves the failure log too, so the body is read once. Measured in docs/dev/performance.md.

    router
  • the per-request stack trace is captured once instead of twice. The Logger and RequestEvents response writers both captured one at WriteHeader time and they nest, so the second overwrote the first with a stack one frame further from the handler.

    router
  • the shared tag validator checks keys in sorted order, so a map that violates more than one rule reports the same violation on every call instead of a random one

    serviceutil
  • The memory-only advisory on the Metrics & Health page gave advice that could not work in a build without SQLite.

    • In the overcast-slim image and the overcastd binaries it suggested mounting a volume or setting OVERCAST_STATE=hybrid, but there the first changes nothing and the second stops the emulator from starting. Those builds are now told so, and pointed at OVERCAST_STATE=wal — the durable backend compiled into every build.

    state web
  • CreateWebACL accepts Tags as a list of {Key,Value} structs, matching the WAFv2 wire format SDK clients send and the shape TagResource already takes — an SDK CreateWebACL with tags previously failed with a SerializationException; create-time tags now get the same validation as TagResource

    waf
  • the Test tab renders a Lambda log tail as UTF-8, so an accented character or an emoji from a handler no longer arrives as mojibake, and a log tail that is not valid base64 costs the log output alone rather than taking the whole result panel down with it

    web lambda
  • a stack that failed and rolled back no longer wears a green success badge.

    • ROLLBACK_COMPLETE and UPDATE_ROLLBACK_COMPLETE both end in _COMPLETE, and the badge tested that suffix before the specific cases below it, so every rollback state read as a success and the explicit ROLLBACK_COMPLETE arm underneath it was dead code. The suffix tests now run last, which also un-shadows REVIEW_IN_PROGRESS, and a rollback in progress is distinguishable from an ordinary deploy rather than sharing its amber

    web cloudformation
  • REVIEW_IN_PROGRESS no longer counts as a transitional state:

    • it spun a progress spinner that never stopped and hid Delete on a stack AWS is willing to delete. canUpdateStack now matches AWS’s enumerated last-known-stable-state set, which adds IMPORT_COMPLETE and IMPORT_ROLLBACK_COMPLETE

    web cloudformation
  • the stack list’s “Last updated” column and the stack detail’s “Last updated” field show the real time instead of always reading —

    web cloudformation
  • the web console binds OVERCAST_HOST rather than every interface. Setting it to 127.0.0.1 restricted the AWS API but left the console — and the state-mutating actions in it — reachable from the network anyway

    web
  • the function Configuration tab no longer crashes to the error boundary on first open. Its tags section synchronised state during render against a default {} that was a new object every time, so while the tag query was still in flight the update never converged and React aborted at 25 re-renders — the tab was unreachable on first paint whenever tags had not already been fetched

    web lambda
  • init hook scripts run via a real POSIX sh (PATH, or the one bundled with Git) instead of the .sh file association, so a failing hook is reported as ERROR rather than successful and each script no longer opens a git-bash terminal window; cmd.exe remains the fallback when no sh is found

    windows
Security 4
  • the compat launcher tells a managed Overcast binary to listen on 127.0.0.1 too, not just a managed container. It set the ports and left OVERCAST_HOST alone, so the instance took the emulator’s 0.0.0.0 default — and since compat prefers a built binary over the container image, that was the common case rather than the fallback

    dev
  • the Lambda Runtime API is no longer published on every interface.

    • It bound 0.0.0.0:9001 unconditionally — including on an instance otherwise restricted to loopback by OVERCAST_HOST — putting an unauthenticated control channel on whatever network the machine was attached to: anyone reaching it can claim a container’s GET /next and answer an invocation with a payload of their own. It now binds loopback plus the single address containers on LAMBDA_NETWORK reach it at — Overcast’s own address on that network when it is containerised, the network’s gateway on a native Linux daemon, the host’s routable address on Docker Desktop, decided by which of them this kernel can actually bind. When none of them resolves it still binds every interface, rather than strand every invocation, and logs that it has

    lambda
  • IAM enforcement no longer authorises Lambda REST calls against an operation inferred by S3’s path heuristics.

    • POST /2015-03-31/functions/{name}/policy authorised as lambda:InvokeFunction, so a principal granted only invoke rights could grant invoke rights to others; RemovePermission was denied as lambda:DeleteObject against a policy that allowed it; and several routes resolved to no action at all and were not gated. Actions now come from the pinned AWS models, scoped to the request’s service.

    middleware lambda
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.32

View on GitHub
v0.0.1-alpha.31 prerelease 11 assets
8 added · 1 changed · 22 fixed · 3 breaking router bff combobox kms ci +22 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.31

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.31

Downloads

Changelog

8 added · 1 changed · 22 fixed · 3 breaking

Breaking changes

  • Fixed

    key-policy mutations apply AWS schema and caller lockout-safety validation.

    Migration

    ensure custom policies retain kms:PutKeyPolicy for the caller, or explicitly set BypassPolicyLockoutSafetyCheck

    kms cloudformation
  • Fixed

    generated-secret templates validate before mutation, while KMS key metadata and CloudFormation tags round-trip through the service APIs.

    Migration

    use PasswordLength 1..4096 (or omit it for 32), and keep GenerateStringKey absent from the template object

    secretsmanager cloudformation
  • Fixed

    PutRetentionPolicy validates retentionInDays against AWS’s fixed value set and returns InvalidParameterException otherwise, matching real CloudWatch Logs

    Migration

    use one of the AWS-documented retention values (1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288 or 3653 days)

    logs
Added 8
    • Resource tagging operations added across 25 services:
    more →

    AppConfig, Athena, Cognito, DynamoDB, ECS, EKS, ElastiCache, ELBv2, EventBridge, Firehose, Glue, IAM, KMS, Lambda, MSK, Pipes, RDS, Scheduler, Secrets Manager, Shield, SNS, SQS, SSM, Step Functions, and WAF. Includes shared Taggable interface, generic ApplyTags/RemoveTags/ListTags helpers in serviceutil, and TagStore + NSStore for separate-namespace tag storage. CloudFormation tag propagation is wired through for Lambda and Secrets Manager.

  • new /_debug/trace/* endpoints for full request tracing — request/response bodies, per-handler structured log capture across 22 services, internal service-hop recording, stack traces captured during handler execution, and AWS errors.

    • Active when OVERCAST_DEBUG=true; traces are looked up by the request ID already returned in every response (x-amzn-requestid / x-amz-request-id); /_debug/traces list and /_debug/trace/{id} detail endpoints support cursor-based bidirectional pagination; internal traces (health, inbox, SSE, debug polling) are capped at 20% of buffer capacity so they never crowd out user-facing requests; service/operation detection for Query-protocol services reads the request body via the generated awsapi.Registry; GET /_events/request/{requestId} queries the event history by request ID (History.FindByRequestID). Includes BFF proxy routes and a web UI at /debug/traces — infinite-scroll list with live polling, deduplication, service filter with counts, millisecond timestamps, and hop-count badges; per-trace tabs for overview (side-by-side request/response with copy buttons and referer), hops (upstream/downstream navigation via request IDs), logs, errors, and events (event-console matching layout); sequence diagram/waterfall/flow map views; Prism syntax highlighting for JSON, XML, form-encoded bodies, and Go stack traces; copy-as-curl; shared CheckboxFilterDropdown and formatBodyForDisplay used by the traces, events, and S3 preview pages; OVERCAST_DEBUG_TRACE_BUFFER env var for ring-buffer sizing.

    router
  • GET /_health now includes a docker block with per-service Docker connection status. Responses from Docker-backed describe endpoints carry x-overcast-backing, x-overcast-backing-reason, and x-overcast-container-health headers.

    router
    • Web UI: S3 copy-URL buttons (S3 URI and path-style formats, actions-menu UX), header endpoint indicator with copy button, overhauled connection dialog with debounced endpoint validation and combobox suggestions, auto-detect API port (Docker socket, native, or 1:1 mapping), connection settings always visible, Docker connectivity banners on resource pages, and Docker health metrics on /metrics.
  • normalizeEndpoint rewrites all loopback endpoints to internal API port, fixing BFF proxying in Docker with remapped ports and no socket.

    • deriveAPIBaseURL returns endpoint known/unknown alongside URL; handles native custom API port via default-UI-port fallback.

    bff
  • add allowFreeText prop for editable-after-selection behaviour (seeds query from current value, commits on blur)

    combobox
  • UpdateKeyDescription

    kms
  • a Release branch base check fails any pull request whose base is a release/* branch and puts the rebase-onto-main instructions in its job summary — the release bot is that branch’s only writer, and a PR into it skips the test matrix, compat suite, changelog gate and breaking-change hold, which all run on pull requests into main

    ci
Changed 1
  • the release changelog gate runs on pull requests into main only, so a mis-based PR gets the base-branch message instead of advice to fold a fragment that was correct all along

    ci
Fixed 22
  • Local secondary indexes are applied on table creation, while unsupported LSI updates now fail without mutating the table. DynamoDB table TTL configuration is validated, applied, and reconciled during stack create and update.

    cloudformation dynamodb
  • Log group retention, resource tags, and propagated stack tags are applied when a stack creates or updates the group.

    cloudformation cloudwatch-logs
  • AWS::KMS::Key updates dispatch PutKeyPolicy only when KeyPolicy actually changed, so an unchanged caller-locking policy created with BypassPolicyLockoutSafetyCheck survives unrelated stack updates;

    • a KeyPolicy given as a JSON string is forwarded verbatim instead of double-encoded; Description changes are applied through UpdateKeyDescription instead of being ignored

    cloudformation kms
  • resources no longer report RUNNING/available/ACTIVE when Docker is unavailable.

    ecs rds elasticache msk
  • KMS keys created by CloudFormation now honor Enabled: false on first deployment and restore the enabled default when the property is removed.

    kms cloudformation
  • fix tag validation gaps:

    • WAF no longer publishes spurious creation events on tag writes; Step Functions typed path validates tags; Shield and ELBv2 validate tag limits; Glue uses shared helpers; RDS fixes unbounded loop; removed dead TaggedResource and shieldTagsToList.

    waf stepfunctions shield glue elbv2 rds
  • TagResource accepts and ListTagsForResource returns Tags/TagList as lists of {Key,Value} structs, matching the WAFv2 wire format that SDK clients send — previously tag operations were unusable via the SDK

    waf
  • path-style S3 copy URLs percent-encode object keys, so keys with #, ?, spaces, or unicode paste as working links

    web
  • bundled builds follow the server-injected API endpoint on boot instead of a stale stored one; only endpoints entered in the connection dialog persist as overrides

    web
  • reopening connection settings seeds the form from the active endpoint, so Connect keeps a custom endpoint instead of reverting it to the default

    web
  • debug tracing no longer pins an oversized request body’s full backing array in memory when truncating it into the trace ring buffer, and internal CloudFormation dispatch hop bodies are capped at 1 MiB and flagged truncated

    router
  • traces of responses written without an explicit status code record 200 instead of appearing in-flight forever in the debug UI

    router
  • TagResource and UntagResource responses include the empty result element botocore requires, so the AWS CLI no longer fails client-side after a successful tagging call

    sns
  • tag operations answer a missing topic with error code ResourceNotFound as on real SNS; topic operations keep NotFound

    sns
  • TagResource validates tags (limit and reserved-prefix checks) with InvalidRequestException, matching real Athena

    athena
  • tag operations no longer 500 (or panic) on a corrupt persisted tag blob, validate tags, surface store write failures, and answer ResourceNotFoundException for a rule or event bus that does not exist

    eventbridge
  • TagDeliveryStream accepts Tags as a list of {Key,Value} structs matching the real API instead of rejecting SDK requests with a 400; ListTagsForDeliveryStream serializes an untagged stream’s Tags as [] rather than null

    firehose
  • the shared /tags/{resourceArn} route space now has a single dispatching owner that routes by the ARN’s service prefix, so Pipes tag operations reach Pipes instead of silently landing in the API Gateway or EKS tag stores; Pipes tag responses use the real API’s lowercase tags member, validate tags, and answer NotFoundException for a missing pipe

    pipes router
  • AddTagsToResource parses the wire form real SDKs send (Tags.Tag.N.Key, the RDS model’s locationName) instead of silently dropping every tag;

    • the member-indexed form remains a fallback. Tag operations now reject resources that do not exist (DBInstanceNotFound, DBClusterNotFoundFault, …) instead of minting a tag store for any string

    rds
  • a corrupt persisted tag blob reads as empty instead of turning every tag operation on that resource into a 500, for all services using the shared tag store helpers

    serviceutil
  • tag operations answer a missing resource with InvalidResourceId instead of ParameterNotFound, and AddTagsToResource validates tags (TooManyTagsError above the 50-tag limit)

    ssm
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.31

View on GitHub
v0.0.1-alpha.30 prerelease 11 assets
1 added · 1 changed · 9 fixed waf web docs ecs cloudformation +4 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.30

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.30

Downloads

Changelog

1 added · 1 changed · 9 fixed

Added 1
  • add Web ACL metadata CRUD views, global search, and system-map nodes while accurately surfacing missing rule enforcement and 501 boundaries

    waf web docs
Changed 1
  • service and task screens prioritize recent container failures, stopped tasks, and logs over follow-on scheduler noise

    web ecs
Fixed 9
  • CloudFormation waits for asynchronous resources to become usable before completing them and preserves their physical IDs through stabilisation failures so rollback can delete what it created.

    • AWS::RDS::DBInstance and AWS::RDS::DBCluster now remain in progress until the database reports available, or fail with RDS’s recorded reason and roll the stack back; cdk deploy no longer returns green while the engine is still initialising. Failed RDS databases and ECS services are now removed during rollback instead of being leaked under names that collide with the next deployment.

    cloudformation rds ecs
  • AWS::S3::Bucket now applies lifecycle, versioning, notification, encryption, tag, CORS, and website properties through S3, including in-place updates, removals, and rollback restoration. S3 lifecycle configuration now validates and round-trips NoncurrentVersionExpiration while true version-history execution remains tracked separately.

    cloudformation s3
  • Lambda resources now preserve supported function and event-source properties, reject unsupported fields before mutation, roll back partial updates, and store validated resource policies safely

    cloudformation lambda
  • CloudFormation SNS topics and subscriptions now forward configured SNS attributes, including subscription filtering and raw SQS delivery (#522)

    sns cloudformation
  • cluster summaries and topology reflect current resources, while stopped tasks remain inspectable with AWS-compatible status, reason, and one-hour retention metadata

    ecs web
  • stopped tasks retain a bounded container log tail for post-mortem diagnostics

    ecs
  • database containers stopped, crashed, or removed through Docker are now recovered with bounded backoff immediately, after Docker reconnects, or during Overcast startup; repeated crash loops settle to failed, only StopDBInstance establishes a durable stopped state, and MySQL 8 initialization now preserves AWS-valid special-character passwords while using caching_sha2_password

    rds
  • service diagnostics now keep the actual failed task and container logs visible instead of letting a later clean exit replace the cause; ECS screens add precise event times, two-way task navigation, and distinct working map destinations

    web ecs
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.30

View on GitHub
v0.0.1-alpha.29 prerelease 11 assets
22 added · 9 changed · 83 fixed · 12 breaking stepfunctions cloudwatch apigateway autoscaling ecs +29 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.29

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.29

Downloads

Changelog

22 added · 9 changed · 83 fixed · 12 breaking

Breaking changes

  • Added

    a real Amazon States Language interpreter — StartExecution now runs the state machine instead of reporting SUCCEEDED immediately.

    • All eight state types, Retry/Catch, the full Choice operator set and the input/output pipeline are interpreted; Task states invoke Lambda, SQS, SNS, DynamoDB and nested state machines through Overcast’s own router. DescribeExecution, GetExecutionHistory, ListExecutions, StopExecution, DescribeStateMachineForExecution and StartSyncExecution report what really ran, and the console gains an executions list and a state-history view. Anything Overcast cannot interpret — .waitForTaskToken, activity tasks, aws-sdk: integrations, distributed Map, JSONata — fails the execution loudly with States.Runtime rather than passing through silently. CreateStateMachine now rejects definitions that are not valid ASL with InvalidDefinition, as AWS does. StartExecution returns while the execution is RUNNING and StopExecution really interrupts it, both matching AWS; StartSyncExecution is the synchronous path

    Migration

    an execution that used to report SUCCEEDED unconditionally can now legitimately end FAILED or TIMED_OUT — assert on the real outcome. State machines created with a definition that is not valid ASL are rejected at create time. OVERCAST_STEPFUNCTIONS_EXECUTION_TIMEOUT (default 15m) is a runaway guard on each execution; it is not on the request path, so ordinary Wait states are unaffected

    stepfunctions
  • Added

    PutMetricAlarm refuses alarm shapes it cannot evaluate — metric math, anomaly detection, extended statistics — with a 501 instead of creating an alarm that never fires

    Migration

    replace a metric-math, anomaly-detection or percentile alarm with a single-metric alarm using Average, Sum, SampleCount, Minimum or Maximum

    cloudwatch
  • Changed

    alarms now evaluate their own metrics: epoch-aligned periods, DatapointsToAlarm M-of-N, Dimensions, and all four TreatMissingData modes

    Migration

    an alarm that previously only moved when you called SetAlarmState now changes state on its own and fires its AlarmActions/OKActions/InsufficientDataActions. Call DisableAlarmActions on alarms whose actions you do not want fired locally

    cloudwatch
  • Changed

    group shapes the reconciler cannot converge are now refused instead of stored and ignored:

    • LaunchTemplate, MixedInstancesPolicy and InstanceId groups, and TargetTrackingScaling/PredictiveScaling policies, return 501, and a group with no launch source at all returns AWS’s ValidationError. A group that reports a desired capacity it never acts on is the failure this rule exists to prevent

    Migration

    define groups with a launch configuration and scale them with SimpleScaling or StepScaling. A group or policy using one of the refused shapes was accepted before and never acted on its desired capacity; it now fails at create time, so a CloudFormation stack carrying one fails rather than provisioning a group that does nothing

    autoscaling
  • Changed

    PutTargets now rejects a target ARN it cannot deliver to instead of accepting it and silently dropping the event: a malformed ARN fails the call with ValidationException, and a well-formed ARN naming an unsupported service comes back in FailedEntries with ErrorCode: UnsupportedTargetType

    Migration

    replace any EventBridge target outside Lambda/SQS/SNS/Step Functions/Kinesis/Firehose/ECS — those never fired, and a CloudFormation stack carrying one now fails to create rather than provisioning a rule that does nothing

    eventbridge
  • Changed

    pipes run every source, enrichment and target Overcast can reach — DynamoDB Streams, Kinesis and SQS sources, an optional Lambda enrichment, and Lambda/SQS/SNS/Step Functions/Kinesis/Firehose/EventBridge-bus targets — instead of only DynamoDB Streams to SQS

    Migration

    CreatePipe and UpdatePipe now require RoleArn (as AWS does) and reject any source, enrichment, target or FilterCriteria Overcast cannot run, with a ValidationException naming the field. A pipe that was accepted before and did nothing will now fail to create — wire it to a supported target, or drop it. UpdatePipe is now routed on PUT /v1/pipes/{name} as AWS routes it, not PATCH — every SDK and the CLI already sent PUT and were answered 405. CreatePipe also answers AWS’s 200 with the six-member response body rather than 201 with the whole pipe

    pipes
  • Changed

    PutObject now honours x-amz-storage-class instead of ignoring it: listings and HeadObject report the class an object was stored with, and an unrecognised class is rejected with InvalidStorageClass

    Migration

    send a documented S3 storage class, or omit the header — an unrecognised x-amz-storage-class used to be discarded silently and now fails the request

    s3
  • Changed

    RotateSecret rejects a secret with no rotation function configured, as AWS does, rather than saving the schedule and rotating nothing

    Migration

    pass RotationLambdaARN on the call or configure it first, and add RotateImmediately: false if you only want the schedule

    secretsmanager
  • Changed

    EFS live mode is the default, so a file system is backed by a real Docker volume whenever a daemon is reachable and an ECS task or Lambda function that declares an EFS mount gets storage it can actually share — no configuration, no OVERCAST_EFS_MODE=live.

    • Live mode asks nothing of a machine that cannot provide it: it creates a volume only for a file system someone created, and with Docker out of reach it creates nothing and behaves exactly as mock mode did

    Migration

    set OVERCAST_EFS_MODE=mock to keep EFS metadata-only. One task-level behaviour changes with the default: a container whose rootDirectory — or whose access-point root directory declared without CreationInfo — does not exist in the volume now fails to start, where before the mount was skipped and the task started without it. That is AWS’s own mount failure, and it surfaces a task definition that was never going to work on AWS

    efs ecs lambda
  • Fixed

    networkConfiguration is required when the task definition’s networkMode is awsvpc, as on AWS, rather than when launchType is FARGATE. An awsvpc task definition launched under EC2, or under a capacityProviderStrategy with no launch type — the shape CDK emits — was accepted and produced a service that could never place a task

    Migration

    pass networkConfiguration (subnets, plus security groups if any) to RunTask and CreateService for awsvpc task definitions; the same call already fails against real AWS without it

    ecs
  • Fixed

    ConsistentRead=true on a Query or Scan against a global secondary index is rejected with the ValidationException AWS returns, instead of silently serving a read AWS has no way to serve

    Migration

    drop ConsistentRead (or set it to false) on GSI queries — the same call already fails against real AWS

    dynamodb
  • Fixed

    MasterUserPassword is held to RDS’s constraints on CreateDBInstance, CreateDBCluster, ModifyDBInstance and ModifyDBCluster:

    • 8–128 printable ASCII characters, none of /, ", @, ' or space. A password real RDS refuses is refused here, so a database that will not deploy says so locally rather than on the way to AWS. Note that this covers passwords Overcast itself can generate — GetRandomPassword’s default punctuation set contains four of the five forbidden characters, exactly as AWS’s does, which is why CDK’s Credentials.fromGeneratedSecret excludes them by default

    Migration

    give the database a password RDS accepts, or set ExcludeCharacters on a generated secret — the same fix real RDS needs. An existing database keeps working untouched; only creating one, or changing its password, is checked

    rds
Added 22
  • usage-plan throttle and quota limits are measured on every request that presents an API key — GetUsage returns AWS’s daily [used, remaining] log per key, and reaching a limit publishes an apigateway:Throttled event.

    • OVERCAST_ENFORCE_APIGATEWAY_THROTTLE (default off) turns that measurement into rejection, answering an over-limit request with API Gateway’s 429: TooManyRequestsException for the rate limit, LimitExceededException for the quota. The console’s Usage Plans page shows each plan’s rate, burst and quota, today’s usage per API key, and a live feed of limits being reached

    apigateway
  • Auto Scaling groups converge for real — a single background reconciler launches and terminates EC2 instances until the owned instance set matches DesiredCapacity, runs the Pending/InService/Terminating lifecycle, replaces unhealthy instances, and records a DescribeScalingActivities entry for every launch and termination.

    • PutScalingPolicy executes SimpleScaling and StepScaling policies, through ExecutePolicy or from a CloudWatch alarm naming the policy ARN in its actions, honouring cooldowns and MinAdjustmentMagnitude; PutLifecycleHook really pauses a launch or termination in Pending:Wait/Terminating:Wait, publishes the EventBridge lifecycle-action event, and waits for CompleteLifecycleAction or the heartbeat timeout. New: DescribeScalingActivities, ExecutePolicy, CompleteLifecycleAction, RecordLifecycleActionHeartbeat, SetInstanceHealth and SetInstanceProtection

    autoscaling
  • alarm state transitions publish the CloudWatch Alarm State Change event to EventBridge and notify SNS alarm actions;

    • new DescribeAlarmHistory, EnableAlarmActions and DisableAlarmActions, plus StateReasonData, Dimensions, DatapointsToAlarm and the action lists on DescribeAlarms. The console’s CloudWatch page gains a live alarms view showing state, reason, what is being evaluated, and recent transitions

    cloudwatch
  • a container definition’s secrets are resolved and injected as environment variables at task start, from Secrets Manager (including the :json-key: suffix that ecs.Secret.fromSecretsManager(secret, "password") produces) or SSM Parameter Store.

    • They were parsed into nothing, so a task promised its credentials started without them and the application failed to reach its database with no explanation. A secret that cannot be resolved is named in a warning and left out rather than injected empty, which would be indistinguishable from a secret whose value is the empty string

    ecs secretsmanager ssm
  • task containers using the awslogs log driver have their output shipped to CloudWatch Logs, into the group from awslogs-group and a stream named <prefix>/<container>/<task-id> as on ECS — under either launch type and however the task was started. A crash-looping task previously explained itself nowhere, its container being gone before docker logs could reach it

    ecs logs
  • deployments report rolloutState, rolloutStateReason and failedTasks, and a service records the AWS-shaped events for a placement failure, for being unable to consistently start tasks, and for a tripped deployment circuit breaker.

    • In the console a service row expands to show its rollout state, failed task count and event log, and task detail shows stopCode and per-container failure reasons; the Run Task and Create Service dialogs now take subnets, security groups and assignPublicIp, so a Fargate task can be started from the UI at all — it previously always failed with “Network Configuration must be provided when networkMode is ‘awsvpc’”

    ecs
  • a service registers its tasks with the target groups in its loadBalancers, at the task’s ENI address and container port, and deregisters them when they stop or the service scales in

    ecs elbv2
  • mount targets can serve a real NFSv4 export — OVERCAST_EFS_NFS=true in live mode starts one unprivileged NFS-Ganesha container per mount target, with access points as pseudo-paths

    efs
  • a load balancer forwards to its targets, so ApplicationLoadBalancedFargateService hands out a URL that serves the application.

    • A listener now stores the DefaultActions carrying its target group — previously parsed by the CloudFormation handler and dropped — and a request arriving on the load balancer’s DNS name is proxied round robin to a registered target, preserving the Host so an app behind it builds its own links correctly. A load balancer with nothing healthy behind it answers 503, as ALB does

    elbv2 ecs
  • rule targets now fan out to Lambda, SNS, Step Functions, Kinesis and Firehose, not just SQS and scheduled ECS tasks;

    • an EventBridge event bus is also a valid target, delivered through PutEvents with a hop budget so bus-to-bus forwarding cannot recurse. InputPath and InputTransformer are applied to a target’s payload before delivery, and a failed delivery honours the target’s RetryPolicy and dead-letter queue. The console’s bus view lists each rule’s targets with its resolved type and the last delivery outcome — delivered, retried, dead-lettered or dropped

    eventbridge
  • SimulateCustomPolicy, evaluating policy documents passed in the request without touching any stored entity.

    • Opt-in request-time enforcement (OVERCAST_ENFORCE_IAM, unchanged and still off by default) now shares the simulator’s evaluator, so a simulation describes what enforcement would decide, and it is documented in the IAM service reference. The console’s IAM page gains a policy simulator — pick a principal or paste a policy, see the decision and the statements behind it — with the enforcement flag’s state shown beside it, so an AccessDenied can be told apart from an application bug

    iam
  • a pipe detail view showing the resolved source, enrichment and target types, recent executions, and whether a pipe is actually wired or only stored

    pipes web
  • breaking changes wait while a minor or patch release PR is open.

    • That PR’s section of CHANGELOG.md is written and reviewed while it sits there, and every push to main is folded into it, so a break merged in the window ships under a version number that promised there was none. The release bot comments on the held pull request with the entries holding it and the ways out — wait, split the compatible part out, correct an entry that is not really a break, or /retarget <branch> onto a next-major branch, which it creates if it does not exist yet — then clears the check itself once the release leaves flight. Pre-1.0 is exempt: 0.x promises no compatibility, so nothing fires below 1.0

    release
  • bucket lifecycle rules:

    • PutBucketLifecycleConfiguration, GetBucketLifecycleConfiguration and DeleteBucketLifecycle are implemented, and an hourly clock-driven sweeper applies them — Expiration deletes objects, Transition marks a synthetic storage class, and AbortIncompleteMultipartUpload abandons stale uploads. Objects in a bucket with an expiration rule are now really deleted, where before nothing ever expired. Prefix, tag and object-size filters are evaluated; the rules that depend on object versioning (NoncurrentVersionExpiration, NoncurrentVersionTransition, ExpiredObjectDeleteMarker) are rejected at Put time rather than stored and ignored

    s3
  • RotateSecret now runs AWS’s four-step rotation protocol against the configured Lambda function — createSecret, setSecret, testSecret, finishSecret — and a single clock-driven loop fires rotations that come due on a RotationRules schedule.

    • The version machinery a rotation function drives comes with it: UpdateSecretVersionStage, staging labels and ClientRequestToken on PutSecretValue, and GetSecretValue by VersionStage. The console’s secret detail page shows rotation status, schedule, version staging labels, the last rotation attempt including which of the four steps failed, and the stored resource policy

    secretsmanager
  • resource policies:

    • PutResourcePolicy, GetResourcePolicy, DeleteResourcePolicy and ValidateResourcePolicy store, return and validate a secret’s policy instead of returning 501. Nothing evaluates it — a stored policy grants and denies nothing (issue #496)

    secretsmanager
  • BatchGetSecretValue accepts Filters (name, description, tag-key, tag-value, all), which it previously ignored:

    • a filtered request returned an empty result rather than the matching secrets, so a caller saw a successful lookup that found nothing. Supplying both Filters and SecretIdList is now the documented InvalidParameterException

    secretsmanager
  • the topic detail view shows each subscription’s live delivery state, and lambda is selectable when subscribing

    web sns
  • a route that fails now shows a proper error screen — the message, a retry that re-runs the page’s data, a collapsed stack trace, and a one-click copy of route, endpoint, region and stack for a bug report.

    • A connection failure says the emulator is unreachable and names the endpoint; a stale tab after an update is told to reload

    web
  • {{resolve:...}} dynamic references, resolved against Secrets Manager and SSM Parameter Store — a reference that cannot be resolved fails its resource rather than being written into it as literal text

    cloudformation
  • DescribeEvents records a DB instance’s create, start, stop, delete and failure transitions as db-instance events, kept for 14 days — this is where the reason an instance failed to start now surfaces, since the real DBInstance shape has nowhere to put it

    rds
  • Exec runs a command inside a running container and returns its exit status and output

    docker
Changed 9
  • the published images pin their base images by digest, so a release is reproducible and an upstream retag of alpine:3.20, node:22-alpine or golang:1.24-alpine can no longer change what ships without appearing in the diff. Dependabot keeps the digests current

    docker
  • the skipped-EFS-mount warning names which of the three causes applied and what it costs — that mock mode still emulates the whole EFS control plane and only the data plane is missing, so the task runs without the mount and writes to that path are lost when it stops

    ecs efs
  • SimulatePrincipalPolicy answers with a real evaluation of the principal’s identity policies — allowed, explicitDeny or implicitDeny, with MatchedStatements naming the statement that decided it and MissingContextValues naming condition keys the call did not supply — where it used to answer allowed to everything. An optional ResourcePolicy and PermissionsBoundaryPolicyInputList are evaluated too, and a policy construct the evaluator does not implement comes back as AWS’s PolicyEvaluation error instead of being resolved to an allow or a deny

    iam
  • a changelog entry folded into an open release PR is placed next to bullets about the same area instead of at the bottom of its category, so the section stays grouped as it grows.

    • Area is read from the [sqs] prefix or from a curated heading like **SQS (long polling)**; when nothing matches it goes at the end, as before. Existing bullets are still never rewritten, reordered or removed

    release
  • secret ARNs carry AWS’s six-character random suffix; the partial ARN without it still resolves, as on AWS

    secretsmanager
  • GetRandomPassword honours RequireEachIncludedType, which AWS defaults to true: a generated password now holds at least one character of every type the exclusions left available, rather than whatever the draw happened to produce

    secretsmanager
  • log timestamps read the same everywhere: an event with no timestamp shows a dash rather than a 1970 clock time in the map’s stream peek, and a debug row carries the same faint tint in every viewer

    web
  • the console’s docs bundle drops the search corpus it never used:

    • web/src/docs-index.gen.ts (655 KB, carrying a flattened copy of every page’s text) is replaced by web/src/docs-nav.gen.ts (112 KB) holding only what the sidebar and page outline render. Docs search already went through the API

    web
  • the generated docs search index is stored per document rather than per term, so editing a doc rewrites that doc’s line instead of up to 1,500 lines of a term-major inverted index.

    • Two branches that each touched docs/ conflicted in the generated file almost every time, and the conflict could only be resolved by regenerating; document-major output merges cleanly. Search results and ranking are unchanged — the same weighted term scores are simply grouped by document, and the inverted index is rebuilt at load time

    docs web
Fixed 83
  • the four hosted configuration version operations — CreateHostedConfigurationVersion, GetHostedConfigurationVersion, ListHostedConfigurationVersions, DeleteHostedConfigurationVersion — are now declared capabilities, so docs/services/appconfig.md and the capability snapshot report what the service has been serving all along

    appconfig
  • ListAssociatedAttributeGroups is now a declared capability rather than a footnote on AssociateAttributeGroup

    appregistry
  • void responses carried a Go type name as their XML root element instead of AWS’s <OperationNameResponse>

    autoscaling
  • two service keys resolved to the wrong AWS model, so their capability rows were validated against the wrong API.

    • waf matched the manifest’s WAF Classic identity (AWSWAF_20150824) although Overcast implements WAF v2 (AWSWAF_20190729) — the two share operation names like CreateWebACL, so v2-only operations such as AssociateWebACL counted as unmodelled. The sesv2 identity (112 operations) was aliased to nothing, so the v2 half of a service documented as “v1 + v2” was absent from model-backed validation and coverage accounting; SES v2 operations now resolve to the ses key

    awsapi
  • --check now sees operations registered only in a service’s typed registry (map[string]op.Operation, built by typedOps()) — detection previously recognised map[string]http.HandlerFunc and action-dispatch switches only, so an operation the emulator dispatches but never lists in the legacy map produced no MISSING row and made its capability entry look like an ORPHAN.

    • A typed registry does not mark detection comprehensive: REST-routed services such as Route 53 and AppRegistry register only part of their surface there, so their remaining rows stay reported as undetectable rather than as violations. --check-model now fails on a service key that no modelled AWS identity backs, and on a compat/suites/registry.json group whose service is not a capability service key — the two mis-mappings above were both invisible because nothing asserted that a key resolves to a model identity by key or alias

    capgen
  • softprops/action-gh-release is pinned to a commit SHA rather than a tag.

    • A tag can be moved to point at different code, so a tag pin is a convenience and not a supply-chain control; the version stays readable as a trailing comment

    ci
  • a transient git failure while fetching the base revision no longer fails the compat baseline and flaky-list lints as though the PR had violated compat policy

    ci
  • a CDK-deployed Fargate service starts its tasks.

    • AWS::ECS::Service applies CloudFormation’s documented default of DesiredCount: 1 for a new service, which CDK depends on because the construct omits the property, and the resource waits for the service to reach that count so one that cannot place its tasks fails the stack instead of reaching CREATE_COMPLETE sitting at 0/0. A stack update now waits for the new deployment the same way a create does, so an update swapping in a task definition whose tasks cannot start fails the resource and unwinds instead of reporting UPDATE_COMPLETE around a service still catching up — or, worse, around one sitting on a failed rollout. AWS::ECS::Cluster and AWS::ECS::Service also get a generated physical name when the template gives none, as CloudFormation does: CDK never emits ServiceName and emits a cluster with no properties at all, so a service was rejected for having no name and every stack’s cluster collided on the ECS API’s default name of “default”

    cloudformation ecs
  • AWS::SecretsManager::Secret generates a value for GenerateSecretString instead of dropping the property.

    • The secret was created with an AWSCURRENT version holding nothing, so it listed and described normally while every GetSecretValue answered ResourceNotFoundException naming that version — a value-less staged version is indistinguishable from an in-flight rotation. SecretStringTemplate and GenerateStringKey place the password inside the template’s JSON object, which is the shape new Secret(..., { generateSecretString }) synthesises, and the generation settings are honoured by dispatching to the service’s own GetRandomPassword. As on AWS the value is generated once, at create; specifying both SecretString and GenerateSecretString now fails the resource rather than silently preferring one

    cloudformation secretsmanager
  • a resource’s resolved properties are persisted, so an update can tell which of them changed — every replacement guard, patch diff and custom-resource OldResourceProperties compared against nothing before

    cloudformation
  • changing a DB instance’s MasterUsername or DBName replaces the instance, as AWS documents, instead of reporting success and leaving it alone

    cloudformation rds
  • dynamic references are compared as written, so rotating a secret behind an unchanged template no longer reads as a changed property

    cloudformation
  • stack outputs leave dynamic references literal, as CloudFormation does, rather than publishing the resolved secret through DescribeStacks

    cloudformation
  • Query-protocol errors use AWS’s ErrorResponse envelope, so SDKs read the error code instead of a generic failure

    cloudwatch
  • image handling no longer defeats itself.

    • Pulling an image ran docker image prune afterwards, and since “dangling” means untagged, an image pulled by digest (repo@sha256:…) has no tag — the prune deleted the image the pull had just fetched, the pull reported success, and the next container create failed with No such image; the prune was also daemon-wide, deleting the user’s own untagged images on every pull. A digest or tag is now sent to Docker in the tag parameter rather than folded into fromImage, which is what the Engine API expects. And a container starts from an image the daemon already holds even when the registry pull fails — an unreachable or rate-limiting registry, or Docker Desktop’s containerd image store returning a stale-lease 404, no longer stops a local image being used, which is the whole point of having pulled it

    docker
  • IsNotFound recognises the 404s reported by every client helper, not only the JSON ones

    docker
  • secondary-index reads use the index.

    • LSI queries read only the queried partition instead of scanning the whole table and honour the sparse-index rule, so an item without the index sort key is no longer returned; a parallel Scan of a global secondary index reads the index rather than the base table, returning only the index’s projected attributes and only items the index actually contains

    dynamodb
  • a parallel Scan segment costs a page rather than the whole table, and an item’s segment no longer shifts when other items are written — a worker-per-segment scan of a table being written to could previously return an item twice or not at all.

    • A Limit=25 page of one segment of a 4-way scan over 8 000 items: 852 µs/67.5 KB per op before, 16 µs/4.6 KB after (memory backend); over 1 500 items on the SQLite backend, 4.92 ms/3.09 MB before, 0.41 ms/0.21 MB after. Measured with go test -bench BenchmarkDynamoDB_ParallelScan -benchmem -benchtime 200x ./internal/services/dynamodb/, three runs each side agreeing within 10%, Go 1.24 in a golang:1.24-bookworm container (container-native filesystem) on a Ryzen 9 5900X / Windows 11 host

    dynamodb
  • deleting a table now deletes its stream records, so a table recreated under the same name gets an empty stream instead of replaying the deleted table’s changes from TRIM_HORIZON

    dynamodb
  • services place real containers.

    • A service’s scheduler goes through the same path as RunTask, so its tasks carry the service’s networkConfiguration, the ENI attachment, the Fargate platform version and the deployment ID in startedBy — previously a service task was a metadata-only record with no container behind it, and the service reported it RUNNING. A task whose containers fail to start is now STOPPED with stopCode TaskFailedToStart and a reason naming the AWS stopped-task error code, and StopTask, scheduler scale-down and essential-container exits set their AWS stop codes too. A service replaces a task whose containers exited instead of draining to zero the first time one finishes; replacements back off from 500 ms to 30 s as tasks keep dying, so a container that exits immediately crash-loops slowly rather than spinning

    ecs
  • entryPoint, workingDirectory, user and dockerLabels on a container definition reach the container — all were dropped, so a task definition setting any of them silently ran something other than what it asked for.

    • The task and service properties CDK sets on nearly every stack now round-trip instead of being dropped: taskRoleArn, executionRoleArn, runtimePlatform, ephemeralStorage, pidMode and ipcMode on a task definition; secrets, healthCheck, dependsOn, readonlyRootFilesystem, privileged and the start/stop timeouts on a container; healthCheckGracePeriodSeconds, enableExecuteCommand, propagateTags, serviceRegistries, placementStrategy and placementConstraints on a service; startedBy on RunTask. Overcast acts on the ones it can and echoes the rest, so DescribeTaskDefinition and DescribeServices agree with what was registered

    ecs
  • a task reaches RUNNING however loaded the machine is.

    • Its PROVISIONING → RUNNING transition was scheduled before the task record was written, and the transition reads that record back and gives up if it is not there — so on a busy host the 200 ms timer could fire first, be spent on a task that did not exist yet, and leave the record that landed a moment later at PROVISIONING for the lifetime of the emulator. Nothing schedules a second one. A service with a task in that state never reaches its running count, never announces a steady state, and anything waiting on it — services-stable, a CloudFormation stack — waits until it times out

    ecs
  • a task stopped while it is still coming up stays stopped, instead of being brought back to RUNNING by its own pending transition landing a moment later — the stop cancels that transition, but cancelling only stops a timer that has not fired, so a scale-in, a StopTask or a container exit could all lose a race with one already in flight and leave a task that reports RUNNING with nothing behind it

    ecs
  • UpdateService and DeleteService write the service record under the same lock the scheduler reconciles it with, so a scale-down that lands while a task is coming up is no longer overwritten by that task’s reconcile — the surplus tasks were left running and the call still returned 200

    ecs
  • a mount target’s availability zone comes from the subnet’s real zone in EC2 rather than a hash of the subnet ID, which put unrelated zones in collision and rejected the second mount target of any multi-AZ file system — the shape every CDK efs.FileSystem produces

    efs ec2
  • deleting a cache cluster or replication group while its container was still starting no longer leaks the container — the delete path had nothing to stop (the container ID was not persisted yet), so the start goroutine now checks for a mid-start delete and tears its own container down, and container fields merge into a fresh read instead of overwriting concurrent transitions with the pre-start snapshot

    elasticache
  • a cluster, replication group or serverless cache deleted while its container was still starting stays deleted over the Query protocol, and its container is torn down rather than left running — the typed protocol already did this

    elasticache
  • a load balancer’s DNSName is built on Overcast’s external hostname, as every other service handing out an endpoint already does, instead of a hardcoded .elb.localhost that OVERCAST_HOSTNAME could not reach. Unchanged by default, since the hostname defaults to localhost

    elbv2
  • target delivery no longer re-enters the emulator’s router on the caller’s chi routing context, which raced with the inbound request still in flight and could hand a sink that request’s URL params

    eventbridge pipes
  • inline policy documents are percent-encoded per RFC 3986, as AWS documents them, instead of form-encoded — a space came back as +, so a client decoding correctly got a literal + in place of the space and any policy that was not minified JSON arrived corrupted

    iam
  • deleting a function while an invocation was still running no longer leaks its execution environment — the in-flight instance was released back into the warm pool for a function that no longer existed, leaving a container nothing would reclaim; it is now destroyed on release, and a function recreated with the same name pools normally again

    lambda
  • an invoke with LogType: Tail waits for the handler’s output to reach the tail buffer rather than for Docker to hand over bytes, so X-Amz-Log-Result carries the function’s own log lines instead of just START/END/REPORT — and the missing line no longer surfaces against the next invocation, whose request ID it was never written under

    lambda
  • a LogType: Tail invoke no longer opens with a log line another invocation wrote.

    • When the wait for a function’s output expires before Docker hands it over, the next tail settles that account first and drops what arrives late, so X-Amz-Log-Result comes back short — which is what it promises, “the last 4 KB of the execution log” — rather than carrying a line logged under a different request ID. The line itself is unaffected in CloudWatch Logs

    lambda
  • deleting a cluster while its Redpanda container was still starting no longer leaks the container — the delete stopped the container ID on the record, which is empty until the start completes, so the start goroutine now tears down its own container when the cluster went away

    msk
  • a DynamoDB-sourced pipe is no longer cancelled part-way through delivery when the write that triggered it answers its client

    pipes
  • three Query-protocol decoding faults.

    • Lists whose members carry a model locationName now decode — RDS CreateDBSubnetGroup rejected every real AWS SDK with “At least one SubnetId is required” because the SDKs serialise SubnetIds.SubnetIdentifier.N and the codec only recognised member.N, flattened, and map forms. Map entries no longer lose their values, entry.N.key and entry.N.value arriving as separate wire keys that were never merged by index, so maps decoded with every value empty. And requests with structures nested more than two levels deep no longer decode those fields as absent

    protocol
  • an ECS task can connect to an RDS instance by its endpoint hostname.

    • The instance’s container is attached to the VPC network — and to the ECS network for tasks outside a VPC — advertising that hostname as a DNS alias, which is what RDS already did for the Lambda network. Without the alias the name fell through to Overcast’s own address and the task connected to a port nothing was listening on

    rds ecs
  • a DB instance endpoint is now a hostname a Lambda function or ECS task can actually resolve and connect to.

    • Endpoint.Address is minted on the endpoint the request arrived on — mydb.ap-southeast-2.rds.localhost.overcast.sh when OVERCAST_HOSTNAME is set — the rule every other service already follows, and the engine container answers to that name on overcast_lambda, overcast_ecs and its VPC network. Previously the name was built from configuration alone (so a caller reaching Overcast on a different hostname was handed one that resolved nowhere), and once the container started it was overwritten with a raw container IP or 127.0.0.1 — an address dialable by exactly one party, which is what a WORDPRESS_DATABASE_HOST baked into an ECS task definition by Fn::GetAtt could not use

    rds
  • the port follows the caller for the same reason:

    • a sibling container is given the engine’s own port (3306/5432), the host the published one. A host request to a containerised Overcast arrives from the Docker bridge gateway rather than loopback, which is now recognised as the host side

    rds
  • any EngineVersion starts a container.

    • The image map was matched exactly, so the precise versions real stacks send (8.0.39 from CDK’s MysqlEngineVersion.VER_8_0_39, 16.3) started nothing at all — the instance went available with no database behind it and its endpoint resolved nowhere. The nearest image in the same family is used and the substitution is logged

    rds
  • stopping a DB instance keeps its container, so StartDBInstance can bring the same instance back

    rds
  • a Docker error on the container logs endpoint is reported as an error, instead of being de-framed into corrupted-looking log output

    rds docker
  • a DB instance whose container cannot be started reports failed with the reason, instead of available with nothing behind it

    rds
  • starting an instance rebuilds a container Docker no longer has, and the startup sweep keeps containers a stopped instance still owns

    rds
  • the emulator’s instance logs endpoint explains a database that failed to start instead of showing an empty pane: it carries the instance’s status and failure reason, serves the tail it captured from the container before that container went away, and answers 404 rather than 500 when the container is gone

    rds
  • ModifyDBInstance applies MultiAZ=false, which only the raw Query path did — stop, start and modify had one implementation per dispatch path and now have one between them

    rds
  • ModifyDBInstance reads MasterUserPassword, which it had never done:

    • the parameter was not on the request type at all, so a stack that rotated a database’s master password deployed clean over an instance — and a container — still on the old one. The change is made the way it has to be for it to mean anything, by running the engine’s own ALTER USER inside the container: the old password stops working and the new one starts, as on AWS. A container reads MYSQL_ROOT_PASSWORD and its equivalents once, when it initialises its data directory, so nothing short of that would have applied

    rds
  • a master password the running engine will not take is reported rather than recorded — the engine’s own error comes back and nothing in the request is stored, and an instance that is not available is refused with InvalidDBInstanceState instead of being told a password it has no way to start honouring

    rds
  • a health check no longer reverts an API call it overlapped: it polls the engine for minutes, and used to write back the instance record it had read before dialling — silently undoing a ModifyDBInstance’s new instance class, or rolling its status back

    rds
  • every writer of an instance, cluster, replication group or cache record now holds that record’s lock across the read and the write, so a lifecycle transition, a Docker event and an API call can no longer discard one another’s edits

    rds elasticache msk
  • a stack update that changes an AWS::RDS::DBCluster property applies it instead of reporting UPDATE_COMPLETE having changed nothing.

    • The handler sent MasterUserPassword, BackupRetentionPeriod, Port, PreferredBackupWindow, PreferredMaintenanceWindow, DBClusterParameterGroupName, VpcSecurityGroupIds, EnableCloudwatchLogsExports and DeletionProtection to ModifyDBCluster, whose request type had two fields, so every one of them was dropped between the wire and the handler. Engine, MasterUsername, DatabaseName and DBSubnetGroupName now force replacement, as AWS documents them, rather than being quietly applied in place. CreateDBCluster accepts the same settings, so what can be updated can be created too

    rds cloudformation
  • ModifyDBCluster rotates the master password on the cluster’s member instances.

    • A DB cluster is a logical record with no container of its own, so the rotation goes through the same code an instance rotation does, once per member; a member whose engine refuses fails the call and is named, and a cluster with no members yet records the password as any metadata-only database does. Nothing that worked before stops working — the parameter was previously discarded, so a call that used to return success having done nothing now either does it or says why

    rds
  • DeleteDBCluster refuses a cluster with DeletionProtection enabled, with AWS’s InvalidParameterCombination, and a CloudFormation stack delete now fails rather than removing a protected cluster and reporting DELETE_COMPLETE.

    • No existing cluster can be affected: the property was dropped before it reached the handler, so it could not be set on a cluster until this change

    rds
  • ModifyDBCluster answers with a ModifyDBClusterResponse element on both dispatch paths.

    • The typed implementation returned a CreateDBClusterResponse, which went unnoticed only because a second raw implementation of the same operation declared the right envelope locally; CreateDBCluster, DescribeDBClusters, DeleteDBCluster and ModifyDBCluster now have one implementation each, as the instance operations already did

    rds
  • CreateDBInstance reports available only once the engine accepts a connection, instead of immediately — a created instance was queryable around 27 seconds after it claimed to be ready, and one whose container never started stayed available for good

    rds
  • a create whose database container cannot be built reports failed with the reason, as a failed start already did

    rds
  • the health check on a newly created instance dials the address Overcast can actually reach, rather than an endpoint name only sibling containers resolve

    rds
  • CreateDBInstance and DeleteDBInstance have one implementation again, joining the operations collapsed in the previous change

    rds
  • OVERCAST_RDS_MODE=mock keeps RDS metadata-only on a machine that has Docker, for testing a control plane without paying for a real engine boot; live remains the default

    rds
  • the master password statement authenticates through the MYSQL_PWD environment variable rather than a -p argument, matching what psql already did with PGPASSWORD. Command arguments are visible to docker inspect and to the host process table, and both MySQL and MariaDB clients also warn about a command-line password on stderr — which, on a TTY-allocated exec, landed in the middle of the engine’s explanation whenever a statement failed

    rds
  • release notes are written without a second approval.

    • finalize-release sat in the release environment and depended on the three publish jobs, so it formed a second approval wave: the maintainer approved once, the release completed and looked finished, and the job that fills in the description waited for an approval nobody knew to give. v0.0.1-alpha.27 and v0.0.1-alpha.28 both published with empty notes as a result. It publishes nothing — everything is already out by the time it runs — so it is no longer gated

    release
  • the Changelog entry check no longer asks the release PR to write itself a release note.

    • A release-prep PR consumes every fragment into its version section and adds none by construction, but it also carries the new, untagged VERSION — the same predicate the check reads to decide which release window a PR is in — so it classified the release PR as “merged, waiting to be tagged” and posted an ask saying the release had already gone out, on the one PR that had not merged. The release PR is now exempt by shape: VERSION and CHANGELOG.md both changed, nothing touched outside those and .changelog/, the version untagged, and a non-empty ## [x.y.z] section present. Push a code change onto the release branch and the check asks again, in words written for that case — the note belongs in the release section the PR already owns, the one place where editing CHANGELOG.md is the answer rather than the thing to avoid

    release
  • a compatibility marker written after the area rather than after the kind — ~ [autoscaling]! … — is rejected by changelog.py check instead of being read as prose.

    • The area group must be followed by whitespace, so a marker in its place made the whole group backtrack out, and the entry silently lost both its ! and its area: it rendered as an ordinary bullet, was never asked for a migration: note, and sorted away from the entries it belonged with. One reached a release section that way. Refusal prose (“are now refused”, “is rejected”) joins the phrases that force an explicit marker, so the same class of break is caught on its own wording too

    release
  • folding an entry into an open release PR no longer adds a blank line under the category heading every time it runs.

    • The ### <Category> pattern ended with \s*$, which under multiline matching runs across the line ending and takes the blank line after the heading with it, so each fold rejoined one newline further down than the last. release-prep folds on every push to main while a release PR is open, so a release window’s worth of pushes stacked them up

    release
  • CopyObject accepts a fully URL-encoded x-amz-copy-source, which AWS requires be URL-encoded and decodes. The header was split on / before decoding, so a client that encodes the separator too — the AWS SDK for .NET does — was rejected with “Invalid copy source”

    s3
  • re-creating a bucket you already own in us-east-1 returns 200 instead of BucketAlreadyOwnedByYou, matching S3, which documents that conflict as returned “in all AWS Regions except in the North Virginia Region” and answers 200 there for legacy compatibility. Every other region still conflicts, and idempotent bucket creation — what the AWS SDK examples do — now works against the default region

    s3
  • PutObject no longer discards the body when the request carries Content-Type: application/x-www-form-urlencoded — the content type many minimal HTTP clients send when none is set, curl --data-binary among them.

    • Protocol detection read AWS Query fields out of the body before routing had decided who owned the request, which drained it: the write answered 200 OK with the empty-string ETag and stored a zero-byte object, with nothing to tell the caller. Form fields are now read through a body-preserving parse, and only for POST, which is the only method the Query protocol puts them on. IAM enforcement did the same thing on the same requests and is fixed with it.

    s3 router
  • An object key containing + is now stored decoded rather than as the literal %2B a client sends on the wire. Only characters Go leaves bare when it re-escapes a path were affected — +, =, & — which is why %20 and multi-byte UTF-8 looked fine.

    s3
  • a lambda-protocol subscription is now actually delivered to — Publish invokes the function with AWS’s Records[].Sns event instead of dropping the message.

    • A subscription pointing at a function Lambda cannot run, through a missing layer version or an unsupported runtime, reports the failed delivery rather than reporting success and dropping the notification; a throttled function is retried inside Lambda instead of bouncing the event back to whatever raised it, where an SNS notification to a function reserved to zero concurrency used to reach the subscription dead-letter queue although the same function invoked over HTTP would have retried. Any delivery that does fail is logged and moved to the subscription’s RedrivePolicy dead-letter queue, for every protocol, rather than being swallowed

    sns
  • notification Timestamp uses AWS’s millisecond form (2012-04-25T21:49:25.719Z)

    sns
  • a rotation function copied from AWS’s published blueprints now works unmodified.

    • Three behaviours its createSecret step depends on were missing: RotateSecret stages the ClientRequestToken as an empty AWSPENDING version before the first invocation, as AWS does — the blueprints assert that staging before every step, so one failed immediately with “Secret version … has no stage for rotation of secret …”; GetSecretValue reports a version holding no value as ResourceNotFoundException rather than an empty 200; and PutSecretValue under that version’s token fills it in rather than answering ResourceExistsException. DescribeSecret still lists the version throughout

    secretsmanager
  • A Query-protocol request addressed by the queue’s own URL — POST /<account>/<queue> with Action=SendMessage and no QueueUrl parameter, the shape AWS’s API Reference documents — no longer fails with MissingParameter.

    • The queue now comes from the request path when the body omits it. A QueueUrl in the body still wins, and a request to POST / without one is still the client error it always was.

    sqs
  • ListQueueTags renders a tag as <Tag><Key>…</Key><Value>…</Value> under the Query protocol, matching AWS and the rest of Overcast. It was emitting <Name> — the shape that belongs to Attribute, not Tag.

    sqs
  • PurgeQueue no longer stops the queue accepting deliveries for the next 60 seconds — that window only governs when a second PurgeQueue is allowed, not whether messages are kept, and the purge itself already completes before the call returns every message sent in the minute after a purge was dropped while SendMessage still answered 200 with a MessageId, so nothing reported the loss; SNS and EventBridge fan-out, EventBridge Scheduler and S3 event notifications into the queue, and DLQ moves and redrive all went with it

    sqs
  • deleting a queue now clears its purge window, FIFO deduplication IDs and receive-attempt records, so a queue recreated under the same name no longer inherits them — it could previously refuse its first PurgeQueue with PurgeQueueInProgress or silently swallow a re-sent deduplication ID

    sqs
  • log viewers render a container’s ANSI colours instead of the escape sequences around them — a colourising entrypoint (Bitnami, npm, go test) no longer shows [38;5;6m litter with an invisible control byte in front of it, and cursor moves, window titles and stray control bytes are dropped.

    • The stored event is untouched: real CloudWatch keeps those bytes too

    web
  • filter-term highlighting in the CloudWatch Logs viewer marks every match in a line rather than every other one

    web
  • network error toasts stay quiet while the UI knows it is offline

    web
  • the instance Logs tab shows the failure reason and the dead container’s last output rather than “No logs available”, and a new Events tab lists the instance’s events newest-first with failures called out

    web rds
  • docs search in pnpm run dev answers like a built binary.

    • The dev BFF matched a raw substring against a flattened text blob while the Go BFF matched whole tokens against a ranked index, so the same query could return different pages, in a different order, with every score reported as 1. Both now read the same generated index with the same tokenizer and ranking

    web
  • docs/networking.md gains a Data-plane endpoints section stating the rule across services: every hostname Overcast hands back is minted on the endpoint the request came in on, how a name that points at a container (rather than at Overcast) is made resolvable, and why the port differs by caller

    docs
  • a doc that cannot be indexed — no title, no section, or no searchable terms — fails make docs-index instead of being written out as a degenerate entry that regenerates byte-identically and so passes the staleness check while the page is missing from search

    docs
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.29

View on GitHub
v0.0.1-alpha.28 prerelease 11 assets
16 added · 14 changed · 24 fixed cloudformation docker ecs efs lambda +7 more

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.28

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.28

Downloads

Changelog

16 added · 14 changed · 24 fixed

Added 16
  • AWS::EFS::FileSystem, AWS::EFS::MountTarget, and AWS::EFS::AccessPoint provision end-to-end, including FileSystemPolicy, LifecyclePolicies, BackupPolicy, FileSystemProtection, tag sync on update, and AWS-matching replacement semantics

    cloudformation
  • the internal Docker client gained volume operations (CreateVolume, RemoveVolume, ListVolumes) with the standard managed labels

    docker
  • task definitions now model volumes (including efsVolumeConfiguration) and container mountPoints, with AWS’s undefined-volume validation; when EFS live mode is active, task containers mount the file system’s backing Docker volume at each mount point (honoring readOnly), sharing real file data with Lambda functions that mount the same file system

    ecs
  • RegisterTaskDefinition now rejects efsVolumeConfiguration combining an accessPointId with a rootDirectory other than /, matching AWS

    ecs
  • opt-in live mode (OVERCAST_EFS_MODE=live):

    • each file system is backed by a named Docker volume (overcast-efs-<FileSystemId>), created on CreateFileSystem, removed on DeleteFileSystem, and reconciled on startup (missing volumes recreated, orphans removed); the control plane degrades gracefully when Docker is unavailable

    efs
  • live-mode mounts now honor root directories:

    • Lambda FileSystemConfigs mounts are scoped to the access point’s RootDirectory, and ECS efsVolumeConfiguration mounts to the access point’s root or the declared rootDirectory, via Docker volume subpath mounts (Docker Engine 26+). Access points with CreationInfo have the directory created in the volume with the declared ownership and permissions before the first mount; without CreationInfo a missing directory fails the mount, matching AWS

    efs
  • new service:

    • EFS control-plane emulation under the real /2015-02-01/ REST-JSON API — file systems (create/describe/update/delete, protection), mount targets (incl. security groups), access points, file-system policies, lifecycle configuration, backup policy, tagging (current + legacy APIs), and account preferences, with creating → available → deleting lifecycle states

    efs
  • FileSystemConfigs is now modeled on CreateFunction, UpdateFunctionConfiguration, and GetFunctionConfiguration (one config max, EFS access-point ARN, /mnt/<name> mount path — matching AWS validation); when EFS live mode is active, the function’s containers mount the backing Docker volume at LocalMountPath, so invocations share real file data with each other and with other services mounting the same file system

    lambda
  • REPORT log lines now carry Init Duration on the cold-start invocation of on-demand execution environments (measured from container start to the runtime’s first GET /next), in AWS’s field order and format; warm invokes and provisioned-concurrency environments omit it, as on AWS

    lambda
  • opt-in proactive initialization (LAMBDA_PROACTIVE_INIT): once a function’s configuration settles after a deploy, one execution environment is pre-created in the background so the next request lands warm — mirroring AWS’s documented proactive initialization, including AWS_LAMBDA_INITIALIZATION_TYPE=on-demand and no Init Duration on the first REPORT line

    lambda
  • changelog entries carry a compatibility marker, so a release bump can be derived rather than chosen.

    • - Removed defaults to breaking and everything else to compatible, prose naming an input or output contract (“now requires”, “now rejects”) forces an explicit answer either way, and a breaking entry must carry a migration: note. Calibrated against the entries in flight: 2 of 32 are asked

    release
  • a Release Prep workflow (workflow_dispatch) prepares a release end to end:

    • derives the version, assembles and inserts the changelog section, repoints both compare links, writes VERSION, deletes the consumed fragments, opens the PR, and comments a summary listing breaking changes with their migration notes. It never merges — VERSION is CODEOWNER-owned and publishing still waits on the release environment. Re-running against an open release PR reports the fragments that have landed since rather than rewriting the section, so curation is never discarded

    release
  • changelog.py release applies the mechanical release-prep edit and changelog.py next-version derives the next version.

    • While in alpha that is the prerelease counter incrementing; deriving a stable bump from the entries stays unimplemented until 1.0 rather than guessing the policy

    release
  • Route 53 is now emulated at inert level (25 operations, up from 10).

    • New: ListHostedZonesByName, GetHostedZoneCount, UpdateHostedZoneComment, tags (ChangeTagsForResource, ListTagsForResource, ListTagsForResources), and health check CRUD (CreateHealthCheck, GetHealthCheck, ListHealthChecks, GetHealthCheckCount, UpdateHealthCheck, DeleteHealthCheck). Hosted zones now get default apex NS/SOA records and a delegation set, CreateHostedZone enforces caller-reference uniqueness (HostedZoneAlreadyExists) and returns the Location header, DeleteHostedZone enforces HostedZoneNotEmpty, ChangeResourceRecordSets validates batches atomically with AWS error codes (InvalidChangeBatch for duplicate creates, missing/mismatched deletes, out-of-zone names, apex CNAMEs) and stores routing metadata (SetIdentifier, Weight, Region, Failover, GeoLocation, MultiValueAnswer), list operations paginate in DNS order, names are canonicalised to lowercase, and errors use Route 53’s ErrorResponse envelope. CloudFormation gains AWS::Route53::HealthCheck plus hosted-zone tag/VPC pass-through and comment-only in-place updates.

    route53
  • a release PR now keeps itself mergeable:

    • every push to main merges into the release branch and appends any new changelog entries to the version section already there, deleting their fragment files, so the changelog gate goes green without anyone editing anything. Folding is additive — no bullet already in the section is read, reordered or rewritten — so curation done by hand survives, and rewording the appended bullets is optional polish rather than a chore every merge forces

    release
  • the release-prep workflow now runs on every push to main:

    • when a release PR is open it merges main into the release branch, pushes, and comments with the entries still to curate. Refreshing the branch is what re-runs the PR checks, so a release PR can no longer sit green while going stale

    release
Changed 14
  • proxied requests no longer re-scan and re-decode the API’s entire resource/route set on every request — routing state is cached per API and invalidated on any resource or route write

    apigateway
  • the invoke path no longer SHA-256s the whole deployment package on every invocation (and every configuration read) — the hash is computed once when code is written and stored on the function record, cutting per-invoke CPU for large packages

    lambda
  • deployment packages are stored separately from function records, so invoke-path reads no longer base64-decode the whole zip on every invocation (and S3 code-sync events no longer decode every function’s package); existing records migrate automatically on their next write

    lambda
  • cold starts reuse cached artifacts instead of rebuilding them every time: code and layer tars are kept in a byte-bounded in-memory cache (LAMBDA_TAR_CACHE_MB, default 256, 0 disables), the bootstrap and TLS trust-root tars are built once, and the per-acquire image-presence check is skipped once an image is verified

    lambda
  • removing a runtime image mid-session (docker rmi) no longer breaks that runtime until restart — a container create that finds the image missing re-pulls it and retries

    lambda
  • cold-start artifacts (code and layer tars) are pre-built in the background once a deploy settles, so even the first cold start of a new code version skips the package fetch and conversion; the artifact cache is reported on the /_lambda/instances debug endpoint

    lambda
  • proactive initialization now counts API Gateway integrations and AppSync Lambda data sources as trigger evidence, so wired functions get a warm environment after a deploy even in a fresh process

    lambda
  • REPORT lines’ Max Memory Used now reports the execution environment’s running peak across warm invocations — matching AWS — and is sampled concurrently with handler execution, so writing the REPORT line no longer holds up the invoke response waiting on Docker stats

    lambda
  • cold starts provision the container filesystem with a single archive (code, layers, TLS trust root, bootstrap) instead of up to four sequential Docker copy round trips — measured cold-start p50 ~355 ms → ~300 ms for cached-image hello-world functions

    lambda
  • changelog fragments are now entry lines rather than frontmatter documents:

    • one file per PR, and each line carries its own category, scope and compatibility marker (<+|-|~|*|section>[!|.] [area] prose). scripts/changelog.py new writes them, appending to the branch’s file so later commits land alongside earlier entries; assemble groups at entry rather than file granularity, so entries about one service sort together even from different PRs

    release
  • the VERSION guard accepts the release App as well as the repository owner, so a generated release PR can bump VERSION. Dispatching the workflow already needs write access, so the set of people who can produce a VERSION change is unchanged

    ci
  • the tail wait is now paid only by invokes that asked for a log tail (X-Amz-Log-Type: Tail, and the console’s test tab). Asynchronous invokes, event-source mappings, function URLs and service-to-service calls discard LogResult and no longer wait on log delivery to produce it

    lambda
  • the release summary comment lists the entries as they will read in the changelog rather than only counting them, with breaking changes and their migration notes first

    release
  • the release-candidate comment is reposted on each build instead of edited in place, so its own timestamp shows when it last changed — GitHub anchors an edited comment at its original position and hides the update behind an “edited” marker, which made a refreshed RC invisible. The new comment is posted before the old one is removed, so a release PR is never left without a candidate to test, and the header now names the head commit it was built for

    ci
Fixed 24
  • compat baseline auto-promotion works again as a PR-based flow:

    • the old direct push to main had been rejected by branch protection on every run since required-check enforcement (#393), silently stranding improvements (#440). The workflow now force-pushes a coalescing automation/baseline-promotion branch and auto-merges it through the normal required checks, using GitHub App credentials; until the App secrets are configured it emits an accurate warning instead of the old false “will retry” message

    ci
  • the last quarantined flake, cdk VerifyTopicSubscription (#435), was a suite-side consumer race:

    • the SNS topic fed the same queue the stack’s Lambda event source mapping polls, so the ESM intermittently consumed the published message before the test’s ReceiveMessage saw it (it would flake identically on real AWS). The topic now feeds a dedicated queue with no competing consumer, and the delivery wait budget rose from 2.5s to 10s; compat/flaky.json is empty for the first time

    compat
  • the compose path can now run the cdk suite: the CDK asset publisher addresses the bootstrap bucket virtual-hosted ({bucket}.overcast), which needed a wildcard-free /etc/hosts entry in the runner services plus OVERCAST_HOSTNAME=overcast on the emulator so host classification recognises the compose hostname as a virtual-host base

    compat
  • the stable cli/lambda-invoke/InvokeDryRun CI failure (plan item R7) is fixed:

    • it was the only invoke test that didn’t wait for the function to leave Pending, and CI never pre-pulled the python runtime image its function uses, so the cold pull outlasted the invoke on every run. The test now waits like its siblings, CI pre-pulls public.ecr.aws/lambda/python:3.12, and the Active waiter survives a cold pull and fails fast with the StateReason when a function enters Failed

    compat
  • the #388 “created-then-not-found” flake family was three suite-side group-isolation bugs, not an emulator race:

    • dotnet’s sns-topics teardown swept sibling groups’ live topics via an over-broad name prefix, and the cli SES and EventBridge groups each shared one identity/bus that sibling groups’ teardowns deleted mid-run (suite groups run in 8 parallel slots). Each group now owns its resources; nine compat/flaky.json entries deleted, and the remaining cdk entry moved to its own issue (#435)

    compat
  • removing a task or database image mid-session (docker rmi) no longer breaks launches until restart: a failed pull is retried on the next launch attempt instead of being cached forever, and a container create that finds its image missing re-pulls and retries once — the same resilience Lambda gained earlier

    ecs rds
  • DescribeFileSystems now rejects requests carrying both FileSystemId and CreationToken with BadRequest, matching AWS

    efs
  • DescribeFileSystems amortizes one mount-target scan across the whole page instead of scanning per file system

    efs
  • deleting a file system whose volume is still held by a running container now retries the volume removal (3 attempts, 30 s apart) before deferring to startup reconciliation

    efs
  • invoking a function still in Pending state now returns AWS’s 409 ResourceConflictException (“The operation cannot be performed at this time…”) instead of a 400 InvalidParameterValueException, matching real Lambda’s state-conflict semantics

    lambda
  • CodeSha256 in function and version responses is now the base64-encoded SHA-256 AWS returns, not hex — tooling that compares its locally computed hash (CDK change detection among them) no longer sees permanent code drift

    lambda
  • layer version responses now populate Content.CodeSha256 (base64, as on AWS); layers published before this release omit it

    lambda
  • layer archives are stored separately from layer records, so listing layers no longer base64-decodes every layer’s full zip per call

    lambda
  • a function deleted while its background image pull was still running could be resurrected as “Active”:

    • the pull-completion callback, the startup Pending-reconciler, and the S3 code-sync watcher all wrote back records snapshotted before their slow operation started. All three now merge into a fresh read and skip a function that was deleted (or transitioned) meanwhile — this was the intermittent compat lambda-crud/DeleteFunction “function still exists” failure (#414), and the same stale write could silently clobber an UpdateFunction* revision landing mid-pull

    lambda
  • instance memory/CPU in the instances panel are now sampled from Docker instead of always reading 0, and the REPORT line’s Max Memory Used no longer reads 0 on slow (Docker-in-Docker) hosts — container stats are fetched with one-shot=true, which also removes the daemon’s ~1–2 s two-cycle stats wait from every invocation’s response path

    lambda
  • the TLS trust-root archive is built once per process instead of per container launch

    lambda ecs
  • the release-prep workflow authenticates with the App client ID; app-id is deprecated from create-github-app-token v3 and warned on every run

    release
  • host classification no longer allocates intermittently on host-routed requests:

    • the region-shape check used a package regexp, whose pooled matcher state is only amortized allocation-free (and is deliberately dropped at random under the race detector, which made TestHostClassifier_lowercaseHostStaysAllocationFree flake in CI); it is now a hand-rolled matcher pinned to the old regexp by an oracle test

    routing
  • X-Amz-Log-Result could come back without the handler’s own output — just the START/END/REPORT lines the emulator writes itself. The wait that lets Docker’s log stream catch up keyed off a container-lifetime watermark, so it could not tell “nothing read yet” from “nothing to read”, and returned early on cold containers and warm reuse alike

    lambda
  • a release-candidate build is skipped when the build context is unchanged:

    • the image is content-addressed by a hash of the paths .dockerignore allowlists plus the build args, carried as a second tag, so an unchanged context is retagged registry-side in seconds instead of rebuilt. Safe because .dockerignore ignores everything and then allowlists — anything Docker can see is in the hash, and anything it cannot see cannot affect the image. The candidate comment states which happened

    ci
  • release-candidate images are stamped with the release version; they were built without the VERSION build arg and so reported dev

    ci
  • the release-candidate content hash covers only what can actually reach the image — the compiled cmd/overcast, the files the runtime stage copies, and what embed.go embeds — so a candidate is not rebuilt for a change that cannot alter it.

    • Go test files and testdata/ are dropped because the toolchain excludes them from go build; other cmd/* trees because a package main cannot be imported; and docs/plans, docs/dev and docs/generated because embed.go deliberately does not embed them and the runtime stages copy in nothing but the binary. Measured over 40 commits, half no longer trigger a rebuild

    ci
  • release-candidate images are now built for linux/amd64 and linux/arm64, the same platforms a release publishes. They were built only for the CI runner, so pulling one on an arm64 host ran emulated amd64 bits — for an emulator that itself launches containers that is a different environment rather than merely a slower one, and it made a platform problem indistinguishable from a regression

    ci
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.28

View on GitHub
v0.0.1-alpha.27 prerelease 11 assets
2 changed · 2 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.27

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.27

Downloads

Changelog

2 changed · 2 fixed

Changed 2
    • Release process (changelog) — unreleased changes are now recorded as one fragment file per PR under .changelog/ instead of direct edits to CHANGELOG.md’s [Unreleased] section, which every concurrent PR used to merge-conflict over.
    more →

    CI lints the fragments and keeps [Unreleased] empty (scripts/changelog.py check); release prep assembles the fragments into the versioned section (scripts/changelog.py assemble), and the release gate fails if any fragment is left unconsumed.

    • CI (release safety) — publishing now requires the maintainer’s one-click approval:
    more →

    the release workflow’s four publish jobs run in a release environment with a required reviewer, so builds and tests stay unattended while nothing ships without a human seeing the exact SHA. Alongside the repository ruleset changes (required status checks, no bypass actors), routine merges no longer use --admin — the rules that were previously convention are now enforced, including on automation.

Fixed 2
    • CloudFormation (stack updates) — stack updates no longer destroy live resources behind pinned names, and resources whose services reject duplicate creates can now actually be updated.
    more →

    Two related engine/handler defects: first, a replacement whose create is an upsert keyed by a pinned name handed back the original physical ID, and both the post-success cleanup and the rollback path then deleted that ID — destroying the one live resource behind a stack that reported UPDATE_COMPLETE (confirmed with CloudWatch alarms); the engine now reports a same-ID replacement as an in-place update, which also makes the create-overwrite of every upsert-style service (Athena, Glue, Firehose, OpenSearch, MSK, ECS, WAFv2) the correct in-place behaviour. Second, pinned-name resources on services that 409 duplicate creates could not be updated at all, because replacement re-created the same name; their handlers now replace only on the properties real CloudFormation replaces on and apply the rest through the service’s own update API (CloudWatch PutMetricAlarm, Route53 UPSERT, EKS UpdateClusterConfig/UpdateClusterVersion/UpdateNodegroupConfig/UpdateAccessEntry/UpdatePodIdentityAssociation, Transfer UpdateUser, Pipes PATCH), while backup vaults, scheduler schedule groups, and the RDS/ElastiCache subnet groups — which have no modify operation — keep the live resource via a name-guarded identity update instead of a guaranteed 409. AWS::ECR::Repository, the original symptom, now updates RepositoryPolicyText and lifecycle rules in place instead of replacing on any property change, so re-running cdk bootstrap after a CDK upgrade succeeds instead of dying with RepositoryAlreadyExistsException, and GetAtt’s RepositoryUri is re-read from ECR so it reflects the registry this environment actually serves. Scheduler schedule groups with tags also create correctly now — CloudFormation-shaped tag lists were passed to the emulated API’s map form, failing every tagged create. Replacement-on-any-change is deliberately kept where it is real CloudFormation behaviour (Shield, ACM, LaunchConfiguration, IAM AccessKey, EKS FargateProfile).

  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.27

View on GitHub
v0.0.1-alpha.26 prerelease 11 assets
6 added · 4 changed · 42 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.26

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.26

Downloads

Changelog

6 added · 4 changed · 42 fixed

Added 6
    • CI (release candidates) — every build of a release PR now publishes the exact images CI built to GHCR as ghcr.io/overcast-sh/overcast[-slim]:<version>-rc.<n> (linux/amd64, <n> auto-increments per build, earlier RCs stay pullable), uploads all ten cross-built native binaries as workflow artifacts, and maintains a single release-notes-shaped bot comment on the PR with pull commands, image digests, and the artifact table.
    more →

    Until now the PR workflow built images and binaries only as checks and discarded them, so pre-release smoke testing always ran against local rebuilds rather than the candidate bits themselves. Release-candidate detection is one shared predicate rather than a branch-name convention: a same-repo PR whose VERSION carries no v<VERSION> tag yet (scripts/release-candidate-check.sh) — which also covers follow-up PRs after a failed release workflow, a case a release/* branch test is blind to; the release workflow’s changelog validation now uses the same predicate and so gains the same coverage. Fork PRs and ordinary PRs are unaffected. The ten-asset binary matrix, previously duplicated between the CI and release workflows and drifting one edit at a time, is now stated once in a shared workflow_call (build-binaries.yml) used by both — so the binaries checked on the release PR are built by the same definition that publishes them.

    • HTTPS / HTTP/2 (OVERCAST_TLS=auto, overcast https) — Overcast can now serve both the API and the web UI over browser-trusted HTTPS with certificates minted from a local overcast CA, unlocking HTTP/2 in browsers.
    more →

    This fixes the web console starving under load: browsers cap HTTP/1.1 at six connections per origin (localhost included) and never negotiate cleartext HTTP/2, so the console’s SSE feed, Lambda invoke progress streams, S3 transfers and polling could exhaust the sockets and make navigation hang; over TLS the browser multiplexes everything on one connection via ALPN. New overcast https enable|disable|status does the whole setup in one command (create the CA under <data dir>/ca, install it into the system trust store — user-scope certificate store on Windows, login keychain on macOS, system CA bundle on Linux — and mint a leaf covering localhost, 127.0.0.1, ::1, localhost.overcast.sh, *.localhost.overcast.sh, *.s3.localhost.overcast.sh, the other wildcard DNS domains, OVERCAST_HOSTNAME, and OVERCAST_SPLIT_HORIZON_HOSTS); overcast trust install|uninstall|status, previously a stub on every platform, now actually manages the CA in the trust store. OVERCAST_TLS=auto makes overcast serve mint/reuse that leaf at startup (re-minted automatically when the name set changes or expiry nears; leaves live 825 days, the CA 10 years, and re-minting never invalidates the trust-store install). Explicit OVERCAST_TLS_CERT/OVERCAST_TLS_KEY now applies to the web UI listener too, not just the API; the SPA bootstrap, minted client-facing URLs, and init-hook environment (AWS_ENDPOINT_URL, plus a new AWS_CA_BUNDLE) all say https when TLS is on; the Docker HEALTHCHECK probes https as a fallback; plain-HTTP defaults are unchanged, including h2c for SDK clients. serve --bridge is skipped with a warning while TLS is on (the port-80 proxy speaks plain HTTP). See the new docs/https.md. Docker setup is now two commands with no shared volume: the daemon serves its CA certificate (public half only) at GET /_overcast/ca.pem (404 until a CA exists; mirrored on the web UI as /api/ca.pem), and overcast https enable --endpoint http://localhost:4566 fetches it, validates it actually is a CA certificate, caches it under <data dir>/ca-remote/<host_port>/ (kept separate from the local CA so status/disable --endpoint find exactly what was installed), and installs it into the system trust store — http:// spellings auto-negotiate to a TLS-only daemon, non-loopback endpoints are refused without an explicit --trust-remote acknowledgement, and the same --endpoint works on overcast trust install|status|uninstall. A containerized daemon logs the exact --endpoint command at startup. Both Docker images serve TLS + HTTP/2 (the slim image on its API listener; both health checks handle https), and on Linux installed anchors are now fingerprint-named so a local CA and fetched daemon CAs coexist (anchors installed by earlier builds under the old fixed name are still recognised and cleaned up).

    • Lambda (concurrency) — a function now scales out to one execution environment per concurrent invocation instead of sharing a single warm container, and provisioned concurrency actually allocates environments.
    more →

    PutProvisionedConcurrencyConfig pre-initializes the requested number in the background (IN_PROGRESS → READY), holds them open against the idle sweep, replenishes them when one is lost, rebuilds them against the new configuration after a code/config update, restores them across a restart, and marks their containers AWS_LAMBDA_INITIALIZATION_TYPE=provisioned-concurrency; Allocated/Available are reported from the environments that actually exist. It behaves as a floor rather than a ceiling, so invocations beyond the reservation spill over into on-demand capacity with a cold start rather than throttling, matching AWS. Previously the config was stored, echoed back as READY, and nothing was ever pre-warmed. DeleteProvisionedConcurrencyConfig and ListProvisionedConcurrencyConfigs are now implemented, and all four operations moved to the /2019-09-30/ path AWS actually serves them on — the AWS SDKs’ calls previously missed the handlers entirely and fell through to the S3 catch-all. Reason: FAILED with a StatusReason is now reported when Docker is unavailable, instead of claiming capacity that cannot exist.

    • Lambda (concurrency limits) — ReservedConcurrentExecutions is now enforced:
    more →

    exceeding it returns AWS’s 429 TooManyRequestsException (Type/message/Reason/retryAfterSeconds body, X-Amzn-Errortype and Retry-After headers) with Reason: ReservedFunctionConcurrentInvocationLimitExceeded, so throttle-handling code and the “reserve 0 to disable a function” idiom now work as they do on AWS; it was previously stored and returned but never applied. The three reserved-concurrency operations also moved to the API versions AWS actually serves them on — PutFunctionConcurrency and DeleteFunctionConcurrency on /2017-10-31/, GetFunctionConcurrency on /2019-09-30/ — having been registered on Lambda’s /2015-03-31/ base, where no AWS SDK or CLI call could reach them: they fell through to the S3 catch-all and came back as 404 NoSuchBucket naming the API version as the bucket, so reserved concurrency could not be set at all through a real client. GetFunctionCodeSigningConfig had the same defect and moved to /2020-06-30/. A disabled Lambda now also reports “service disabled” on every API version it serves rather than letting the non-base ones fall through to S3. New LAMBDA_MAX_INSTANCES, LAMBDA_MAX_INSTANCES_PER_FUNCTION and LAMBDA_MAX_WARM_INSTANCES (default 10) bound how many containers Overcast will run on your machine; when unset, the first two — along with LAMBDA_DOCKER_MAX_CONCURRENT_STARTS — are sized to the machine Docker actually runs containers on (its GET /info NCPU/MemTotal, which is the Docker Desktop VM or remote daemon, not necessarily where the Overcast process runs): concurrent starts clamp(NCPU/2, 2, 8) because each start bursts ~2 CPUs during INIT, instances clamp(MemTotal×0.65 / 256 MiB, 4, 32), per-function clamp(maxInstances/2, 2, maxInstances), logged once at startup, with the previous fixed defaults (4/25/10) applying when /info cannot be read. New LAMBDA_MAX_MEMORY_MB additionally budgets memory in bytes rather than containers: a new container is admitted only while Σ MemorySize of live containers stays inside the budget (default 65% of the Docker host’s MemTotal; unlimited when /info cannot be read) — a real bound, since every container is hard-capped at its MemorySize with swap disabled — using the same reclaim → queue → throttle-at-timeout ladder, warning once (naming the env var) when first exhausted, and preferring to destroy rather than keep warm a just-finished container while the pool sits above ~90% of the budget; provisioned environments are never taken and running invocations are never interrupted. The pressure is visible at the default log level without per-container spam: entering the high-water regime warns once (budget, reserved, threshold — expect cold starts), recovering logs once with how many containers were shed and for how long, and a memory-bound idle-container reclaim warns where the routine count-cap reclaim stays at info — an invocation that cannot get one reclaims the least-recently-used idle container (never a provisioned one), then queues, and is throttled with Reason: ConcurrentInvocationLimitExceeded only if it is still waiting when the function’s timeout expires. Asynchronous invocations are never throttled back to the caller (they were already answered 202) and are retried internally instead; a throttled event source mapping batch is left in flight so its messages return to the queue on the visibility timeout. Account-wide quotas and requests-per-second limits remain out of scope.

    • Lambda (code signing) — the code signing configuration a function is created with is now stored and read back.
    more →

    GetFunctionCodeSigningConfig was a stub that reported “no config” for every function, and nothing accepted or stored a CodeSigningConfigArn anywhere, so a CDK Function with codeSigningConfig set had it silently dropped. CreateFunction now accepts the ARN, CloudFormation passes the property through so the association survives a deploy, and PutFunctionCodeSigningConfig / DeleteFunctionCodeSigningConfig are implemented on the /2020-06-30/ path AWS serves them on — they previously fell through to the S3 catch-all and returned NoSuchBucket. The ARN is validated against AWS’s documented pattern. Code signing is optional and that path is unchanged: a function without a configuration still answers ResourceNotFoundException, which is what AWS’s own model requires, since CodeSigningConfigArn is a required member of the success response and so “no config” cannot be expressed as one. The configuration itself is now a real resource: CreateCodeSigningConfig, Get, Update, Delete, ListCodeSigningConfigs and ListFunctionsByCodeSigningConfig are implemented on /2020-04-22/, along with the AWS::Lambda::CodeSigningConfig CloudFormation resource type, so a CDK stack that declares a CodeSigningConfig and points a Function at it deploys and reads back. AllowedPublishers is required and UntrustedArtifactOnDeployment defaults to Warn, both as AWS models them; a function naming a configuration that does not exist is rejected with CodeSigningConfigNotFoundException, and deleting one still referenced by a function returns ResourceConflictException. Signature validation itself is deliberately not emulated — Overcast is not a security boundary.

    • Web UI — the system map now shows a function’s real concurrency: one card per execution environment rather than one per function, ordered so environments doing work appear first, with a header summary of how many are running/idle/provisioned, a “+N more · scroll” hint when the list overflows, and provisioned environments marked and shown as reserved rather than counting down an idle TTL they are exempt from.
Changed 4
    • Web UI (connection status) — losing the emulator is now reported by a persistent toast in the bottom-right corner instead of a bar across the top of the shell, and it says what the bar could not:
    more →

    which reconnect attempt is pending, the seconds until it runs, when the data on screen was last live, and that writes are paused until the connection returns. retry now fires the pending attempt immediately, view events goes to the one page that still reads without the emulator, and the return is announced by a toast of its own, so a reconnect that happened while you were reading something else leaves a trace rather than being a disappearance you had to catch. Reconnection is Overcast’s own now, not the browser’s: an EventSource retries a dropped connection on a schedule it never exposes, and that is the common case rather than an edge one — a stopped emulator is a network-level failure, which leaves the connection in CONNECTING, and only a bad response closes it outright — so nothing could say when the next attempt was due and “retry now” had nothing to pre-empt. The connection is now closed on the first error either way and every attempt is scheduled by the SharedWorker that owns it, so all open tabs count down to the same moment, and one backoff (1s, 2s, 4s, then 5s) replaces the copy the worker and its no-SharedWorker fallback each kept. Resuming is unaffected: the last event id already travelled as a query parameter for exactly the reconnects a browser does not manage for us, so nothing is replayed twice or missed. The toast shares one bottom-right dock with ordinary toasts — it holds the corner and transient ones stack above it — and the topbar’s endpoint dot goes amber for a drop, matching the toast that explains it, rather than the grey it uses while a first connection is still outstanding.

    • Web UI (tabs and sidebar) — the browser tab now carries the icon of the service you are on, drawn from the same registry entry the sidebar renders and inked light or dark from the tab strip’s colour scheme, so a wall of open Overcast tabs is readable at a glance;
    more →

    a run narrowed with OVERCAST_SERVICES starts with its enabled services pinned in the sidebar instead of an empty pinned list, and those defaults are only written to local storage once you actually pin, unpin, or reorder them; a pinned service that this run has switched off is dimmed rather than hidden and stays clickable, so its page can explain why it is disabled; and CloudWatch’s sub-navigation lists Logs before Metrics.

    • OVERCAST_SERVICES removed — every service now runs, always;
    more →

    a leftover value is ignored rather than rejected, so an existing compose file or CI job keeps working. The variable never saved resources: services were constructed either way and their constructors are barred from store reads and I/O, so a disabled service cost about what an enabled idle one costs. What it did cost was 85 branches in the wiring, eight of them pairs — a Lambda function without logs ran but wrote no invocation logs, one without ec2 started with no VPC attachment, S3 notifications without sqs were never delivered. Those failure modes are now unreachable rather than merely documented. Calls to a service you did not ask for no longer return 503 ServiceDisabled, because there is no such state; the web console no longer dims services or seeds the sidebar from a narrowed run.

    • Build from source — git clone && go build ./... now works with only the Go toolchain:
    more →

    the docs search index (internal/docssearch/index.gen.go, web/src/docs-index.gen.ts) is committed rather than generated on every build, and a committed web/dist/.gitkeep keeps //go:embed all:web/dist resolving before the SPA is built. A binary compiled without the SPA serves the API normally and returns an explanatory 503 (naming make build-web) on the web UI port instead of a bare 500; Docker and release builds are unaffected and still assert a real SPA.

Fixed 42
    • TLS (containers) — Lambda functions and ECS tasks can now reach Overcast when it serves TLS.
    more →

    With OVERCAST_TLS enabled the API listener speaks only TLS, but containers were still handed http:// endpoints, so every SDK call from every function failed with Go’s plaintext rejection (“Client sent an HTTP request to an HTTPS server”) surfaced as SDK deserialization garbage. Container endpoints now follow the listener’s scheme (containerendpoint.BaseURL, shared by both runtimes), the trust root — the local CA in auto mode, the operator’s certificate chain in explicit mode — is injected into every container at /opt/overcast/ca.pem by the same CopyToContainer mechanism as function code (a dockerized Overcast has no host path to bind-mount), and AWS_CA_BUNDLE/NODE_EXTRA_CA_CERTS/SSL_CERT_FILE/REQUESTS_CA_BUNDLE point at it, covering the CLI, botocore, Go, Node, Ruby and python-requests stacks. The Java SDK reads only its own truststore — documented caveat in docs/https.md. Under TLS, environment/payload URL rewriting targets the SAN-covered hostname rather than the raw container address, which would trade a scheme error for a certificate error. Plain-HTTP deployments are unchanged: no CA material is injected and endpoints stay http.

    • Networking (client-facing URLs) / AppSync / API Gateway / Lambda / Cognito / CloudFormation — every URL Overcast mints now carries the port the caller reached it on, so it is dialable by the party it was handed to.
    more →

    With OVERCAST_HOSTNAME set and the API port remapped (docker run -p 4652:4566), AppSync’s uris, API Gateway v2’s apiEndpoint, Lambda function URLs and the Cognito issuer all carried the listen port and were undialable from the host — measured: SQS queue URLs said :4652 while AppSync said :4566 on the same instance. The repo had four hand-kept base-URL precedences (serviceutil, SQS, CloudFormation, Cognito’s typed path) and two disagreed on the port; they now share one implementation, serviceutil.ClientBaseURLFromOrigin: configured hostname (authoritative, #351), the caller’s port, and a TLS-aware scheme that upgrades and never downgrades. The Cognito issuer following the caller’s port is what OIDC Discovery 1.0 §4.3 requires — issuer must equal the URL the configuration was retrieved from — and Overcast’s own token validation is port-agnostic (the pool ID is read from the issuer path, guarded by test); the typed CBOR path used to mirror this precedence by hand and had drifted to the config port, so a caller’s wire protocol changed their issuer — both paths now call the same function. SQS wire responses deliberately keep echoing the caller’s exact origin (SDKs dial the QueueUrl itself; documented in code so a tidy-up cannot “unify” it away), and ECR’s repositoryUri stays on the configured host:port because the docker daemon, not the API caller, dials it. containerendpoint correspondingly rewrites split-horizon hostnames carrying the published port to the listen port on the way into function and task environment and invoke payloads — the name resolves inside a container but that port is bound only on the host; the patterns are precomputed so the per-invoke miss path allocates nothing. Full design and per-service constraints: docs/plans/client-facing-url-minting.md; user-facing summary in docs/networking.md.

    • S3 (POST on an object) — POST /{bucket}/{key} with no subresource now answers 405 MethodNotAllowed, as S3 does, instead of 501 NotImplemented.
    more →

    POST is only an operation on an object when a subresource selects one (?uploads, ?uploadId=, ?restore, ?select); without one there is no such AWS operation, so the two responses make different claims. NotImplemented — with its x-emulator-unsupported header — tells a client this emulator is incomplete and invites a workaround, for a request real S3 refuses too. That misdirection had a cost: it was the response surfaced when a CloudFront distribution routed a POST to an S3 origin, and it sent the investigation after a missing feature rather than a misrouted request. The subresource operations dispatch unchanged.

    • CloudFront (cache behaviors, origins) — two defects that sent a viewer request to the wrong origin, both silently.
    more →

    Path patterns now follow the documented CacheBehavior.PathPattern semantics: * matches across /, and the leading / is optional as AWS states (“CloudFront behavior is the same with or without”). The matcher special-cased a trailing * and otherwise used Go’s path.Match, whose * stops at a /. So AWS’s own *.jpg example matched nothing below the root, a wildcard in the middle (/api/*/detail) never matched, and — worst — a pattern written without a leading slash (api/*, jobs*) could never match anything at all, because a request path always has one. Such a behavior was dead: its requests fell through to the DEFAULT behavior and were served by whichever origin that named, with nothing logged to say so. Origin resolution now recognises any endpoint this emulator serves, by deferring to the same HostClassifier the router uses for inbound requests and dialling locally with the origin’s own Host preserved. Only the {bucket}.s3.{...}.amazonaws.com spelling had been recognised, by a bespoke prefix check; an S3 website endpoint, a legacy dash-region bucket ({bucket}.s3-{region}.amazonaws.com), or an origin naming any non-S3 service fell through to “custom origin” and was dialled at its literal domain — so a distribution fronting an emulated service reached out to real AWS over the internet instead of being served locally. API Gateway, Lambda function URLs and AppSync origins work as a result, and a service becomes usable as an origin as soon as it becomes routable, rather than needing its own case here. Genuine third-party origins are unaffected and still dialled as configured.

    • CloudFront (origin groups) / Web UI — a cache behavior whose TargetOriginId names an origin group rather than an origin is now served.
    more →

    CreateDistribution has always accepted the shape — validateOriginRefs adds every OriginGroup Id to the set of legal targets, citing AWS’s origin failover documentation — but the proxy resolved the target against Origins only, so every viewer request returned 502 Origin not found. CDK emits exactly this when a Distribution is given an origin group, so a stack could deploy clean and then fail on every request, with the distribution’s own config offering no hint that the target was legal but unreachable. Group members are now tried in declared order, with failover on the response codes the group’s FailoverCriteria lists and on a connection failure to the member. Failover is restricted to GET, HEAD and OPTIONS, as AWS restricts it — for any other method CloudFront returns the primary’s response as-is, and a request whose body has already been streamed to the first origin cannot be replayed to the second anyway. A member naming an origin that does not exist is skipped rather than failing the lookup; a group with no resolvable member still reports Origin not found, so a genuinely broken distribution does not become a silent one. The console’s distribution detail page gained an Origin Groups table alongside Origins, listing each group’s primary, its failover members and the status codes that trigger them — the origin list on its own could not explain where a distribution’s traffic actually goes.

    • API Gateway / AppSync / ECR (advertised URLs) — the URLs Overcast hands back now use the grammar AWS clients expect wherever the address they are minted on can carry them.
    more →

    The web console’s REST v1 invoke URL — what its copy button yields, and what its test panel calls — was built path-style as {base}/restapis/{apiId}/{stage}/_user_request_{path}, a shape no AWS SDK produces and nothing outside Overcast understands, even on a deployment whose every subdomain resolves; it is now http://{apiId}.execute-api.{region}.{base}/{stage}{path}. AppSync’s uris.GRAPHQL and uris.REALTIME were path-style for the same reason and are now host-routed too, which also fixes CloudFormation’s Fn::GetAtt GraphQLUrl and RealtimeUrl, since those pass the value straight through. Both fall back to the path-style form — still served, so nothing holding one breaks — only when the base cannot carry a subdomain (a bare localhost, whose *.localhost does not resolve on Windows or macOS, or an IP literal); that condition is now one predicate shared by the server and the console rather than a per-service judgement, so OVERCAST_HOSTNAME=localhost.overcast.sh gets the AWS shape everywhere and a default install keeps the URL that works there. A custom domain’s appsyncDomainName was minted as d-{hex}.appsync-api.{region}.amazonaws.com, a name that resolves to real AWS and can never reach the emulator — the same defect dns.GRAPHQL carried — and is now on the configured hostname. Fn::GetAtt RepositoryUri on AWS::ECR::Repository rebuilt {account}.dkr.ecr.{region}.amazonaws.com/{name} instead of reading the repositoryUri ECR had just returned, so a stack published a registry no docker push in this environment can reach while aws ecr describe-repositories on the same repository returned the working one; neither dkr nor ecr is a host-route label, so the stack-output re-hosting could not correct it after the fact. Unchanged and deliberate: Lambda FunctionUrl, API Gateway v2 apiEndpoint, AppSync dns.* and CloudFront DomainName were already host-routed; SQS queue URLs and Cognito’s OIDC issuer stay path-style because AWS addresses those as service endpoints too. See docs/plans/host-routing-precedence.md §8.

    • CloudFront (ViewerProtocolPolicy) — a distribution set to redirect-to-https no longer redirects to a scheme this server does not answer.
    more →

    Overcast listens for TLS only when OVERCAST_TLS_CERT and OVERCAST_TLS_KEY are both set, but the policy was enforced unconditionally, so on a default (plain HTTP) run every viewer request was answered with 301 https://{host}:4566/… — a URL that gives the browser a TLS handshake against an HTTP listener. redirect-to-https is a common template default, so a distribution could be completely unusable in the browser with nothing in the response explaining why. https-only had the same defect one step earlier, returning a 403 HTTPS is required that no client could ever satisfy, making the distribution permanently unreachable. Both are now enforced only when TLS is actually configured, and otherwise served as allow-all with a one-time warning naming the two environment variables; a TLS-terminating proxy in front that sets X-Forwarded-Proto: https continues to satisfy the policy as before. This is a deliberate divergence from AWS, which always serves HTTPS and can therefore always satisfy both — pointing a caller at an endpoint the emulator cannot answer is the worse of the two, the same trade already recorded for AppSync’s uris map and Cognito’s OIDC issuer scheme.

    • Networking (host-based addressing) — a Host header is now matched case-insensitively in every part, so the same address in any case reaches the same service.
    more →

    A hostname is case-insensitive (RFC 4343; RFC 3986 §3.2.2 for the URI authority), but only the base domain was folded — the .s3. separator, the bucket name, the service label and the region segment were all compared verbatim, so casing decided which service answered a request. An upper-case service label missed the dispatch table entirely and S3 virtual-hosted addressing then took the whole {id}.{label}.{region} as a bucket name, so UrlId.Lambda-Url.US-East-1.localhost — a Lambda function URL — came back as S3’s NoSuchBucket, and the API Gateway and AppSync invoke hosts did the same. This is not a hand-typed edge case: a browser lower-cases the Host before sending it, so it is what a user gets by pasting an address Overcast minted into the address bar. CloudFront is where it surfaced, because its distribution IDs are the only upper-case ones Overcast mints (E + 13 upper-case alphanumerics) and the lookup was verbatim: pasting a minted DomainName answered 502 Bad Gateway — Distribution "e…" not found — while the identical request with curl -H Host: succeeded. Host-derived distribution IDs are re-canonicalised to upper case, the only form the store holds. An upper-case region segment was worse than a misroute, because that region becomes the state store’s key prefix: a request on …execute-api.US-East-1.… partitioned its resources into a region no other request could name. S3 virtual-hosted addressing is fixed in both forms (MyBucket.localhost and mybucket.S3.localhost now resolve, where a bucket name is lower-case-only by AWS rule so an upper-case Host segment could only ever have meant a case-folded name). The same rule now applies on the way out, so what Overcast mints is canonical rather than an echo of however the caller typed the Host: every client-facing URL funnels through one helper (serviceutil.RequestBaseURL), so a queue URL, invoke endpoint or stack output created over MixedCase.localhost:4566 came back carrying that casing and was not string-equal to the same resource’s URL fetched over the lowercase host, despite addressing one endpoint. requestContext.domainName and domainPrefix — data function code can branch on — and CloudFront’s redirect-to-https Location are folded for the same reason. One shared implementation now serves both directions, since two copies of a case rule is exactly how this arose. Deliberately not folded: the Host used to verify a SigV4 signature, which must stay byte-identical to what the client signed. Paths are untouched and stay case-sensitive, matching AWS. Classification remains allocation-free on every path — folding is pay-per-use, so a host that is already lower-case is returned unchanged — at a measured cost of 4–13 ns per request, well inside the existing budget; see docs/plans/host-routing-precedence.md §4 and §7. Separately, a host-routed invoke now resolves in the region its own URL names: the region hint was read off the Host by a second, older service list that had drifted from the routing table and knew nothing of appsync-api, appsync-realtime-api or lambda-url, so an AppSync API or Lambda function URL created outside the default region was invoked against the default region’s partition of a region-scoped store and answered NotFoundException: GraphQL API … not found / 404 Function not found. These are exactly the requests with no SigV4 to fall back on — AppSync host-routed invokes authenticate with an x-api-key header and a Lambda function URL with nothing at all — so the Host was the only region evidence they carried. Lambda’s case survived a comment asserting the lookup was region-agnostic, which held only for resolving the URL config: the getFunction call immediately after it is region-scoped. Region extraction now reads the same label table the router dispatches on, so registering a host-routed service can no longer leave it resolving in the wrong region, and the documented precedence is unchanged — an explicit X-Overcast-Region, then the SigV4 credential scope, then the Host — so a signed request’s scope is never overridden by its Host. The same read also stopped taking the base hostname for a region when the address has none: an unsigned request to an ordinary S3 virtual-hosted address such as mybucket.s3.localhost.overcast.sh resolved to the region localhost and partitioned that request’s state under a name nothing else could reach — the same failure as an unfolded region segment, and invisible to signed callers because the credential scope resolves ahead of the Host.

    • Per-service storage overrides — OVERCAST_STATE_<SERVICE> naming a service that does not exist is now a startup error instead of being silently ignored.
    more →

    The override was read by probing OVERCAST_STATE_<name> for each known service, so a variable that loop never thought to construct — OVERCAST_STATE_CLOUDWATCH_LOGS for a service actually named logs, any typo, or a name that predated a rename — did nothing at all: no error, no warning, and the service quietly kept using the global backend while its owner believed otherwise.

    • SQS (dead-letter queues) / Web UI — ListDeadLetterSourceQueues returned its source-queue list under QueueUrls, but AWS names that one member lower-camel queueUrls — the single SQS list response that does, where ListQueues and the rest capitalise it.
    more →

    AWS SDKs decode an absent member as an empty list rather than erroring, so every SDK client, not just the console, saw a dead-letter queue with no source queues and had no way to distinguish that from a queue nothing dead-letters to. The Query-protocol XML was never affected and still renders repeated <QueueUrl> elements, because AWS models the member with that xmlName. The web UI gates DLQ redrive on this call exactly as AWS does — StartMessageMoveTask accepts “only ARNs of dead-letter queues (DLQs) whose sources are other Amazon SQS queues” — so the queue detail page’s redrive button had never appeared for any queue. It now does, and is no longer additionally disabled while the DLQ happens to be empty, a restriction AWS does not impose. Individual messages on a DLQ also gained a button that returns just that one message to the queue it failed on, routed by its DeadLetterSourceQueueArn the same way a whole-queue redrive routes each message back to its own source.

    • Lambda (Runtime API) — an invocation is no longer stranded when a function has more than one execution environment.
    more →

    GET /2018-06-01/runtime/invocation/next is a long poll, and the server tracked one waiting container per function ARN, so the second container to poll replaced the first one’s channel. The displaced container stayed blocked on a channel nothing would ever send to and never re-checked the queue, while SubmitInvocation delivered into the surviving buffered channel — a send that succeeds even when the reader has gone. Either way the invocation was never handed to a running container: the function logged START, produced no output for its entire configured timeout, and was reported as a timeout that the handler had no part in. The invocation also stayed in the function’s queue after the caller gave up, because CancelInvocation only removed it from the pending map, so the next container to start picked up work that had already been reported as failed and ran the handler for real — side effects included — under a dead request ID, its response then discarded as “invocation not found”. A second, later invocation would find the queue still holding the stale one and take that instead, so once a function tipped into this state it stayed there. Waiters are now tracked per container in FIFO order, an invocation claimed by a poll that unwinds is redelivered rather than lost, cancelled invocations are dropped from the queue and skipped at pickup, and a container’s parked polls are released when it is unregistered. The Extensions API had the same hazard on /2020-01-01/extension/event/next — an event handed to an unwinding poll vanished, and a lost SHUTDOWN is an extension never told to flush — and is now requeued the same way. Functions with a layered extension hit this most often (the AWS Parameters and Secrets Lambda Extension being the common case) because a longer, more variable cold start widens the window in which two containers poll at once.

    • Web UI (event stream) — the Events page now opens with the history that preceded it instead of starting from whenever you first looked at it.
    more →

    The server has always replayed its rolling history buffer to every new SSE client, and the app shell has always opened that connection on load, but the captured events were being thrown away before they could be shown. The subscription writes into the React Query cache with setQueryData, and nothing observes that query unless a consumer is mounted — so away from the Events page it was an unobserved query, garbage collected after the default five minutes, and setQueryData does not reschedule that timer. Opening the Events page afterwards then created the query afresh and ran its queryFn, which resolves to an empty list, wiping whatever had accumulated since the last collection. Both stream queries are now pinned for the lifetime of the page, registered as query defaults so the retention holds from the first event written rather than from whenever something first subscribed. Retention was also being undone from the other end: the SharedWorker kept the last 1,000 events and each tab the last 5,000, both as flat FIFOs, so the UI’s own polling — every request publishes a request:Received — filled them with telemetry and pushed out exactly the state changes a developer was looking for. Every client-side buffer now holds 10,000 events, matching the server’s own capacity, and evicts the way the server does: oldest request telemetry and heartbeats first, oldest overall only once no noise is left. Backscroll is also the first thing given back under memory pressure — the working capacity starts lower on a low-RAM device and halves while the JS heap is near its limit, recovering as room returns. Events are now delivered from the worker in batches rather than one message per frame, which matters most on the path that was already correct: a fresh load replaying thousands of events cost a postMessage, a cache write, a re-sort of the whole buffer and a full query-invalidation pass each, with the event→query-key map rebuilt every time. Ordering is fixed too — events were sorted by comparing timestamps as strings, and Go’s RFC3339Nano drops trailing zeros, so an event at …:05.5Z sorted before one at …:05Z. Finally, /_events now gives every frame an SSE id and honours the Last-Event-ID a reconnecting client sends back (or a last_event_id query parameter, for the reconnects a browser does not manage for us, where a fresh EventSource has no memory of the id — the BFF forwards both). Without it every reconnect replayed the whole buffer and a client that had kept its own copy ended up holding several: a laptop waking from sleep was enough. The id names the run as well as the position, so a token minted before a restart is discarded rather than believed — sequence numbers restart with the process, and honouring a stale one would silently skip the beginning of the new run. The resume point is the highest sequence number below which everything has been sent, not the newest id written, because the bus dispatches through a worker pool and events reach one subscriber out of order; that costs a handful of duplicate events at a drop and can never lose one.

    • Web UI (navigation under load) — clicking to another service now responds even while the emulator is busy.
    more →

    Routes are code-split, so a first visit to a page fetches its JavaScript chunk from the same HTTP/1.1 origin that carries the app-wide SSE stream, Lambda invoke progress streams, S3 transfers and polling queries — and a browser allows six connections per origin, so under a Lambda burst the chunk fetch could not start and the click looked dropped, with nothing painted to say otherwise. Every route’s chunk is now warmed through the router’s own chunk loader while the browser is idle after first paint — code only, one chunk at a time; loaders and queries still run on intent and navigation — so navigating stops needing the network once the tab has had a quiet moment. When a navigation does still wait (the first seconds of a session, or a loader in flight), the content area now paints the design system’s static skeleton rows almost immediately (after 50 ms, held ≥ 300 ms once shown) instead of nothing. The burst itself is tamed at both ends: the event stream’s flush window now widens 50→400 ms under sustained pressure and narrows back when traffic subsides, on the same halve/double hysteresis the buffer already uses for heap pressure, and each query key is invalidated at most once per second with a trailing call — so a burst no longer refetches instance lists and the topology graph twenty times a second, returning those sockets and that main-thread time to the page, while sparse events stay exactly as fresh as before.

    • Web UI (pinned sidebar items) — dragging a pinned service to reorder it no longer navigates to that service, and no longer scrolls the sidebar sideways.
    more →

    Two separate defects, both in the drag interaction. First, dropping a row — inside the sidebar or anywhere else on the page — followed the link under the pointer as a full page load. Once a drag activates, dnd-kit adds a capture-phase click listener on the document that calls only stopPropagation(); that is enough to stop React’s synthetic onClick, which is exactly where TanStack Router’s <Link> would have called preventDefault() and routed client-side, but the anchor’s default action was never cancelled, so the browser navigated itself. Because the sortable translates the row rather than rendering a drag overlay, the link under the release point was often a neighbouring pinned service, so the reload frequently went somewhere the user had not dragged from. A guard now cancels the click that terminates a drag; it is armed from onDragStart, which fires only once the sensor’s 6px activation distance is met, so a click without a drag is untouched, and a fresh pointerdown disarms it so a drag that never produced a click (Escape, released off-window) cannot swallow an unrelated click later. Second, the dragged row was given a horizontal translate that chased the pointer even though the list only reorders vertically. That pushed it past the nav’s right edge, which grew the nav’s scrollable width, which stopped dnd-kit’s auto-scroller seeing the nav as fully scrolled and set it scrolling sideways — and that scroll was fed back into the drag transform, pushing the row further right again. Dragging right and holding took scrollLeft past 15,000px. overflow-x: hidden does not prevent this, as the element is still a scroll container with no visible scrollbar. The drag is now pinned to the vertical axis, which removes the feedback loop at its source rather than damping the auto-scroller.

    • Lambda (invoke) / web console — a function that runs longer than 30 seconds now completes when invoked from the console’s Test tab.
    more →

    The BFF proxied the invoke progress stream through the shared HTTP client, whose 30-second Timeout covers reading the response body, so any longer invocation had its upstream request cancelled mid-run: the browser saw the stream close with no result and reported “Stream ended without result”, and the emulator recorded the cancellation as if the function had overrun — logging lambda invoke timed out and writing Status: timeout into the CloudWatch REPORT line for a function whose configured timeout, up to AWS’s 15-minute maximum, had not been reached. That pointed straight at the handler for a fault that was in the proxy, and was most visible on functions doing real work during INIT — fetching secrets through the AWS Parameters and Secrets Lambda extension, for instance — where the wall clock includes cold start. The stream is now proxied with the streaming client, and emits SSE keepalive frames every 15 seconds while the handler runs so a long invocation does not sit on an idle connection that proxies and browsers drop. A caller disconnecting is now reported as a cancellation with Status: error, distinct from a timeout; a function that genuinely overruns returns AWS’s payload — <timestamp> <request id> Task timed out after N.NN seconds — rather than a Go error string wrapped in Runtime.ExitError. Container teardown after either outcome stops the container with a 5-second grace period, so the docker:ContainerDied event that follows reports exit code 137 whenever the runtime does not handle SIGTERM; that is the teardown, not the cause.

    • Networking (container DNS) / Lambda / ECS — containers Overcast starts can now reach it by every hostname it advertises, not just the exact ones.
    more →

    The split-horizon names were pinned with Docker’s --add-host, and /etc/hosts is an exact-match table with no wildcard syntax, so a subdomain — {bucket}.s3.localhost.overcast.sh, {apiId}.execute-api.{region}.localhost.overcast.sh, the forms AWS SDKs actually build — missed the table, fell through to public DNS, and resolved to 127.0.0.1, which inside a container is the container itself. Because the lookup succeeded, it surfaced as a refused connection rather than an unknown host, and virtual-hosted S3 addressing and API Gateway invoke URLs were unusable from function code even though Overcast has always routed those Hosts server-side. Enumerating entries could not fix it: bucket names and API IDs are minted after a container starts, and a container’s /etc/hosts is fixed at creation. Overcast now serves the zone itself (internal/dns, UDP and TCP) and points containers at it with HostConfig.Dns; Docker keeps its embedded resolver in front and uses Overcast’s as an upstream, so container-name service discovery and external names are unaffected. Answers are per caller, because Overcast attaches to overcast_lambda and overcast_ecs by default and has an address on each. AAAA for an owned name is answered NODATA rather than forwarded, since the public record is ::1 and a dual-stack client would take it and dial its own loopback; forwarding is refused for peers outside loopback and RFC1918, so a published port 53 cannot become an open resolver. Disable with OVERCAST_DNS=false; a failed bind is logged rather than fatal, as port 53 needs privilege outside a container, and either way the /etc/hosts entries still cover the exact hostnames.

    • Lambda / ECS (endpoint) — AWS_ENDPOINT_URL inside containers is now a hostname (http://localhost.overcast.sh:4566) rather than Overcast’s raw address.
    more →

    An IP endpoint forces every AWS SDK to path-style addressing and makes the virtual-hosted URLs an SDK derives from the endpoint unusable, and an address baked into a warm execution environment stops working if Overcast’s container is recreated on a different one. /etc/hosts and the resolver still target the address, so the endpoint resolves whether or not the resolver is running. Invoke payloads now get the same URL rewriting function environment already receives: a queue URL minted by a host-side caller and handed to Invoke was the last route by which an unreachable origin reached a container, because AWS SDKs resolve the SQS endpoint from the QueueUrl rather than from AWS_ENDPOINT_URL.

    • CloudFormation (intrinsic functions) — Fn::Join and Fn::Select now resolve a list argument that is itself an intrinsic, and Fn::FindInMap, Fn::Base64 and Fn::Cidr are implemented.
    more →

    Fn::Select/Fn::Join asserted their second argument was already a list, so Fn::Select [n, {"Fn::Split": [...]}] resolved to an empty string and Fn::Join formatted the unresolved Go map into its output verbatim. That broke every CDK stack containing a nested stack: CDK builds the nested template’s S3 key as Fn::Select [n, Fn::Split ["||", ...]], so the key came out empty, the TemplateURL collapsed to /<bucket>/, S3 answered that as a ListObjectsV2 with HTTP 200, and the stack failed three layers away with cannot unmarshal string into cloudformation.Template. The same defect broke Fn::Select [0, {"Fn::GetAZs": ""}], the standard idiom for placing a subnet in an availability zone. Fn::FindInMap was never implemented at all — Mappings was parsed and carried on the resolve context but never read — so region-keyed lookups resolved to the raw map; a miss now yields an empty string rather than embedding Go’s map formatting into a resource property (real CloudFormation rejects the template, which the resolver has no way to signal). Fn::Base64 was recognised by the YAML short-form parser but had no resolver. Fn::Cidr is new, IPv4 only. Fn::ToJsonString and Fn::Length remain unimplemented; both require the AWS::LanguageExtensions transform. With these, the CDK compat suite goes from 4 passed/1 failed/30 skipped to 35 passed, 0 failed across the full bootstrap → synth → deploy → verify → update → destroy lifecycle.

    • CloudFormation (AWS::Events::EventBus) — Ref now returns the event bus name, as AWS documents, rather than its ARN.
    more →

    Consumers build an ARN as arn:aws:events:<region>:<account>:event-bus/ + name, so a Ref passed onward produced a doubled ARN (…:event-bus/arn:aws:events:…:event-bus/my-bus). Fn::GetAtt Arn still returns the ARN. This was invisible until the nested-stack fix above let a CDK deploy get far enough to exercise it.

    • Router (unimplemented AWS operations) — 675 more modeled AWS REST operations now return a protocol-correct 501 NotImplemented instead of an S3 error.
    more →

    A REST path shared by several AWS services — GET /tags/{resourceArn} is bound by roughly seventy of them, DELETE /workspaces/{workspaceId} by a handful — was treated as unclaimable and handed to S3’s catch-all, so an SDK calling one got NoSuchBucket rather than an honest “not emulated”. The ambiguity only ever decided which service to name, never whether S3 owned the path: the fallback already requires a SigV4 credential scope that is present and not S3’s, and no AWS SDK signs an S3 request as another service. That scope is therefore sufficient evidence on its own, and an exact modeled method-and-path match now returns the modeled 501 whether or not one service can be singled out. Requests that are unsigned, or signed for S3 or an S3-family service (s3-object-lambda, s3-outposts, s3express, which speak the S3 API under their own signing names and were previously only partly recognised), still reach S3 unchanged. Two further APIs that the credential scope alone cannot place are now resolved from the evidence they do carry: S3 Control shares S3’s signing name but sends x-amz-account-id on every operation, which S3 never does; CodeCatalyst models no SigV4 name at all and authenticates with a bearer token, an auth scheme S3 has no mode for. Both are only consulted when the scope is otherwise indistinguishable from S3’s, so S3 traffic pays one header read and never reaches the operation trie. No modeled non-S3 operation now falls through to S3, and a router-level corpus test over all 18,720 of them keeps it that way.

    • CloudFormation (stack outputs) — a stack output naming one of Overcast’s own path-style URLs is now re-minted on the origin the caller reached Overcast on, so it is dialable by whoever asked for it.
    more →

    Resource handlers build these URLs while provisioning, from the configured origin — provisioning runs through internal requests, so there is no caller to derive one from — and that value was then handed to every later caller unchanged. With the API port remapped (docker run -p 4600:4566, as scripts/run-test-instance.sh does) a stack’s queue-URL output named port 4566 while the same server’s GetQueueUrl returned 4600, and the output was undialable: AWS SDKs resolve the SQS endpoint from the QueueUrl rather than from client configuration, so a JS SDK client reading the stack output failed with the queue URL’s origin substituted for its endpoint. The AWS CLI masked this by honouring AWS_ENDPOINT_URL. Host-routed outputs were already corrected this way; this extends the same treatment to path-style URLs whose origin is recognisably Overcast’s own, leaving ARNs, ECR URIs and third-party endpoints untouched.

    • CloudFormation — stack outputs that name a host-routed AWS endpoint are now returned on an origin the caller can reach.
    more →

    CDK composes an API Gateway invoke URL in the template itself, with the scheme as a literal and no port (["https://", {"Ref":"Api"}, ".execute-api.us-east-1.", {"Ref":"AWS::URLSuffix"}, ...]), so DescribeStacks handed back https://abc.execute-api.us-east-1.amazonaws.com/prod/ — a URL that resolves to real AWS. AWS::URLSuffix cannot express a scheme or a port, so it still resolves to amazonaws.com as it does on AWS; the correction happens at output emission, where the finished string is parsed with the same grammar that routes inbound requests and re-minted through the same helper every service uses. Outputs that are not host-routed AWS endpoints — ECR registry URIs, S3 URLs, ARNs, plain strings — are left untouched, because Overcast does not serve those hostnames and must not claim to.

    • Networking (host-based addressing) — three more AWS hostname forms now reach the right service, and the recognised-domain list is no longer duplicated.
    more →

    {apiId}.appsync-realtime-api.{region}.{host} was not registered, so it was claimed as an S3 bucket named {apiId}.appsync-realtime-api.{region} — a subscription to the hostname AWS actually serves, and the one Amplify derives by substituting into the GraphQL URL, landed on the S3 handler. It now routes to the same endpoint as appsync-api, with the query string preserved because AppSync carries connection auth there. {distributionId}.cloudfront.net now reaches the distribution proxy instead of falling through, and Distribution.DomainName is minted on the hostname the caller reached Overcast on ({id}.cloudfront.{host}) rather than the literal cloudfront.net, which is a fixed AWS domain Overcast cannot serve without a DNS override. And localhost.floci.io was missing from the S3 virtual-hosted base list while internal/containerendpoint already advertised it, so a bucket was unreachable on a domain Overcast tells users resolves to it; both now derive from one list in internal/config, with a test that fails if they diverge again. docs/networking.md gains an inventory of every known AWS resource subdomain and whether Overcast routes it — AWS publishes no such list, and the SDK endpoint rulesets and Smithy endpointPrefix cover control-plane endpoints only.

    • API Gateway (HTTP v2) / AppSync — the endpoint URLs these services report are now ones Overcast can actually serve.
    more →

    API Gateway v2 never populated apiEndpoint at all, so CloudFormation’s Fn::GetAtt ApiEndpoint resolved to an empty string; it now returns the canonical {apiId}.execute-api.{region}.{host} endpoint on the hostname the caller reached Overcast on, and is omitted when DisableExecuteApiEndpoint is set, as AWS does. AppSync’s dns map hardcoded amazonaws.com, advertising names that resolve to real AWS and can never reach the emulator; it now carries the configured hostname. dns.REALTIME deliberately reports the same host as dns.GRAPHQL, because Overcast colocates the GraphQL and realtime endpoints and appsync-api is the host that routes — an appsync-realtime-api name would be one Overcast hands out but cannot serve. AppSync’s uris map deliberately stays path-style: the host-routed form needs *.localhost to resolve, which it does not on Windows by default, and advertising a URL the caller may be unable to dial is worse in practice than the shape difference. All three services now mint through one shared helper, so a URL Overcast returns is by construction one its own router resolves back to that resource.

    • S3 (bucket naming) — bucket-name validation now matches AWS’s documented rules in both directions.
    more →

    Names AWS accepts were being rejected: any name containing a period (example.com, www.example.com and my.example.s3.bucket are AWS’s own documented valid examples — periods are discouraged, because they break the *.s3.<region>.amazonaws.com wildcard certificate for virtual-hosted-style addressing over HTTPS, but they are legal), and any name with consecutive hyphens, which AWS permits — it forbids two adjacent periods, and its own reserved suffixes --ol-s3, --x-s3 and --table-s3 contain double hyphens. Rejecting a valid name is the worse direction, since it fails a stack locally that deploys fine against AWS. Names AWS rejects were being accepted: the reserved prefixes xn--, sthree- and amzn-s3-demo-, the reserved suffixes -s3alias, --ol-s3, .mrap, --x-s3 and --table-s3, and names containing two adjacent periods. Because periods are now accepted, a bucket whose name carries a host-routed service label (execute-api, lambda-url, appsync-api) as a second-or-later dot segment is reachable path-style and as {bucket}.s3.{host} but not in the bare {bucket}.{host} form, where it parses as a service address; CreateBucket logs a warning naming the label and both alternatives, and still creates the bucket, as AWS does.

    • Networking (host-based addressing) — every Host-routed invoke URL on a hostname Overcast can actually resolve now reaches its service.
    more →

    API Gateway (REST v1 and HTTP v2), Lambda function URLs and AppSync GraphQL were all reachable only on an .amazonaws.com Host, which does not resolve to the emulator without a hosts-file entry; on localhost, localhost.overcast.sh, localhost.localstack.cloud or a configured OVERCAST_HOSTNAME they returned 403 Missing Authentication Token. S3 virtual-hosted addressing and host-routed service dispatch were separate middlewares that both claimed the same request: the S3 matcher took everything in front of the base as a bucket name, so abc123.execute-api.us-east-1.localhost yielded a bucket called abc123.execute-api.us-east-1 which was prepended to the path before the service rewrite prepended its own prefix on top, and API Gateway then read the mangled first segment as a stage name. Lambda was the sharpest case, since CreateFunctionUrlConfig minted exactly such a URL and Overcast then refused to serve it. The two are now one decision with a fixed precedence derived from the host grammar rather than from middleware registration order, so a bucket and a service can never both claim a Host. Classification also became allocation-free and runs once instead of twice: a plain path-style request costs 31.9 ns and no heap allocations, down from 230 ns and four (Go 1.24.13, linux/amd64, AMD Ryzen 9 5900X, median of 3).

    • Cognito (OIDC issuer) — the iss claim in every minted JWT, and every endpoint in the OIDC discovery document, now come from the configured external origin instead of the Host header the authenticating caller happened to send.
    more →

    OVERCAST_HOSTNAME was ignored outright, so a token minted for a host CLI claimed an issuer a sibling container could not dial, and vice versa — and because an OIDC client validates iss and fetches signing keys from {iss}/.well-known/jwks.json, the mismatch failed token validation rather than degrading. The scheme was hardcoded http as well, so with OVERCAST_TLS_CERT/OVERCAST_TLS_KEY set the advertised issuer and jwks_uri pointed at a scheme the server does not answer. Both now resolve through the same helper every other client-facing URL uses, which derives host, port and scheme together from config; authorization_endpoint, token_endpoint, userinfo_endpoint and revocation_endpoint moved with them. Token validation is unaffected — the pool ID is read from the issuer’s trailing path segment, never compared against a recomputed string — so tokens issued before this change still verify. Separately, a client speaking Smithy RPC v2 CBOR was given a different issuer again: CBOR dispatches to Cognito’s typed operation table, whose handlers receive a context rather than a request, and the issuer built there was a bare {region}/{poolId} with no scheme or host at all — unusable for OIDC key discovery and rejected outright by an API Gateway JWT authorizer, which compares the claim exactly. All twelve typed auth flows now resolve the same origin the JSON path does, so a caller’s wire protocol no longer changes the issuer their token carries.

    • Lambda / API Gateway / CloudFront (account IDs) — resources and invoke events now report the configured OVERCAST_ACCOUNT_ID rather than the default account.
    more →

    requestContext.accountId was the literal 000000000000 in Lambda function-URL events and in all three API Gateway proxy-event shapes (REST v1, HTTP v2, and v2 routes on the v1 payload format), so customer handler code reading it — to compose an ARN, or to assert which account it is running in — saw the wrong account on any server started with a different one. CloudFront function and realtime-log-config ARNs were hardcoded the same way, which meant a CloudFront function and a CloudFront distribution created on the same server reported different accounts, since distribution ARNs already read config correctly.

    • SMTP (mail capture) — the mock SMTP server no longer hangs on shutdown when a connection arrives while it is stopping.
    more →

    The accept loop’s break on a cancelled context ended its enclosing select rather than the loop, so the server closed the connection and then started a session handler for it anyway. That handler released a concurrency slot it had never acquired: with no other sessions in flight the release blocked forever, Serve never returned, and shutdown stalled until the caller’s timeout fired; with sessions in flight it took a live session’s slot instead, permanently lowering how many connections the server would go on to accept. Reaching it needed a connection accepted in the window between the context being cancelled and the listener closing, and that was roughly a coin flip per connection rather than a rare race, since Go chooses at random when both select cases are ready.

    • Lambda (event source mappings) — shutting the service down no longer hangs waiting for ESM delivery goroutines that have no stop signal to observe.
    more →

    StopAll cancelled only the per-mapping delivery contexts it had registered, but the delivery manager’s base context was context.Background(), so the ReloadAll goroutine that resumes persisted mappings at startup — tracked by the very WaitGroup the shutdown drain waits on — had nothing that could cancel it; parked waiting for a persistent store to become ready, it held the drain open indefinitely. A ReloadAll that instead woke up mid-teardown could register a fresh SQS poller after the drain had begun, stranding a poller nothing would stop and adding to a WaitGroup already being waited on. Both windows only opened while ReloadAll was still running at shutdown, so this surfaced as a rare, load-dependent stall rather than a reliable failure. The base context is now cancelled by StopAll, delivery started after shutdown begins is refused, and the drain is bounded by the caller’s shutdown context — logging loudly instead of blocking forever, the way cmd/overcast’s bounded store close already did.

    • Router / REST APIs — modeled REST JSON and REST XML operations that no configured handler claims now return protocol-correct 501 NotImplemented instead of falling through to S3. The generated path trie is consulted only after explicit routes, and SigV4’s modeled service scope disambiguates AWS SDK/CLI/CDK traffic from legitimate S3 bucket and object paths.
    • Router / AWS JSON API — modeled AWS JSON targets that no configured service dispatcher claims now return 501 NotImplemented with the standard unsupported-operation marker and request ID, rather than the legacy 400 UnknownOperationException; existing service dispatchers and legitimate S3 traffic retain precedence.
    • Router / Smithy RPC v2 — modeled RPC v2 CBOR operations, including services that advertise CBOR alongside AWS JSON, now return a CBOR 501 NotImplemented when no configured dispatcher implements them instead of 415 UnsupportedProtocol;
    more →

    a known disabled service returns 503 ServiceDisabled before modeled-operation ownership is considered; RPC v2 JSON uses the same generated ownership path when AWS publishes modeled operations for it, while headerless S3 multipart requests whose bucket/key happens to match the RPC path grammar remain with S3.

    • CloudFormation — a stack can now contain more than one resource of a type that its template does not name, and a failed update no longer destroys the resource it was replacing.
    more →

    Physical names for unnamed resources were built from the stack name alone ({StackName}-Function), so every unnamed resource of a type in a stack got the same name and the second collided with the first — ResourceConflictException: Function already exist. Since CDK almost never names its resources, leaving CloudFormation to generate one, a stack with two Lambda functions could not deploy at all; the same fallback affected queues, tables, topics, buckets, roles, policies, instance profiles, secrets, log groups, log streams, SSM parameters, layer versions and event buses. Generated names now follow CloudFormation’s own shape, {StackName}-{LogicalID}-{RANDOM}, truncated to each service’s limit with the unique suffix preserved. Separately, replacing a resource deleted the old one before creating its replacement, so if the update then failed anywhere the original was already gone and rollback had nothing to restore — the update destroyed the resource outright. Replacement now creates first and defers deleting the original to a cleanup phase that runs only once the whole update succeeds, so a failure rolls back to the intact original and removes the replacement instead; this is what the random name component is for, since the two must coexist. An unnamed resource also keeps its generated name across an update rather than being renamed into a needless replacement, and the removal phase is now reported as AWS reports it — UPDATE_COMPLETE_CLEANUP_IN_PROGRESS and UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS on the way to the matching terminal status, so a stack held up by a resource that will not delete is visibly in cleanup rather than looking mid-update. Two further rollback defects went with it: a rollback rebuilt the stack’s resource list from a map the update had been emptying as it went, so every resource handled before the failure was dropped and the stack came back believing it owned nothing while those resources still existed — the next update would then try to create them on top of themselves; and a replacement under UpdateReplacePolicy: Retain left its replacement behind on rollback, so the retained original and the abandoned replacement both survived with nothing recording which one the stack owned.

    • Web UI (container port mapping) — the console now works when Overcast’s ports are remapped, e.g.
    more →

    docker run -p 4580:4566 -p 4581:4567. The SPA is handed its API endpoint by the server, which built it from the port Overcast listens on; inside a container that is the internal port, so a console opened on the published UI port was told the API was on localhost:4566, which is closed on the host, and every request from it failed. Overcast now asks Docker for its own container’s port bindings and gives the browser the published host port, falling back to the listen port when there is no answer to be had — not containerised, no Docker socket mounted, or the port not published — so native binaries and 1:1 mappings are unchanged. The /api/* proxy the console calls server-side is unaffected by the same change: an endpoint naming Overcast’s own published port on a loopback host is mapped back to the internal port before being dialled, since that port exists only outside the container. This affected every non-default port mapping, including the port pair scripts/run-test-instance.sh picks.

    • Router / Query API — unclaimed AWS Query GET and POST actions now return 501 NotImplemented with the standard unsupported-operation marker and request ID, rather than GET falling through to S3 or POST returning a bare 400; requests for a disabled Query service now report ServiceDisabled.
    • ECS — task containers can now actually reach Overcast.
    more →

    AWS_ENDPOINT_URL was hardcoded to host.docker.internal, a name Docker Desktop synthesises but native Linux does not resolve at all, so on Linux hosts every task’s AWS calls failed outright; it now carries a real address — Overcast’s own IP on the ECS network when it runs in a container, otherwise the host address on the interface carrying the default route (skipping link-local 169.254.0.0/16 addresses and virtual switches a container cannot route to), with host.docker.internal kept only as a last resort and paired with a host-gateway /etc/hosts entry so it resolves off Docker Desktop too. Separately, AWS SDKs resolve the SQS endpoint from the QueueUrl rather than from AWS_ENDPOINT_URL, so a queue URL baked into a task definition by a host-side cdk deploy sent the task’s SQS client to its own loopback; loopback origins on Overcast’s port are now rewritten in task-definition and RunTask-override environment values, and split-horizon hostnames (localhost.overcast.sh, localhost.localstack.cloud, localhost.floci.io, plus OVERCAST_HOSTNAME and the new comma-separated OVERCAST_SPLIT_HORIZON_HOSTS) are mapped to Overcast in each task container’s /etc/hosts, so one URL works from both the host and inside the task.

    • Web UI — expanded sidebar rows are clickable across their whole height and width.
    more →

    The row element carried the padding and the hover highlight while the link inside it only filled the content box, leaving a 10px horizontal / 7px vertical ring that highlighted on hover but swallowed the click; every row in the expanded sidebar was affected — nav links, pinned favourites, and the expand/collapse toggle on services with sub-pages — and the drag grip on a pinned favourite no longer blocks clicks on the row’s left edge. Row dimensions and styling are unchanged. An object’s name in the S3 bucket listing is now a link that opens the same object inspector the row’s eye action does, instead of being inert text. Object rows also no longer show a pointer cursor or the interactive hover tint: those advertised a whole-row click that did nothing but toggle a selection highlight nothing else read. Folder rows, which do navigate on click, keep both. Tables and scrollable panels now share one subtle scrollbar treatment across Firefox, Chromium, and Safari, with the system’s own scrollbars preserved in forced-colors mode. On the Metrics & Health page, the Advisories and Storage Activity sections no longer drop back to their loading skeletons every three seconds while debug mode is off, shifting the page below them on every poll: GET /_debug/metrics returning “debug disabled” is now an answer the poll carries rather than a fetch error, which stops the query from being rewound to its loading state on each refresh. The health strip’s Journal Mode also no longer reports “Debug mode required” when debug mode is on and the backend is memory-only — there is no SQLite journal mode to report there, and it now says so. On the Lambda function detail page, the Test, Versions, Monitor, Configuration and Triggers tabs no longer open flush against the tab bar: each panel rendered at a 0px offset, so bare headings and text sat directly on the rule and a narrower card read as jammed under it. They now use the same 16px offset the ECS, Cognito and VPC detail pages already did. The Code tab keeps its flush alignment, since its editor is a full-width bordered slab whose top edge continues the rule. Every copy-to-clipboard control in the console now works outside a secure context: navigator.clipboard is gated on one, so it is absent — not failing, absent — whenever the console is served over plain HTTP from anything other than localhost, and all thirteen copy buttons read it directly. On http://localhost.overcast.sh, the hostname Overcast now points containers and docs at, on a LAN address, or on a container hostname, clicking Copy did nothing at all and said nothing about it. Copies now fall back to a hidden textarea driven by document.execCommand("copy"), which has no secure-context requirement, and both outcomes are reported as toasts rather than only the happy one — several controls (EC2, ECS and RDS detail pages, the system map) previously gave no feedback either way, so a silent no-op was indistinguishable from success. Copy is now one component across the console, which also gave the icon-only buttons the accessible names they never had, and added a copy action to each row of the event stream that yields the whole event envelope as formatted JSON. Reading the clipboard has no such fallback — execCommand("paste") is refused everywhere and Firefox never exposes readText to page scripts — so Lambda’s “Paste .env” control is now disabled with an explanation where it cannot work, instead of appearing live and doing nothing.

    • CloudFormation / Lambda — functions declared with inline code (Code.ZipFile) are now packaged into a real deployment archive, so they can actually be invoked.
    more →

    A template’s Code.ZipFile is source text while the Lambda API’s is a base64 zip; the source was passed straight through, so every inline-code function created cleanly and then failed at invoke with build code tar: open zip: zip: not a valid zip file. Affects AWS::Lambda::Function on both stack create and update, and therefore any CDK construct that injects an inline handler — including BucketNotificationsHandler, which CDK adds automatically to any stack whose bucket has notifications, so stacks that declared no Lambda of their own were affected too. The console also no longer presents its example “Hello from Lambda!” stub as a function’s real code: unreadable packages are labelled as examples, and code stored unpackaged by earlier versions is now shown as-is. Existing broken functions are repaired by redeploying the stack.

    • Lambda — changing a function’s configuration now retires its warm execution environments immediately instead of leaving the old containers serving invocations until the 15-minute idle timeout.
    more →

    Previously only UpdateFunctionCode invalidated the warm container, and only lazily — on the next invocation — so editing environment variables (in the web UI, via the AWS CLI/SDK, or through CloudFormation/CDK) appeared to have no effect: the next invoke was served by the container started with the old values. Now any update that changes what the container was built with — environment variables, memory, timeout, handler, runtime, layers, VPC config, image config, architecture, or code — destroys idle containers as soon as the update is stored, and lets containers that are mid-invocation finish before destroying them, matching AWS, which never interrupts an in-flight invocation. Updates that change nothing the container can observe, such as the description or role, keep the warm containers, so cosmetic edits cost no cold start. The same retirement applies to code refreshed reactively from S3. GET /_lambda/instances and the system map now report one entry per execution environment. They were keyed by function name, so a function running five concurrent invocations reported a single instance; the web UI had been built for multiple all along but the data could never contain them. Two related leaks are fixed alongside: deleting a function stopped its containers but left phantom instances listed for 15 minutes, and a retired environment stayed listed as reusable until the idle sweeper ran. a warm container that disappears without Overcast asking — removed with docker rm -f, OOM-killed, or lost to a Docker restart — is now dropped from the warm set as soon as the Docker event stream reports it. Previously the pool kept treating it as warm, so the next invocation was handed the dead container, submitted the event to a Runtime API nothing was polling, and hung for the function’s entire configured timeout before failing (only the invocation after that cold started). Docker container-died events are now also subscribed reliably: the subscription raced Docker probing, and if probing won, exit detection was silently disabled for the process lifetime, meaning a container that crashed mid-invocation also hung until the timeout instead of failing immediately.

    • SQS, Lambda — queue URLs are now minted per request, on the origin the caller reached Overcast on, so a host CLI and a sibling Lambda container each get a URL they can actually dial.
    more →

    This matters because AWS SDKs resolve the SQS endpoint from the QueueUrl rather than from client configuration and AWS_ENDPOINT_URL does not override that (see the ECS entry above for the mechanism), so a single server-wide hostname could only ever satisfy one side: an SQSClient inside a Lambda function was sent to its own container by a localhost:4566 queue URL, while the documented Docker Compose setup handed host-side clients an overcast:4566 URL that does not resolve on the host. OVERCAST_HOSTNAME becomes the fallback for callers without a usable origin rather than a server-wide override, requests arriving on a real AWS hostname still fall back to it, and queue URLs minted on any origin remain accepted since only the queue name is read from them. Lambda containers additionally get the same environment-rewriting and split-horizon /etc/hosts treatment as ECS tasks, so a queue URL baked into function environment by a host-side cdk deploy reaches Overcast too. That rewriting now also recognises URLs minted against the host port Overcast is published on, not only the port it listens on: a container started with remapped ports (docker run -p 4580:4566, as scripts/run-test-instance.sh does) listens on 4566 while host callers mint queue URLs on 4580, so a baked-in URL was previously left untouched and the function’s SQS client dialled its own loopback on a dead port. URLs a function fetches itself were never affected.

  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.26

View on GitHub
v0.0.1-alpha.25 prerelease 11 assets
6 added · 10 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.25

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.25

Downloads

Changelog

6 added · 10 fixed

Added 6
    • Networking — real AWS Host-routed (subdomain) addressing now works alongside path-style URLs, via a shared host-route dispatch table (internal/middleware/hostroute.go):
    more →

    API Gateway invoke ({apiId}.execute-api.{region}.<base>, both REST v1 stage-prefixed and HTTP v2 $default/named-stage forms), a brand-new Lambda function URLs feature (CreateFunctionUrlConfig/Get/Update/Delete/ListFunctionUrlConfigs plus Host-routed invocation mapping the request to the payload-v2.0 event shape; AuthType is stored but not enforced, matching Overcast’s non-security-boundary stance), and AppSync GraphQL ({apiId}.appsync-api.{region}.<base>/graphql, also covering the colocated /realtime endpoint). See docs/networking.md for the wildcard-DNS (*.localhost.overcast.sh) setup story and its offline caveats.

    • Diagnostics — GET /_debug/metrics and the Metrics & Health page’s new “Storage Activity” card report cumulative storage-layer reads/writes since process start for every backend (memory, persistent, WAL, hybrid);
    more →

    a hybrid-mode store additionally breaks reads down by which tier actually served them (memory vs. a fall-through to SQLite) and shows how many accepted writes have been flushed to disk so far.

    • Events — resource lifecycle events (SQS queues, S3 buckets, DynamoDB tables, SNS topics/subscriptions, Lambda functions, and most other resource-create/delete/update events) now carry the resource’s ARN on the event envelope (resourceArn in the SSE stream and MCP event feed); the web UI’s Event Stream page auto-links any ARN it finds — the new field, or one embedded anywhere in a payload/error string — to that resource’s page for every service that has one.
    • CloudFormation — RollbackStack is now supported, so cdk rollback can recover a stack that a failed deploy left stuck:
    more →

    a stack in UPDATE_FAILED or UPDATE_ROLLBACK_FAILED rolls forward through UPDATE_ROLLBACK_IN_PROGRESS to UPDATE_ROLLBACK_COMPLETE, and a CREATE_FAILED stack unwinds through ROLLBACK_IN_PROGRESS to ROLLBACK_COMPLETE, emitting the usual stack events along the way; previously the operation returned 501 and a failed cdk bootstrap/cdk deploy left the stack permanently un-deployable, since the CDK CLI refuses to retry until the stack is rolled back. Any state without a stable state to return to (CREATE_COMPLETE, UPDATE_COMPLETE, in-progress states) is rejected with the AWS-accurate 400 ValidationError. Note that Overcast keeps no snapshot of each resource’s pre-update properties, so a rollback restores the stack’s status and retires resources left in a failed state rather than reverting individual resource configuration.

    • S3 — virtual-hosted-style addressing now recognises localhost.overcast.sh and localhost.localstack.cloud as base hostnames with no configuration (both are wildcard-DNS domains resolving to 127.0.0.1), so an SDK or CDK pointed at either resolves buckets correctly out of the box;
    more →

    OVERCAST_HOSTNAME still works and now adds to those defaults rather than replacing them, and bases are matched longest-first so a configured parent domain cannot shadow a longer default. When a request still looks virtual-hosted against an unrecognised base, the server now logs a warning (once per distinct host) naming the received Host, the configured OVERCAST_HOSTNAME, and the fix — previously the bucket was silently not extracted, the request stayed path-style, and the object key was then parsed as the bucket name, producing a confusing “bucket name is not valid” error naming the key rather than the real problem.

    • Web UI — the console is restyled to the Overcast brand design system:
    more →

    brand colour tokens and JetBrains Mono throughout, working in both light and dark themes (including a theme-aware favicon.svg that follows prefers-color-scheme), a 228px sidebar and 52px topbar, breadcrumbs that omit the current page since its heading already names it, a search box that is present on every page and ranks results from the service you are currently viewing first, and a dashboard that groups services by how completely they are emulated (fully/partially/not emulated) with a grid-or-list view toggle that persists between visits. Every service list page now shares one layout — a card-wrapped table with mono column headers, a per-row type icon, consistent row actions, and the same Docs/Raw state/Refresh/Create header ordering — so pages no longer each invent their own. The command palette follows suit: service icons are monochrome, pin stars are always visible rather than appearing on hover, and services the emulator has disabled are greyed out and non-navigating instead of looking available. Typography now follows the brand rule throughout (JetBrains Mono for headings, labels, badges, table bodies and identifiers; the system sans for prose), and the dashboard no longer files a running service under “not emulated” merely because its emulation tier is incomplete. Lists now load into static skeleton rows rather than a centred spinner; dialogs gained a header/body/footer anatomy and the ⏎ to create · esc to cancel contract they previously only advertised; toasts gained a pending variant, so a slow upload or deploy reports progress instead of nothing; and an emulator that is configured but unreachable now shows a connecting screen with a retry, where it previously rendered nothing at all.

Fixed 10
    • S3 — virtual-hosted-style addressing (bucket name in the Host header, e.g.
    more →

    bucket.s3.localhost:4566) now also recognises the legacy dash-region dialect bucket.s3-<region>.<base> (e.g. bucket.s3-us-west-2.amazonaws.com); it previously mis-extracted the bucket name as bucket.s3-<region> instead of bucket, breaking every request. Added integration-level pinning coverage (previously unit-test-only) confirming the existing .s3./plain-.localhost/OVERCAST_HOSTNAME forms already route correctly end-to-end through the real router, including dotted bucket names, nonexistent-bucket/key error fidelity (NoSuchBucket/NoSuchKey), and sub-resource calls (?location).

    • Wire protocols — JSON-protocol services using the shared request decoder now reject an unparseable request body with 400 SerializationException, matching real AWS (DynamoDB-style coral services and Smithy’s malformed-request protocol tests), instead of the non-AWS InvalidArgument code; five services (Kinesis, Backup, CloudTrail, Transfer, ECS) already returned the correct code via local variants and are unchanged.
    • AppSync, API Gateway — request header names reach resolvers and integration events with AWS’s casing rather than Go’s.
    more →

    Go canonicalises incoming header names, so a client’s x-api-key arrived as X-Api-Key, and that casing was copied verbatim into the VTL and JS resolver contexts, the direct Lambda resolver event, and the Lambda authorizer’s requestHeaders. AppSync documents ctx.request.headers in lowercase and VTL map access is an exact lookup, so a resolver written from the AWS docs as $ctx.request.headers.get("x-api-key") silently returned null — authentication itself was never affected, which is why this presented as “the header works but my resolver cannot see it”. Two API Gateway divergences found alongside it: REST (payload 1.0) headers and queryStringParameters returned the first duplicate value where AWS returns the last, with all values in multiValueHeaders; and the HTTP API payload-1.0 branch emitted lowercase comma-joined headers beside Go-canonical multiValueHeaders in the same event.

    • Web UI — hover, focus and busy states now match the design and, in several places, exist at all:
    more →

    a dozen surfaces used hover classes that were never declared and so did nothing, a focus-visible:: typo meant that ring never compiled, buttons never showed a pointer cursor, and busy buttons were marked disabled so they dimmed and read as unavailable while working. Two keyboard defects went with them — inactive tabs were unreachable (no arrow-key handling behind tabIndex={-1}) and clickable table rows could not be focused. The event stream is legible in light mode, having previously painted itself on a hardcoded near-black panel with text chosen to suit it; its JSON now shares the themed palette the JSON editor already used, and its per-service colours became a defined ramp that keeps two previously-identical pairs apart. EC2 instance state showed contradictory colours between the list and detail views — stopped red on one and neutral on the other, terminated the reverse — because each page defined its own badge. Disabled services now sort to the bottom of their dashboard section rather than sitting between services you can open.

    • Web UI — the system map no longer overlaps nodes when a Lambda function has many concurrent instances:
    more →

    Lambda group nodes now cap at four visible instance rows and scroll internally, matching the existing SQS/CloudWatch Logs node pattern. The Raw State Debugger’s selection is now URL-backed — reloading or sharing the URL reproduces the exact view, picking a namespace pushes a history entry while clicking through keys replaces it (so back/forward steps through namespaces, not every key ever clicked).

    • Diagnostics — the slow-data-directory startup warning and Metrics & Health advisory no longer tell users already on a named Docker volume to “switch to a named volume”:
    more →

    the fsync probe now looks up the data directory’s actual filesystem type (/proc/mounts) and tailors the advice — bind/file-sharing mounts (9p, virtiofs, grpcfuse) still get the named-volume recommendation, while native filesystems (ext4 etc.) get host/VM I/O-pressure guidance instead. The probe also now takes the median of three fsync samples rather than a single boot-time sample, so one transient stall during container startup no longer raises a false alarm; GET /_debug/metrics reports the detected fsType and mountClass alongside the probe result.

    • Logging — an invalid OVERCAST_LOG_LEVEL no longer prevents the emulator from starting:
    more →

    it now logs a warning naming the configured value, the effective fallback (info), and the valid values, then continues — previously a typo in this observability setting was a fatal startup error. The SSE event stream’s periodic heartbeat log line moved from debug to trace (it fires on an idle server — the log-level policy’s litmus test), and heartbeat timestamps now come from the injected clock.

    • DynamoDB — GSI Query and Scan now read from a real per-index ordered structure instead of scanning the entire table on every call (Query on a GSI is now partition-scoped like base-table Query;
    more →

    costs are flat in table size). This also closes a projection-fidelity gap: KEYS_ONLY and INCLUDE GSIs previously returned base-table attributes that real AWS refuses to project — they now return exactly the projected attribute set. TransactWriteItems now maintains GSI index entries like every other write path. LSI queries and parallel (TotalSegments) scans intentionally keep the previous behavior.

    • Storage — SQLite’s WAL auto-checkpoint threshold raised from 1000 to 4000 pages on the persistent/hybrid write path:
    more →

    sustained write bursts previously paid a mid-burst checkpoint that showed up as ~0.9-1.4ms per SQS send at moderate queue depths; measured after the change at ~57-74µs per send (repeatable across independent runs, allocations unchanged, no regression at higher depths). The WAL file may now grow to ~16MB between checkpoints; the background maintenance loop’s periodic checkpoint behavior is unchanged.

  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.25

View on GitHub
v0.0.1-alpha.24 prerelease 11 assets
7 added · 13 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.24

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.24

Downloads

Changelog

7 added · 13 fixed

Added 7
    • Storage (behavior change) — OVERCAST_STATE now defaults to auto instead of a baked-in memory (Docker image) or hardcoded default (native binary).
    more →

    Unset or set to auto, Overcast resolves the mode at startup from evidence of persistence intent: a mounted volume or bind mount at the data directory, an explicitly configured OVERCAST_DATA_DIR, or an existing database file — any of these selects hybrid; none selects memory. Concretely: docker run -v myvolume:/data ghcr.io/overcast-sh/overcast now persists automatically (it previously ran silently in-memory — mounting a volume had no effect unless you also set OVERCAST_STATE), and fresh unconfigured runs — CI containers especially — get fast in-memory mode with zero configuration. Existing databases are always honored, so no previously-persisted data is ever silently stranded in memory mode. The startup log states the auto decision and which signal drove it, /_health reports both the configured and resolved modes, and the Metrics & Health page shows an actionable notice when auto lands on memory. Set OVERCAST_STATE explicitly to override in either direction — see docs/storage.md.

    • Events — the web UI Event Stream now replays recent history on connect (a rolling server-side buffer of the last 10,000 events) instead of starting blank, so events from before the page was opened — the ones someone debugging actually wants — are visible;
    more →

    request-telemetry events are evicted from the buffer first when it fills and stay hidden by default in the UI. Also fixed on the Events page: the “Pings” toggle previously had no visible effect (a second, hidden source filter dropped what the toggle revealed), and the source filter now shows events from newly-added sources by default instead of silently hiding any source it didn’t already know about.

    • Logging — a new trace level now sits below debug (OVERCAST_LOG_LEVEL=trace), and log levels were reclassified repo-wide so each tier is readable:
    more →

    health-check and /_debug/* polling request lines (Docker HEALTHCHECK and the web UI poll these continuously) moved from info to trace along with periodic flush/sweep/vacuum cycle logs, per-invoke Lambda pool internals moved to debug, storage degradation to memory-only is now an error, and SQLite migration failures now reach structured logs instead of only stderr. Also fixed: OVERCAST_LOG_LEVEL previously never actually gated verbosity (it only switched the encoder) — warn/error settings now work, and debug output is quiet enough to attach to bug reports.

    • Web UI / Debug endpoints — the metrics page is now Metrics & Health: a health strip (storage mode, health/degraded badge, live SQLite journal mode, last flush, uptime) and a recommendations section driven by a new advisories array in GET /_debug/metrics — the server now computes storage advisories (journal mode not WAL, store degraded to memory-only, store unhealthy, slow data-directory filesystem with a named-volume suggestion, sustained read pressure, memory-mode notice) and reports the live PRAGMA journal_mode readback per store, so silently-misapplied storage configuration is visible at a glance instead of discoverable only by symptom.
    • Docs — the published documentation (web UI docs browser, docs search, embedded builds) now contains only user-facing pages;
    more →

    contributor documentation (development setup, step debugging, wire-protocol architecture, storage internals, performance baselines, the compatibility-review harness) moved to docs/dev/, which is excluded from publishing the same way docs/plans/ already was — slightly shrinking the embedded docs in binaries and images. The web UI docs browser no longer renders a page’s internal frontmatter block or duplicates the page title, supports section deep links (heading anchors scroll correctly on navigation — storage advisories link directly to the relevant tuning section), and notes where contributor docs live; the performance guide gained copy-paste docker run/docker compose examples for the recommended named-volume setup including a host layer-cache mount; and the README’s badges were repaired, with a new disclaimer section covering AWS non-affiliation and the project’s as-is, best-effort status. Two new user-facing guides landed in the split: a performance-tuning page (storage-mode selection including OVERCAST_STATE=memory for CI, per-service overrides, named-volume-vs-bind-mount guidance with the new startup probe warning, the hybrid flush tuning knobs, and client-perceived-latency expectation-setting) and a storage-modes page (per-backend durability comparison and what survives a restart).

    • Debug endpoints — GET /_debug/metrics now reports storage diagnostics (recent flush history, seed duration, pending-log size, and opt-in per-namespace row counts via ?includeRowCounts=true) instead of a “not yet implemented” stub;
    more →

    GET /_debug/state/{namespace} is now paginated, returning {values, nextKey} pages (?after= exclusive cursor, ?limit= capped at 5000, default 500) instead of a single flat key→value map — a response-shape change for anything scripting against it (?key= single-value fetches are unchanged); the web Raw State Debugger now pages incrementally (fetching further pages only as the user scrolls near the end of what’s loaded) instead of eagerly merging every page, virtualizes both the flat key table and the key tree (which also gained per-node collapse/expand) so large namespaces render a bounded number of DOM rows, lazily fetches a deep-linked key’s value via the single-key endpoint when it hasn’t loaded yet, and restricts search to key-only matching over loaded rows.

    • Storage — SQLite-backed storage now applies versioned schema migrations automatically on startup instead of ad-hoc CREATE TABLE IF NOT EXISTS calls, writing a one-time backup file before the first pending migration runs against an existing database, and periodically checkpoints its WAL and reclaims free pages in the background instead of only growing the database file over time; new OVERCAST_HYBRID_SYNC/OVERCAST_HYBRID_SYNC_INTERVAL (pending-log fsync policy — the hybrid pending log is now fsynced on a 100ms interval by default, where it was previously never fsynced and an OS crash could lose the whole unflushed window), OVERCAST_HYBRID_DIRTY_ENTRY_THRESHOLD/OVERCAST_HYBRID_DIRTY_BYTE_THRESHOLD (size-triggered early flush, so write bursts flush ahead of the timer instead of accumulating unboundedly), and OVERCAST_HYBRID_MAINTENANCE_INTERVAL (WAL checkpoint/vacuum cadence) config options.
Fixed 13
    • Events — events published without a timestamp (31 call sites across 14 services) rendered as 00:00:00.000 in the Events page history;
    more →

    the event bus now stamps its clock on publish whenever a timestamp is missing. Separately, the wal storage backend no longer issues an fsync on shutdown (or on idle sync intervals) when nothing has been written since the last sync — on hosts with degraded fsync (Docker Desktop under pressure) that pointless fsync could stall shutdown for minutes.

    • Wire protocols — AWS Query-protocol requests (IAM, EC2, RDS, CloudFormation, STS, SNS, SES, Auto Scaling, ElastiCache, ELBv2, Route 53) now resolve their operation name during protocol identification like every other protocol, making the typed operation registries the primary dispatch path for Query traffic — previously that entire path was unreachable and a second, legacy copy of every operation served all requests.
    more →

    Divergences surfaced by the switchover were fixed: CloudFormation’s typed stack operations used the server’s default region instead of the request’s region, IAM SimulatePrincipalPolicy skipped its required PolicySourceArn validation, and SNS Subscribe skipped cross-region endpoint validation. EC2 (all operations), CloudFormation DeleteStack/ExecuteChangeSet/DeleteChangeSet, and SNS Publish/PublishBatch stay on the legacy path for now — each with a documented reason. New OVERCAST_PROTOCOL_STRICT option: by default a request claiming a wire protocol its service doesn’t declare is attempted anyway with a loud protocol drift warning (the reactive posture for AWS’s 2026 no-notice protocol-change policy); setting the flag restores strict rejection. CloudWatch (metrics) now resolves operations through the shared protocol middleware instead of bespoke header parsing.

    • Kinesis — sequence numbers were derived from the shard’s current record count, which regressed after any record deletion and could silently overwrite an existing record with a colliding sequence number;
    more →

    they now come from a persisted monotonic per-shard counter. The same fix removes a full-shard scan-and-decode that ran on every PutRecord (and once per record in a PutRecords batch), and GetRecords polling now reads only the requested range after the iterator instead of the whole shard — put cost and steady-state poll cost are now independent of shard depth. All of this applies to the default JSON wire protocol as well as CBOR: the JSON handlers (including SplitShard/MergeShards) duplicated the shared logic and now delegate to one implementation.

    • Pagination — invalid continuation tokens passed to CloudFormation DescribeStackEvents, CloudFront’s listing operations, and SSM DescribeParameters/GetParametersByPath/GetParameterHistory now return each operation’s documented AWS error (ValidationError/InvalidArgument/InvalidNextToken) instead of silently restarting from the first page — the silent restart caused duplicate delivery for any client paging with a stale or corrupted token;
    more →

    SSM’s MaxResults defaults and caps now match AWS’s documented per-operation values. DynamoDB Query/Scan had the same duplicate-delivery bug through a different mechanism: ExclusiveStartKey was resolved by searching for an item exactly equal to the cursor, so a cursor item deleted between pages silently restarted pagination from page 1 — the cursor now resolves by key-order position (matching real DynamoDB’s degradation), responses are capped per page (explicit Limit, or an implicit 1000-item cap approximating AWS’s documented 1 MB page bound), a plain Scan now reads only the requested page from storage instead of the entire table on every call, and ListTables now honors its documented Limit (default/cap 100) and ExclusiveStartTableName parameters and returns LastEvaluatedTableName instead of ignoring them and returning every table. S3 ListObjectsV2 had the same silent-restart bug (a garbled ContinuationToken now returns InvalidArgument like real S3), S3 ListParts/ListMultipartUploads previously ignored MaxParts/PartNumberMarker/MaxUploads/KeyMarker/UploadIdMarker entirely and omitted every truncation field (IsTruncated, NextPartNumberMarker, NextKeyMarker/NextUploadIdMarker) from their XML responses — both now paginate per AWS’s documented semantics (defaults/caps 1000, compound key+uploadId resume rule, malformed part-number-marker rejected with InvalidArgument) — and S3 object listings (ListObjects/ListObjectsV2) now stream the bucket in bounded internal pages instead of materializing and sorting every object on every list call, making per-call cost independent of bucket size beyond the requested page. CloudWatch Logs GetLogEvents — the worst offender found by the audit — parsed but ignored Limit/NextToken/StartFromHead and synthesized its tokens from the stream’s event count, so a client tailing with the returned forward token (the standard CloudWatch Logs polling pattern) re-received the entire event set on every call, forever; it now implements real positional f//b/ tokens, StartFromHead direction semantics, AWS’s 10,000 default/cap, the documented same-token-when-exhausted termination convention, and InvalidParameterException for garbled tokens — and FilterLogEvents, which previously ignored limit and returned no nextToken at all, now honors both. These fixes apply to the default JSON wire protocol, whose handlers previously ran a separate broken copy of the logic and now delegate to the shared implementation.

    • Storage — SQLite-backed persistence has been running without WAL mode since the project’s inception:
    more →

    the connection strings used another driver’s parameter spelling, which modernc.org/sqlite silently ignores, leaving every persistent deployment in rollback-journal mode — whose commits take a reader-blocking exclusive lock. Under a sustained ~2,000 writes/sec load this starved concurrent reads into multi-second waits and eventual timeout errors. WAL (and the intended synchronous=NORMAL) are now genuinely enabled, pinned by tests that read PRAGMA journal_mode back, plus an end-to-end regression test proving reads stay fast under paced write load. Three hardening changes land with it: flush transactions are now chunked (500 ops / 1 MiB per transaction, safe to replay after a crash) so no single flush holds the write lock for a burst-sized window; a storage read that still times out under extreme pressure now returns each service’s real AWS throttling error (SlowDown 503 for S3, ThrottlingException for JSON services, Throttling for Query services) — which SDKs retry automatically — instead of a generic 500 InternalError; and startup now probes the data directory’s fsync latency, logging a warning that recommends a named volume when it looks like a slow bind mount, with the probe result, plus new read-retry/read-timeout counters, exposed in GET /_debug/metrics.

    • Storage — a per-service OVERCAST_STATE_<SERVICE> override (e.g.
    more →

    OVERCAST_STATE_S3=memory) could silently switch an unrelated service’s persistence to memory-only instead of only the overridden service — confirmed for DynamoDB, whose items/streams lost persistence across restarts with no warning whenever any other service had an override configured; every code path that specially detects the storage backend (persistence-capability detection, startup-readiness waiting, persistent-health reporting, CloudFormation’s explicit flush) now resolves the correct underlying store first instead of silently losing that capability. A crash or unclean stop leaving a torn final line in the WAL-mode pending log previously refused to start the daemon at all; startup now tolerates and warns instead of aborting, matching the hybrid store’s existing behavior. A corrupt or unopenable SQLite database previously poisoned every subsequent read/write with the same error forever; the store now degrades to memory-only (reporting itself unhealthy via the health endpoint) instead of failing every request, and a single undecodable row encountered while seeding memory at startup is skipped with a warning instead of aborting the whole seed. Requests arriving during a startup schema migration previously returned an incorrect empty/not-found result in hybrid mode, or hung indefinitely in persistent mode; they now get an immediate, AWS-shaped ServiceUnavailable 503 (already retried automatically by AWS SDKs) until migration finishes. Shutdown now bounds the final store flush to the configured shutdown timeout instead of potentially hanging past it, and the server-error shutdown path runs the same cleanup as a normal signal-triggered shutdown instead of skipping it. CloudWatch metric-data retention enforcement, previously memory-mode only, now also runs periodically in persistent/hybrid modes. Separately: hybrid-mode writes that crossed the size-trigger flush threshold while the background seed was still running could miss their early-flush wakeup and sit unflushed until the next timer interval; S3, Kinesis, CloudWatch Logs, and SNS list operations now skip a malformed persisted record instead of failing the whole listing, and large listings use single-scan reads instead of per-key lookups; hybrid-mode reads of high-volume data (queue messages, log events, metric datapoints) no longer wait behind an in-flight flush transaction — they run on a dedicated read connection pool; bulk deletes (queue purge, log-group deletion, stack teardown) record one ranged tombstone and execute one ranged SQL delete instead of one log entry and one statement per key; API Gateway’s REST and HTTP API resource/stage/deployment/route/integration listings now also skip a malformed persisted record instead of failing the whole listing, and their per-API delete-all-by-prefix helpers use a single ranged delete when the store supports it instead of one delete per key (ECS’s task-definition-family listing was audited too and confirmed already free of the per-key-lookup pattern, so it needed no change); a store degraded to memory-only now caps its pending-log file at 64 MiB instead of growing it on every write for the rest of the run, and startup log replay streams the file instead of loading it whole. Per-service overrides for CloudFormation, API Gateway, and EventBridge (whose storage prefixes cfn/apigw/eb differ from their config names) and for SSM, KMS, Step Functions, AppSync, and CloudFront (whose storage namespaces contain no : separator) were previously accepted but silently never took effect; both routing gaps are fixed and the overrides now work — note that a previously-set override for one of these services activates for the first time against its own fresh per-service store, while data written during the inert period stays in the default store. Overrides that can never have an effect (dynamodbstreams — a facade over DynamoDB; sts — its state lives under IAM; bedrock/organizations — stateless stubs) now log a startup warning instead of failing silently.

    • CloudWatch — PutMetricData no longer rescans and re-decodes the metric’s already-retained datapoints on every write (a cost that grew with the number of points in the retention window, making sustained bursts to one metric progressively slower on the hybrid/persistent backends);
    more →

    retention is unchanged — reads still filter expired points and the periodic background sweep still deletes them; metric datapoint reads (GetMetricStatistics, GetMetricData, alarm evaluation) now fetch only the requested time window via a key-range read instead of scanning and decoding the metric’s whole retained history (allocations per read are now independent of how many points exist outside the window), and the retention sweep decides expiry from key timestamps without decoding values.

    • CloudWatch Logs — log event storage moved from one read-modify-write JSON blob per stream to a dedicated indexed table, with existing data converted by the one-time startup migration (after the automatic pre-migration database backup);
    more →

    this fixes append/read cost that previously grew with a stream’s history — measured at 1,000,000 pre-existing events, appends went from ~1.46s/903MB to ~1.2µs/216B per call, and a full-stream read went from ~1.88s/401MB to ~10ms/32MB. RetentionInDays is now enforced in persistent/hybrid modes via a periodic ranged delete instead of being stored but never acted on outside memory mode. The same periodic sweep now also deletes a log stream’s DescribeLogStreams metadata once its last event has aged out of the retention window and no events remain for it (persisted or buffered) — previously an expired stream’s metadata was never cleaned up and stayed listed forever with stale timestamps. Log events remain visible via the Raw State Debugger and resettable via /_debug/reset despite no longer living in the generic key-value store. GetLogEvents and FilterLogEvents now read only the requested time window and page via indexed range queries (FilterLogEvents issues one group-wide indexed query instead of one full-history read per stream) while still merging each stream’s unflushed write buffer, so just-ingested events stay visible — allocations per call are flat from 100 to 1,000,000 events per stream at a fixed window.

    • SQS — message storage moved from one JSON blob per message in the generic key-value store to a dedicated indexed table (existing messages converted by the one-time startup migration, after the automatic pre-migration backup);
    more →

    ReceiveMessage previously scanned and JSON-decoded the entire queue on every poll — a consumer polling a 10,000-deep queue paid ~85ms and 210,000 allocations per empty receive on the default backend — and now fetches only a bounded batch of visible candidates via a visible_at index, making standard-queue receive cost independent of queue depth (FIFO receive is bounded by visible messages rather than total, since FIFO ordering requires a sort; group-locking, per-call group dedup, and DLQ semantics are unchanged). GetQueueAttributes message counts use an indexed count instead of decoding every message. Sends pay a small flat cost for this (+9–19 allocations, a synchronous row insert replacing an async key-value write). Messages remain visible in the Raw State Debugger and resettable via /_debug/reset.

    • DynamoDB — Number-typed key attributes now order numerically, matching AWS’s documented contract (“If the data type of the sort key is Number, the results are returned in numeric order”):
    more →

    previously every ordering comparison used the key’s raw decimal text, so a Number sort key holding 5, 10, 50 returned Query results and pagination pages in the order 10, 5, 50. The fix covers Query/Scan result order, pagination cursors, and the stored key order on both backends — existing persisted items with Number-typed keys are re-encoded by the one-time startup migration (after the automatic pre-migration backup). As part of the same fix, two textual representations of the same numeric key ("5", "5.0", "5e0") now correctly address the same item on write instead of creating separate items, matching real AWS’s numeric key equality. Anything that depended on the old lexicographic order, or on distinct-text-same-value Number keys being separate items, changes behavior with this release. String-keyed tables are unaffected and pay no new cost.

    • CloudFormation — stack events moved from one read-modify-write JSON blob per stack (cost grew with a stack’s event history, and concurrent appends could silently drop events) to one row per event, with an existing stack’s history converted automatically the first time it’s read.
    • Web UI — the system map log-stream peek drawer now switches directly to a newly clicked lambda instance or log stream row while already open instead of swallowing the click and just closing.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.24

View on GitHub
v0.0.1-alpha.23 prerelease 11 assets
11 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.23

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.23

Downloads

Changelog

11 fixed

Fixed 11
    • Web UI — Raw State Debugger namespace views now truncate large stored value strings for responsive browsing and provide an Open action for the selected full raw value with JSON/text content-type detection.
    • Debug endpoints — POST /_debug/reset/{service} now succeeds for enabled services even when they have no stored resources yet instead of reporting them as unknown.
    • AppSync — CreateGraphqlApi, GetGraphqlApi, and ListGraphqlApis now return local executable GraphQL URLs for the connected Overcast endpoint, Lambda data sources strip ARN aliases/versions before invocation, direct Lambda resolver events include AWS/Powertools-compatible context fields while mapped Lambda requests pass their evaluated JSON payloads through, resolver identities now better match AWS shapes for Cognito/OIDC/IAM including IAM user-store lookup, configured Lambda authorizers now invoke Lambda with GraphQL request context and propagate/cache resolverContext/deniedFields, mapped Lambda request objects validate operation/invocation type with async Event null results, and direct plus VTL/APPSYNC_JS-mapped Lambda nested-list batching now supports maxBatchSize.
    • CloudFormation/Lambda — AWS::Lambda::Alias resources now create and delete real Lambda aliases instead of being treated as unsupported/stubbed resources.
    • DynamoDB/Lambda — DynamoDB stream event source mappings now process records through a bounded per-mapping worker, honor BatchSize and MaximumBatchingWindowInSeconds, apply filters per record before Lambda invocation, and expose filter decision evidence in the system map through a compact filter node with a searchable ordered receipt-history drawer.
    • DynamoDB — BatchWriteItem and TransactWriteItems writes now emit stream records, and composite-key PutItem condition checks such as attribute_not_exists(PK) AND attribute_not_exists(SK) reject existing items consistently.
    • S3 — bucket encryption APIs now support CDK asset bucket checks by returning default SSE-S3 encryption and round-tripping AES256/KMS encryption configuration.
    • State — hybrid SQLite-backed reads now retry canceled, busy, locked, and interrupted SQLite operations with bounded backoff, serialize SQLite flushes to avoid concurrent transaction contention, seed hot control-plane namespaces into memory while keeping bulk data-plane namespaces lazy, expose persistent backend health and pending-write counts, persist accepted writes to a pending log before async flush, and force CloudFormation terminal stack state through synchronous persistence.
    • Lambda — startup no longer pre-pulls every managed runtime image by default; set LAMBDA_SEED_RUNTIME_IMAGES=true to opt back into broad startup seeding while per-function prewarming and lazy first-use pulls remain enabled.
    • Web UI — bundled console builds now include the Tailwind typography plugin as a production build dependency, fixing Docker release builds that install only runtime dependencies.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.23

View on GitHub
v0.0.1-alpha.22 prerelease 11 assets
1 added · 3 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.22

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.22

Downloads

Changelog

1 added · 3 fixed

Added 1
    • Agent tooling — repo-local OpenCode skill registration now discovers the Overcast agent skills without manual setup.
Fixed 3
    • AppSync/CloudFormation — CreateApiKey now accepts CDK-style Expires values 365 days from creation without rolling back AWS::AppSync::ApiKey resources.
    • Web UI — the sidebar now defaults to collapsed on narrow viewports, keeps separate narrow/wide collapse preferences across refreshes, and shows immediate tooltips for collapsed icon navigation.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.22

View on GitHub
v0.0.1-alpha.21 prerelease 11 assets
2 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.21

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.21

Downloads

Changelog

2 fixed

Fixed 2
    • AppSync/CloudFormation — StartSchemaCreation now accepts AppSync built-in scalar types in GraphQL SDL, fixing CDK AWS::AppSync::GraphQLSchema rollbacks on types such as AWSDateTime, while rejecting unsupported custom scalars and custom object types using the reserved AWS prefix.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.21

View on GitHub
v0.0.1-alpha.20 prerelease 11 assets
3 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.20

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.20

Downloads

Changelog

3 fixed

Fixed 3
    • AppSync/CloudFormation — StartSchemaCreation now accepts AppSync built-in auth and subscription directives in GraphQL SDL, fixing CDK AWS::AppSync::GraphQLSchema rollbacks on directives such as @aws_api_key.
    • CloudWatch Logs — StartLiveTail now subscribes before emitting sessionStart, fixing a race where events written immediately after the session opened could be missed.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.20

View on GitHub
v0.0.1-alpha.19 prerelease 11 assets
4 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.19

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.19

Downloads

Changelog

4 fixed

Fixed 4
    • AppSync/CloudFormation — AWS::AppSync::GraphQLApi now accepts CDK/CloudFormation tag arrays, provisions environment variables, exposes GraphQLEndpointArn, supports S3-backed schema/resolver/function/channel namespace assets, provisions domain names, domain associations, API caches, source API associations, Events APIs, and channel namespaces, routes AppSync tag APIs without colliding with MSK, and validates CreateGraphqlApi/UpdateGraphqlApi consistently across REST and typed protocol paths.
    • SQS — ReceiveMessage replay with ReceiveRequestAttemptId is invalidated after deletes or visibility changes, CreateQueue rejects FIFO-only attributes on standard queues and returns QueueNameExists on idempotent conflicts, and SendMessage validates delay, FIFO delay, and body size constraints.
    • Web UI — debug sidebar navigation and raw-state links now stay hidden unless OVERCAST_DEBUG is enabled for the connected emulator.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.19

View on GitHub
v0.0.1-alpha.18 prerelease 11 assets
2 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.18

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.18

Downloads

Changelog

2 fixed

Fixed 2
    • CloudFormation — scalar properties forwarded to service APIs now preserve decimal numeric formatting instead of scientific notation across SQS queues, nested stack parameters, RDS flags, and ElastiCache replication group updates; AWS::SQS::Queue also forwards KMS/SSE properties so CDK-created FIFO queues with MessageRetentionPeriod: 1209600 and KmsMasterKeyId deploy successfully.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.18

View on GitHub
v0.0.1-alpha.17 prerelease 11 assets
2 added · 3 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.17

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.17

Downloads

Changelog

2 added · 3 fixed

Added 2
    • CloudFormation / AppSync — AppSync CDK-style stacks now provision real GraphQL APIs, schemas, API keys, data sources, resolvers, and pipeline functions through CloudFormation, including AWS-shaped Ref/Fn::GetAtt outputs and local GraphQL execution via /_appsync/{apiId}/graphql.
    • Web UI — added a read-only Raw State Debugger with shipped Go BFF and dev BFF support, service/namespace/resource deep links, search/filtering, copy actions, refresh controls, and contextual raw-state links from S3, SQS, SNS, and DynamoDB resource pages.
Fixed 3
    • SQS — CreateQueue and SetQueueAttributes now reject invalid queue attributes, including out-of-range MessageRetentionPeriod, DelaySeconds, MaximumMessageSize, KmsDataKeyReusePeriodSeconds, invalid boolean/enum values, malformed policy JSON, incompatible SSE settings, and unknown attribute names instead of persisting invalid queue state.
    • Web UI — S3 object previews now indent XML/RSS closing tags correctly, use file extensions for syntax highlighting when the stored content type is generic, and apply Prism colours for HTML/XML, CSS, and JavaScript tokens instead of only highlighting JSON-like tokens; DynamoDB table detail controls no longer require crypto.randomUUID on insecure origins.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.17

View on GitHub
v0.0.1-alpha.16 prerelease 11 assets
1 added · 6 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.16

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.16

Downloads

Changelog

1 added · 6 fixed

Added 1
    • Docs / Web UI — the bundled docs now include a browsable documentation route (with a sidebar entry alongside Map/Events/Metrics/Inbox) with server-backed search, generated metadata, full markdown typography for headers/tables/scrollable code blocks, no more raw YAML frontmatter leaking into rendered pages, and CDK local VPC guidance, while excluding developer-only planning notes and contributor-only content (e.g. “how to add a service”) from the shipped docs index.
Fixed 6
    • Startup metrics — startup_duration_ms now measures Go-side startup work from the earliest Overcast Go timestamp to readiness, while new pre_init_ms and an environment timeline phase separately report OS loader, antivirus, container init, entrypoint, and exec time.
    • CloudWatch Logs — StartLiveTail now streams AWS event-stream sessionStart/sessionUpdate frames with filter pattern and stream name/prefix support, and the web console uses the SDK-backed Live Tail path for log tailing.
    • CloudFormation — stack create/update/delete now wait briefly for fast provisioning before returning so SDK waiters can observe terminal status immediately, and executed change sets now advance to EXECUTE_COMPLETE/EXECUTE_FAILED instead of remaining EXECUTE_IN_PROGRESS.
    • Lambda — skipped Lambda layers now emit a warning in the function logs when layer content is unavailable locally and remote AWS fetching is not configured or fails.
    • Web UI — system map and global search resource links now support middle-click/new-tab navigation with full-name tooltips, CloudWatch log streams are sorted by latest write time, and S3 object previews better format self-closing XML and content types with parameters.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.16

View on GitHub
v0.0.1-alpha.15 prerelease 11 assets
5 fixed

Docker images

Full image

docker pull ghcr.io/overcast-sh/overcast:0.0.1-alpha.15

Slim image

docker pull ghcr.io/overcast-sh/overcast-slim:0.0.1-alpha.15

Downloads

Changelog

5 fixed

Fixed 5
    • SQS — FIFO ReceiveMessage retries with the same ReceiveRequestAttemptId now return the same in-flight messages and receipt handles instead of returning an empty response, invalid attempt IDs are rejected with InvalidParameterValue, CreateQueue/SetQueueAttributes reject invalid ReceiveMessageWaitTimeSeconds queue defaults instead of persisting malformed or out-of-range values, and redrive policies now move messages to a DLQ only after the receive count exceeds maxReceiveCount.
    • SQS / DynamoDB — CreateQueue and CreateTable now reject invalid resource names with AWS-modeled validation errors instead of creating resources with unsupported names.
    • Web UI — CloudWatch Logs viewers now keep formatted log rows stable while scrolling, support separate compact syntax highlighting and pretty-printing, add group-wide interleaved log views with plaintext/table toggles and live tailing, and avoid duplicated system map peek log lines; S3 object inspection now previews common images and text-like objects with JSON/XML/HTML formatting and syntax highlighting.
    • CloudFormation / EventBridge / EC2 — CDK scheduled Fargate task stacks now preserve EventBridge ECS target metadata on AWS::Events::Rule create/update, EventBridge can deliver matching events to SQS targets and scheduled ECS/Fargate targets to RunTask, and CloudFormation-created VPCs expose subnet tags plus NAT gateway route metadata so CDK can classify private subnet groups during VPC lookups.
  • Release: https://github.com/overcast-sh/overcast/releases/tag/v0.0.1-alpha.15

View on GitHub