Skip to content

v0.0.1-alpha.29

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 04 Aug 10:53
593e427

Overcast 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.29

Native 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 — 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

  • BREAKING [cloudwatch] 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

  • [apigateway] 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

  • [autoscaling] 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

  • [cloudwatch] 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

  • [ecs/secretsmanager/ssm] 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/logs] 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] 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/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=true in 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 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

  • [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 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

  • [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 an AccessDenied can 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.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

  • [s3] 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

  • [secretsmanager] 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

  • [web/sns] the topic detail view shows each subscription's live delivery state, and lambda is 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] 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

  • [docker] Exec runs 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, 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

  • BREAKING [autoscaling] 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

  • BREAKING [eventbridge] 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

  • 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: 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

  • BREAKING [s3] 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

  • BREAKING [secretsmanager] 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

  • [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-alpine or golang:1.24-alpine can 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] 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

  • [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] 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

  • [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 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

  • 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: 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

  • [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] 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

  • BREAKING [dynamodb] 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

  • [appconfig] 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

  • [appregistry] ListAssociatedAttributeGroups is now a declared capability rather than a footnote on AssociateAttributeGroup

  • [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. 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

  • [capgen] --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

  • [ci] 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

  • [cloudformation/ecs] 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/secretsmanager] 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] 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/rds] changing a DB instance's MasterUsername or DBName replaces 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 ErrorResponse envelope, so SDKs read the error code instead of a generic failure

  • [docker] 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

  • [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=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

  • [ecs] 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

  • [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.FileSystem produces

  • [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 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

  • [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: 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

  • [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 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

  • [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.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/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 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/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::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] 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

  • BREAKING [rds] 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] 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

  • [release] 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

  • [s3] 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/router] 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] 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.

  • [sns] 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)

  • [secretsmanager] 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

  • [sqs] 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

  • [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;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/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 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

  • [docs] 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

Release: https://github.com/Neaox/overcast/releases/tag/v0.0.1-alpha.29