v0.0.1-alpha.29
Pre-releaseOvercast v0.0.1-alpha.29
Docker Images
Full image with the web management console:
- Pull:
ghcr.io/neaox/overcast:0.0.1-alpha.29 - Channel tag:
ghcr.io/neaox/overcast:alpha - Registry: overcast package
Headless slim image for CI pipelines:
- Pull:
ghcr.io/neaox/overcast-slim:0.0.1-alpha.29 - Channel tag:
ghcr.io/neaox/overcast-slim:alpha - Registry: overcast-slim package
docker run --rm -p 4566:4566 -p 4567:4567 ghcr.io/neaox/overcast:0.0.1-alpha.29
docker run --rm -p 4566:4566 ghcr.io/neaox/overcast-slim:0.0.1-alpha.29Native Binaries
Download a binary for your platform from the assets below and verify it with SHA256SUMS.
| Asset | SHA256 |
|---|---|
overcast-darwin-amd64 |
e30067d382ad85882b047ad3f3f5ffdba9aa62fe153ccf5b511ffde58bb9f5bb |
overcast-darwin-arm64 |
e63537f321caf3c40332153f67ea03045c0ef5c2f91811922818bf46a594117b |
overcast-linux-amd64 |
dc1bc0fbc8c310afe96cec43306bc5653b5fdcc59da3e4bca8bed4c444f9ee9e |
overcast-linux-arm64 |
092e0f96a26240e88f99fd721fe4173be9f229b0830f91701b727dda86eeb89b |
overcast-windows-amd64.exe |
0c7aeda9f89551bd080e79468025e5d11ada06b60b2a96aa1d195797f12292c0 |
overcastd-darwin-amd64 |
17e5941eb1b097117588c682e9315df49eb72dbd15a55b4b75fdcab7abf497ef |
overcastd-darwin-arm64 |
1e606a262852328af745d561662138a1003837910b82a63e5a60db1e0efe3937 |
overcastd-linux-amd64 |
e2c4700f644e7ee4e27871445d7bec67e0eb5bd31c688230031d4f7ccd252dda |
overcastd-linux-arm64 |
c9d3403c5fdbf1fe6b9d2057903f68156b1b05868188074782a6d42c4999d664 |
overcastd-windows-amd64.exe |
5648600b256c6b265a3d50e16c735eb401add63d4ba5cae3de63c135a7a7d86d |
Release Notes
Added
-
BREAKING [stepfunctions] a real Amazon States Language interpreter —
StartExecutionnow runs the state machine instead of reportingSUCCEEDEDimmediately. All eight state types,Retry/Catch, the full Choice operator set and the input/output pipeline are interpreted;Taskstates invoke Lambda, SQS, SNS, DynamoDB and nested state machines through Overcast's own router.DescribeExecution,GetExecutionHistory,ListExecutions,StopExecution,DescribeStateMachineForExecutionandStartSyncExecutionreport 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, distributedMap, JSONata — fails the execution loudly withStates.Runtimerather than passing through silently.CreateStateMachinenow rejects definitions that are not valid ASL withInvalidDefinition, as AWS does.StartExecutionreturns while the execution isRUNNINGandStopExecutionreally interrupts it, both matching AWS;StartSyncExecutionis the synchronous path
migration: an execution that used to reportSUCCEEDEDunconditionally can now legitimately endFAILEDorTIMED_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(default15m) is a runaway guard on each execution; it is not on the request path, so ordinaryWaitstates are unaffected -
BREAKING [cloudwatch]
PutMetricAlarmrefuses alarm shapes it cannot evaluate — metric math, anomaly detection, extended statistics — with a501instead 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 -
[apigateway] usage-plan throttle and quota limits are measured on every request that presents an API key —
GetUsagereturns AWS's daily[used, remaining]log per key, and reaching a limit publishes anapigateway:Throttledevent.OVERCAST_ENFORCE_APIGATEWAY_THROTTLE(default off) turns that measurement into rejection, answering an over-limit request with API Gateway's429:TooManyRequestsExceptionfor the rate limit,LimitExceededExceptionfor 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 -
[autoscaling] Auto Scaling groups converge for real — a single background reconciler launches and terminates EC2 instances until the owned instance set matches
DesiredCapacity, runs thePending/InService/Terminatinglifecycle, replaces unhealthy instances, and records aDescribeScalingActivitiesentry for every launch and termination.PutScalingPolicyexecutesSimpleScalingandStepScalingpolicies, throughExecutePolicyor from a CloudWatch alarm naming the policy ARN in its actions, honouring cooldowns andMinAdjustmentMagnitude;PutLifecycleHookreally pauses a launch or termination inPending:Wait/Terminating:Wait, publishes the EventBridge lifecycle-action event, and waits forCompleteLifecycleActionor the heartbeat timeout. New:DescribeScalingActivities,ExecutePolicy,CompleteLifecycleAction,RecordLifecycleActionHeartbeat,SetInstanceHealthandSetInstanceProtection -
[cloudwatch] alarm state transitions publish the
CloudWatch Alarm State Changeevent to EventBridge and notify SNS alarm actions; newDescribeAlarmHistory,EnableAlarmActionsandDisableAlarmActions, plusStateReasonData,Dimensions,DatapointsToAlarmand the action lists onDescribeAlarms. The console's CloudWatch page gains a live alarms view showing state, reason, what is being evaluated, and recent transitions -
[ecs/secretsmanager/ssm] a container definition's
secretsare resolved and injected as environment variables at task start, from Secrets Manager (including the:json-key:suffix thatecs.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/logs] task containers using the
awslogslog driver have their output shipped to CloudWatch Logs, into the group fromawslogs-groupand 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 beforedocker logscould reach it -
[ecs] deployments report
rolloutState,rolloutStateReasonandfailedTasks, 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 showsstopCodeand 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/elbv2] 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 -
[efs] mount targets can serve a real NFSv4 export —
OVERCAST_EFS_NFS=truein live mode starts one unprivileged NFS-Ganesha container per mount target, with access points as pseudo-paths -
[elbv2/ecs] a load balancer forwards to its targets, so
ApplicationLoadBalancedFargateServicehands out a URL that serves the application. A listener now stores theDefaultActionscarrying 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 -
[eventbridge] 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
PutEventswith a hop budget so bus-to-bus forwarding cannot recurse.InputPathandInputTransformerare applied to a target's payload before delivery, and a failed delivery honours the target'sRetryPolicyand 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 -
[iam]
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 anAccessDeniedcan be told apart from an application bug -
[pipes/web] a pipe detail view showing the resolved source, enrichment and target types, recent executions, and whether a pipe is actually wired or only stored
-
[release] breaking changes wait while a minor or patch release PR is open. That PR's section of
CHANGELOG.mdis written and reviewed while it sits there, and every push tomainis 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.xpromises no compatibility, so nothing fires below 1.0 -
[s3] bucket lifecycle rules:
PutBucketLifecycleConfiguration,GetBucketLifecycleConfigurationandDeleteBucketLifecycleare implemented, and an hourly clock-driven sweeper applies them —Expirationdeletes objects,Transitionmarks a synthetic storage class, andAbortIncompleteMultipartUploadabandons 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 atPuttime rather than stored and ignored -
[secretsmanager]
RotateSecretnow 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 aRotationRulesschedule. The version machinery a rotation function drives comes with it:UpdateSecretVersionStage, staging labels andClientRequestTokenonPutSecretValue, andGetSecretValuebyVersionStage. 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,DeleteResourcePolicyandValidateResourcePolicystore, return and validate a secret's policy instead of returning 501. Nothing evaluates it — a stored policy grants and denies nothing (issue #496) -
[secretsmanager]
BatchGetSecretValueacceptsFilters(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 bothFiltersandSecretIdListis now the documentedInvalidParameterException -
[web/sns] the topic detail view shows each subscription's live delivery state, and
lambdais selectable when subscribing -
[web] 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
-
[cloudformation]
{{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 -
[rds]
DescribeEventsrecords a DB instance's create, start, stop, delete and failure transitions asdb-instanceevents, kept for 14 days — this is where the reason an instance failed to start now surfaces, since the realDBInstanceshape has nowhere to put it -
[docker]
Execruns a command inside a running container and returns its exit status and output
Changed
-
BREAKING [cloudwatch] alarms now evaluate their own metrics: epoch-aligned periods,
DatapointsToAlarmM-of-N,Dimensions, and all fourTreatMissingDatamodes
migration: an alarm that previously only moved when you calledSetAlarmStatenow changes state on its own and fires itsAlarmActions/OKActions/InsufficientDataActions. CallDisableAlarmActionson alarms whose actions you do not want fired locally -
BREAKING [autoscaling] group shapes the reconciler cannot converge are now refused instead of stored and ignored:
LaunchTemplate,MixedInstancesPolicyandInstanceIdgroups, andTargetTrackingScaling/PredictiveScalingpolicies, return501, and a group with no launch source at all returns AWS'sValidationError. 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 withSimpleScalingorStepScaling. 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 -
BREAKING [eventbridge]
PutTargetsnow rejects a target ARN it cannot deliver to instead of accepting it and silently dropping the event: a malformed ARN fails the call withValidationException, and a well-formed ARN naming an unsupported service comes back inFailedEntrieswithErrorCode: 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 -
BREAKING [pipes] 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:CreatePipeandUpdatePipenow requireRoleArn(as AWS does) and reject any source, enrichment, target orFilterCriteriaOvercast cannot run, with aValidationExceptionnaming 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.UpdatePipeis now routed onPUT /v1/pipes/{name}as AWS routes it, notPATCH— every SDK and the CLI already sentPUTand were answered405.CreatePipealso answers AWS's200with the six-member response body rather than201with the whole pipe -
BREAKING [s3]
PutObjectnow honoursx-amz-storage-classinstead of ignoring it: listings andHeadObjectreport the class an object was stored with, and an unrecognised class is rejected withInvalidStorageClass
migration: send a documented S3 storage class, or omit the header — an unrecognisedx-amz-storage-classused to be discarded silently and now fails the request -
BREAKING [secretsmanager]
RotateSecretrejects a secret with no rotation function configured, as AWS does, rather than saving the schedule and rotating nothing
migration: passRotationLambdaARNon the call or configure it first, and addRotateImmediately: falseif you only want the schedule -
[docker] the published images pin their base images by digest, so a release is reproducible and an upstream retag of
alpine:3.20,node:22-alpineorgolang:1.24-alpinecan no longer change what ships without appearing in the diff. Dependabot keeps the digests current -
[ecs/efs] 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
-
[iam]
SimulatePrincipalPolicyanswers with a real evaluation of the principal's identity policies —allowed,explicitDenyorimplicitDeny, withMatchedStatementsnaming the statement that decided it andMissingContextValuesnaming condition keys the call did not supply — where it used to answerallowedto everything. An optionalResourcePolicyandPermissionsBoundaryPolicyInputListare evaluated too, and a policy construct the evaluator does not implement comes back as AWS'sPolicyEvaluationerror instead of being resolved to an allow or a deny -
[release] 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 -
[secretsmanager] secret ARNs carry AWS's six-character random suffix; the partial ARN without it still resolves, as on AWS
-
[secretsmanager]
GetRandomPasswordhonoursRequireEachIncludedType, 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 -
[web] 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 byweb/src/docs-nav.gen.ts(112 KB) holding only what the sidebar and page outline render. Docs search already went through the API -
BREAKING [efs/ecs/lambda] 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: setOVERCAST_EFS_MODE=mockto keep EFS metadata-only. One task-level behaviour changes with the default: a container whoserootDirectory— or whose access-point root directory declared withoutCreationInfo— 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 -
[docs/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
Fixed
-
BREAKING [ecs]
networkConfigurationis required when the task definition'snetworkModeisawsvpc, as on AWS, rather than whenlaunchTypeisFARGATE. An awsvpc task definition launched under EC2, or under acapacityProviderStrategywith no launch type — the shape CDK emits — was accepted and produced a service that could never place a task
migration: passnetworkConfiguration(subnets, plus security groups if any) to RunTask and CreateService for awsvpc task definitions; the same call already fails against real AWS without it -
BREAKING [dynamodb]
ConsistentRead=trueon a Query or Scan against a global secondary index is rejected with theValidationExceptionAWS returns, instead of silently serving a read AWS has no way to serve
migration: dropConsistentRead(or set it tofalse) on GSI queries — the same call already fails against real AWS -
[appconfig] the four hosted configuration version operations —
CreateHostedConfigurationVersion,GetHostedConfigurationVersion,ListHostedConfigurationVersions,DeleteHostedConfigurationVersion— are now declared capabilities, sodocs/services/appconfig.mdand the capability snapshot report what the service has been serving all along -
[appregistry]
ListAssociatedAttributeGroupsis now a declared capability rather than a footnote onAssociateAttributeGroup -
[autoscaling] void responses carried a Go type name as their XML root element instead of AWS's
<OperationNameResponse> -
[awsapi] two service keys resolved to the wrong AWS model, so their capability rows were validated against the wrong API.
wafmatched the manifest's WAF Classic identity (AWSWAF_20150824) although Overcast implements WAF v2 (AWSWAF_20190729) — the two share operation names likeCreateWebACL, so v2-only operations such asAssociateWebACLcounted as unmodelled. Thesesv2identity (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 theseskey -
[capgen]
--checknow sees operations registered only in a service's typed registry (map[string]op.Operation, built bytypedOps()) — detection previously recognisedmap[string]http.HandlerFuncand 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-modelnow fails on a service key that no modelled AWS identity backs, and on acompat/suites/registry.jsongroup whoseserviceis 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 -
[ci]
softprops/action-gh-releaseis 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
-
[cloudformation/ecs] a CDK-deployed Fargate service starts its tasks.
AWS::ECS::Serviceapplies CloudFormation's documented default ofDesiredCount: 1for 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::ClusterandAWS::ECS::Servicealso get a generated physical name when the template gives none, as CloudFormation does: CDK never emitsServiceNameand 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/secretsmanager]
AWS::SecretsManager::Secretgenerates a value forGenerateSecretStringinstead of dropping the property. The secret was created with anAWSCURRENTversion holding nothing, so it listed and described normally while everyGetSecretValueansweredResourceNotFoundExceptionnaming that version — a value-less staged version is indistinguishable from an in-flight rotation.SecretStringTemplateandGenerateStringKeyplace the password inside the template's JSON object, which is the shapenew Secret(..., { generateSecretString })synthesises, and the generation settings are honoured by dispatching to the service's ownGetRandomPassword. As on AWS the value is generated once, at create; specifying bothSecretStringandGenerateSecretStringnow fails the resource rather than silently preferring one -
[cloudformation] a resource's resolved properties are persisted, so an update can tell which of them changed — every replacement guard, patch diff and custom-resource
OldResourcePropertiescompared against nothing before -
[cloudformation/rds] changing a DB instance's
MasterUsernameorDBNamereplaces the instance, as AWS documents, instead of reporting success and leaving it alone -
[cloudformation] 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
-
[cloudwatch] Query-protocol errors use AWS's
ErrorResponseenvelope, so SDKs read the error code instead of a generic failure -
[docker] image handling no longer defeats itself. Pulling an image ran
docker image pruneafterwards, 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 withNo 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 thetagparameter rather than folded intofromImage, 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]
IsNotFoundrecognises the 404s reported by every client helper, not only the JSON ones -
[dynamodb] 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=25page 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 withgo test -bench BenchmarkDynamoDB_ParallelScan -benchmem -benchtime 200x ./internal/services/dynamodb/, three runs each side agreeing within 10%, Go 1.24 in agolang:1.24-bookwormcontainer (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 -
[ecs] services place real containers. A service's scheduler goes through the same path as
RunTask, so its tasks carry the service'snetworkConfiguration, the ENI attachment, the Fargate platform version and the deployment ID instartedBy— 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 withstopCodeTaskFailedToStartand 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,useranddockerLabelson 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,pidModeandipcModeon a task definition;secrets,healthCheck,dependsOn,readonlyRootFilesystem,privilegedand the start/stop timeouts on a container;healthCheckGracePeriodSeconds,enableExecuteCommand,propagateTags,serviceRegistries,placementStrategyandplacementConstraintson a service;startedByon 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]
UpdateServiceandDeleteServicewrite 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 -
[efs/ec2] 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.FileSystemproduces -
[elasticache] 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
-
[elbv2] a load balancer's
DNSNameis built on Overcast's external hostname, as every other service handing out an endpoint already does, instead of a hardcoded.elb.localhostthatOVERCAST_HOSTNAMEcould not reach. Unchanged by default, since the hostname defaults tolocalhost -
[eventbridge/pipes] 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
-
[iam] 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 -
[lambda] 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: Tailwaits for the handler's output to reach the tail buffer rather than for Docker to hand over bytes, soX-Amz-Log-Resultcarries the function's own log lines instead of justSTART/END/REPORT— and the missing line no longer surfaces against the next invocation, whose request ID it was never written under -
[lambda] a
LogType: Tailinvoke 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, soX-Amz-Log-Resultcomes 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 -
[msk] 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
-
[pipes] a DynamoDB-sourced pipe is no longer cancelled part-way through delivery when the write that triggered it answers its client
-
[protocol] three Query-protocol decoding faults. Lists whose members carry a model
locationNamenow decode — RDSCreateDBSubnetGrouprejected every real AWS SDK with "At least one SubnetId is required" because the SDKs serialiseSubnetIds.SubnetIdentifier.Nand the codec only recognisedmember.N, flattened, and map forms. Map entries no longer lose their values,entry.N.keyandentry.N.valuearriving 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 -
[rds/ecs] 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] a DB instance endpoint is now a hostname a Lambda function or ECS task can actually resolve and connect to.
Endpoint.Addressis minted on the endpoint the request arrived on —mydb.ap-southeast-2.rds.localhost.overcast.shwhenOVERCAST_HOSTNAMEis set — the rule every other service already follows, and the engine container answers to that name onovercast_lambda,overcast_ecsand 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 or127.0.0.1— an address dialable by exactly one party, which is what aWORDPRESS_DATABASE_HOSTbaked into an ECS task definition byFn::GetAttcould 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
EngineVersionstarts a container. The image map was matched exactly, so the precise versions real stacks send (8.0.39from CDK'sMysqlEngineVersion.VER_8_0_39,16.3) started nothing at all — the instance wentavailablewith 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
StartDBInstancecan bring the same instance back -
[rds/docker] a Docker error on the container logs endpoint is reported as an error, instead of being de-framed into corrupted-looking log output
-
[rds] a DB instance whose container cannot be started reports
failedwith the reason, instead ofavailablewith 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]
ModifyDBInstanceappliesMultiAZ=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]
ModifyDBInstancereadsMasterUserPassword, 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 ownALTER USERinside the container: the old password stops working and the new one starts, as on AWS. A container readsMYSQL_ROOT_PASSWORDand 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
availableis refused withInvalidDBInstanceStateinstead 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/elasticache/msk] 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/cloudformation] a stack update that changes an
AWS::RDS::DBClusterproperty applies it instead of reportingUPDATE_COMPLETEhaving changed nothing. The handler sentMasterUserPassword,BackupRetentionPeriod,Port,PreferredBackupWindow,PreferredMaintenanceWindow,DBClusterParameterGroupName,VpcSecurityGroupIds,EnableCloudwatchLogsExportsandDeletionProtectiontoModifyDBCluster, whose request type had two fields, so every one of them was dropped between the wire and the handler.Engine,MasterUsername,DatabaseNameandDBSubnetGroupNamenow force replacement, as AWS documents them, rather than being quietly applied in place.CreateDBClusteraccepts the same settings, so what can be updated can be created too -
[rds]
ModifyDBClusterrotates 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]
DeleteDBClusterrefuses a cluster withDeletionProtectionenabled, with AWS'sInvalidParameterCombination, and a CloudFormation stack delete now fails rather than removing a protected cluster and reportingDELETE_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]
ModifyDBClusteranswers with aModifyDBClusterResponseelement on both dispatch paths. The typed implementation returned aCreateDBClusterResponse, which went unnoticed only because a second raw implementation of the same operation declared the right envelope locally;CreateDBCluster,DescribeDBClusters,DeleteDBClusterandModifyDBClusternow have one implementation each, as the instance operations already did -
[rds]
CreateDBInstancereportsavailableonly 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 stayedavailablefor good -
[rds] a create whose database container cannot be built reports
failedwith 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]
CreateDBInstanceandDeleteDBInstancehave one implementation again, joining the operations collapsed in the previous change -
[rds]
OVERCAST_RDS_MODE=mockkeeps RDS metadata-only on a machine that has Docker, for testing a control plane without paying for a real engine boot;liveremains the default -
BREAKING [rds]
MasterUserPasswordis held to RDS's constraints onCreateDBInstance,CreateDBCluster,ModifyDBInstanceandModifyDBCluster: 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'sCredentials.fromGeneratedSecretexcludes them by default
migration: give the database a password RDS accepts, or setExcludeCharacterson 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] the master password statement authenticates through the
MYSQL_PWDenvironment variable rather than a-pargument, matching whatpsqlalready did withPGPASSWORD. Command arguments are visible todocker inspectand 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 -
[release] release notes are written without a second approval.
finalize-releasesat in thereleaseenvironment 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.27andv0.0.1-alpha.28both 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 entrycheck 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, untaggedVERSION— 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:VERSIONandCHANGELOG.mdboth 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 editingCHANGELOG.mdis the answer rather than the thing to avoid -
[release] a compatibility marker written after the area rather than after the kind —
~ [autoscaling]! …— is rejected bychangelog.py checkinstead 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 amigration: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-prepfolds on every push tomainwhile a release PR is open, so a release window's worth of pushes stacked them up -
[s3]
CopyObjectaccepts a fully URL-encodedx-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-1returns 200 instead ofBucketAlreadyOwnedByYou, 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/router]
PutObjectno longer discards the body when the request carriesContent-Type: application/x-www-form-urlencoded— the content type many minimal HTTP clients send when none is set,curl --data-binaryamong them. Protocol detection read AWS Query fields out of the body before routing had decided who owned the request, which drained it: the write answered200 OKwith 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 forPOST, 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] An object key containing
+is now stored decoded rather than as the literal%2Ba client sends on the wire. Only characters Go leaves bare when it re-escapes a path were affected —+,=,&— which is why%20and multi-byte UTF-8 looked fine. -
[sns] a
lambda-protocol subscription is now actually delivered to —Publishinvokes the function with AWS'sRecords[].Snsevent 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'sRedrivePolicydead-letter queue, for every protocol, rather than being swallowed -
[sns] notification
Timestampuses AWS's millisecond form (2012-04-25T21:49:25.719Z) -
[secretsmanager] a rotation function copied from AWS's published blueprints now works unmodified. Three behaviours its
createSecretstep depends on were missing:RotateSecretstages theClientRequestTokenas an emptyAWSPENDINGversion 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 …";GetSecretValuereports a version holding no value asResourceNotFoundExceptionrather than an empty200; andPutSecretValueunder that version's token fills it in rather than answeringResourceExistsException.DescribeSecretstill lists the version throughout -
[sqs] A Query-protocol request addressed by the queue's own URL —
POST /<account>/<queue>withAction=SendMessageand noQueueUrlparameter, the shape AWS's API Reference documents — no longer fails withMissingParameter. The queue now comes from the request path when the body omits it. AQueueUrlin the body still wins, and a request toPOST /without one is still the client error it always was. -
[sqs]
ListQueueTagsrenders 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 toAttribute, notTag. -
[sqs]
PurgeQueueno longer stops the queue accepting deliveries for the next 60 seconds — that window only governs when a secondPurgeQueueis 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 whileSendMessagestill answered 200 with aMessageId, 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
PurgeQueuewithPurgeQueueInProgressor silently swallow a re-sent deduplication ID -
[web] 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;6mlitter 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/rds] 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] docs search in
pnpm run devanswers 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 -
[docs]
docs/networking.mdgains 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-indexinstead 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
Release: https://github.com/Neaox/overcast/releases/tag/v0.0.1-alpha.29