v0.0.1-alpha.24
Pre-release
Pre-release
Overcast v0.0.1-alpha.24
Docker Images
Full image with the web management console:
- Pull:
ghcr.io/neaox/overcast:0.0.1-alpha.24 - 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.24 - 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.24
docker run --rm -p 4566:4566 ghcr.io/neaox/overcast-slim:0.0.1-alpha.24Native Binaries
Download a binary for your platform from the assets below and verify it with SHA256SUMS.
| Asset | SHA256 |
|---|---|
overcast-darwin-amd64 |
71024f3cf22af9a426ca3189750b19df60a24bf1994d5f5815616dac6bc4548e |
overcast-darwin-arm64 |
1906e02852340e9ceea26e28282060571020a2ac17317ce0a59ab59ec797e28c |
overcast-linux-amd64 |
9d13b08ba2a71d13682e324cbff13e4b6f5504e8ca7ab04e4f37c07e13083827 |
overcast-linux-arm64 |
03bab96596a124b395aa8aa08cbfc7a49c3834c46d4d36f4ecc11e93460db62b |
overcast-windows-amd64.exe |
17ca428de4649d0e442d9621b38ad4164f21860af9a33252a9aac92ad900f62c |
overcastd-darwin-amd64 |
92c3446268718b8a1509add04c14e247a13263cc04ce7cdd692d1e306cf35d8b |
overcastd-darwin-arm64 |
81bf2b23341f240b33257f9095d92032fefc800d4b16612d1465f62491223912 |
overcastd-linux-amd64 |
bc92e022b8126cb4468680093e617d0effa9387a1d7811ea7313bcad991708e9 |
overcastd-linux-arm64 |
8f645a0bec4b437eac446afa07bc27e270e7c0121ae147b86e5bfd2ca5ca93f0 |
overcastd-windows-amd64.exe |
c7e882f72f54eee4ab5f0d9112701a2b8584ab498720ebd77454de44badfc9ee |
Release Notes
Added
- Storage (behavior change) —
OVERCAST_STATEnow defaults toautoinstead of a baked-inmemory(Docker image) or hardcoded default (native binary). Unset or set toauto, Overcast resolves the mode at startup from evidence of persistence intent: a mounted volume or bind mount at the data directory, an explicitly configuredOVERCAST_DATA_DIR, or an existing database file — any of these selectshybrid; none selectsmemory. Concretely:docker run -v myvolume:/data ghcr.io/neaox/overcastnow persists automatically (it previously ran silently in-memory — mounting a volume had no effect unless you also setOVERCAST_STATE), and fresh unconfigured runs — CI containers especially — get fast in-memory mode with zero configuration. Existing databases are always honored, so no previously-persisted data is ever silently stranded in memory mode. The startup log states the auto decision and which signal drove it,/_healthreports both the configured and resolved modes, and the Metrics & Health page shows an actionable notice when auto lands on memory. SetOVERCAST_STATEexplicitly to override in either direction — see docs/storage.md. - Events — the web UI Event Stream now replays recent history on connect (a rolling server-side buffer of the last 10,000 events) instead of starting blank, so events from before the page was opened — the ones someone debugging actually wants — are visible; request-telemetry events are evicted from the buffer first when it fills and stay hidden by default in the UI. Also fixed on the Events page: the "Pings" toggle previously had no visible effect (a second, hidden source filter dropped what the toggle revealed), and the source filter now shows events from newly-added sources by default instead of silently hiding any source it didn't already know about.
- Logging — a new
tracelevel now sits belowdebug(OVERCAST_LOG_LEVEL=trace), and log levels were reclassified repo-wide so each tier is readable: health-check and/_debug/*polling request lines (Docker HEALTHCHECK and the web UI poll these continuously) moved from info to trace along with periodic flush/sweep/vacuum cycle logs, per-invoke Lambda pool internals moved to debug, storage degradation to memory-only is now an error, and SQLite migration failures now reach structured logs instead of only stderr. Also fixed:OVERCAST_LOG_LEVELpreviously never actually gated verbosity (it only switched the encoder) —warn/errorsettings now work, anddebugoutput is quiet enough to attach to bug reports. - Web UI / Debug endpoints — the metrics page is now Metrics & Health: a health strip (storage mode, health/degraded badge, live SQLite journal mode, last flush, uptime) and a recommendations section driven by a new
advisoriesarray inGET /_debug/metrics— the server now computes storage advisories (journal mode not WAL, store degraded to memory-only, store unhealthy, slow data-directory filesystem with a named-volume suggestion, sustained read pressure, memory-mode notice) and reports the livePRAGMA journal_modereadback per store, so silently-misapplied storage configuration is visible at a glance instead of discoverable only by symptom. - Docs — the published documentation (web UI docs browser, docs search, embedded builds) now contains only user-facing pages; contributor documentation (development setup, step debugging, wire-protocol architecture, storage internals, performance baselines, the compatibility-review harness) moved to
docs/dev/, which is excluded from publishing the same waydocs/plans/already was — slightly shrinking the embedded docs in binaries and images. The web UI docs browser no longer renders a page's internal frontmatter block or duplicates the page title, supports section deep links (heading anchors scroll correctly on navigation — storage advisories link directly to the relevant tuning section), and notes where contributor docs live; the performance guide gained copy-pastedocker run/docker composeexamples for the recommended named-volume setup including a host layer-cache mount; and the README's badges were repaired, with a new disclaimer section covering AWS non-affiliation and the project's as-is, best-effort status. Two new user-facing guides landed in the split: a performance-tuning page (storage-mode selection includingOVERCAST_STATE=memoryfor CI, per-service overrides, named-volume-vs-bind-mount guidance with the new startup probe warning, the hybrid flush tuning knobs, and client-perceived-latency expectation-setting) and a storage-modes page (per-backend durability comparison and what survives a restart). - Debug endpoints —
GET /_debug/metricsnow reports storage diagnostics (recent flush history, seed duration, pending-log size, and opt-in per-namespace row counts via?includeRowCounts=true) instead of a "not yet implemented" stub;GET /_debug/state/{namespace}is now paginated, returning{values, nextKey}pages (?after=exclusive cursor,?limit=capped at 5000, default 500) instead of a single flat key→value map — a response-shape change for anything scripting against it (?key=single-value fetches are unchanged); the web Raw State Debugger now pages incrementally (fetching further pages only as the user scrolls near the end of what's loaded) instead of eagerly merging every page, virtualizes both the flat key table and the key tree (which also gained per-node collapse/expand) so large namespaces render a bounded number of DOM rows, lazily fetches a deep-linked key's value via the single-key endpoint when it hasn't loaded yet, and restricts search to key-only matching over loaded rows. - Storage — SQLite-backed storage now applies versioned schema migrations automatically on startup instead of ad-hoc
CREATE TABLE IF NOT EXISTScalls, writing a one-time backup file before the first pending migration runs against an existing database, and periodically checkpoints its WAL and reclaims free pages in the background instead of only growing the database file over time; newOVERCAST_HYBRID_SYNC/OVERCAST_HYBRID_SYNC_INTERVAL(pending-log fsync policy — the hybrid pending log is now fsynced on a 100ms interval by default, where it was previously never fsynced and an OS crash could lose the whole unflushed window),OVERCAST_HYBRID_DIRTY_ENTRY_THRESHOLD/OVERCAST_HYBRID_DIRTY_BYTE_THRESHOLD(size-triggered early flush, so write bursts flush ahead of the timer instead of accumulating unboundedly), andOVERCAST_HYBRID_MAINTENANCE_INTERVAL(WAL checkpoint/vacuum cadence) config options.
Fixed
- Events — events published without a timestamp (31 call sites across 14 services) rendered as
00:00:00.000in the Events page history; the event bus now stamps its clock on publish whenever a timestamp is missing. Separately, thewalstorage backend no longer issues an fsync on shutdown (or on idle sync intervals) when nothing has been written since the last sync — on hosts with degraded fsync (Docker Desktop under pressure) that pointless fsync could stall shutdown for minutes. - Wire protocols — AWS Query-protocol requests (IAM, EC2, RDS, CloudFormation, STS, SNS, SES, Auto Scaling, ElastiCache, ELBv2, Route 53) now resolve their operation name during protocol identification like every other protocol, making the typed operation registries the primary dispatch path for Query traffic — previously that entire path was unreachable and a second, legacy copy of every operation served all requests. Divergences surfaced by the switchover were fixed: CloudFormation's typed stack operations used the server's default region instead of the request's region, IAM
SimulatePrincipalPolicyskipped its requiredPolicySourceArnvalidation, and SNSSubscribeskipped cross-region endpoint validation. EC2 (all operations), CloudFormationDeleteStack/ExecuteChangeSet/DeleteChangeSet, and SNSPublish/PublishBatchstay on the legacy path for now — each with a documented reason. NewOVERCAST_PROTOCOL_STRICToption: by default a request claiming a wire protocol its service doesn't declare is attempted anyway with a loudprotocol driftwarning (the reactive posture for AWS's 2026 no-notice protocol-change policy); setting the flag restores strict rejection. CloudWatch (metrics) now resolves operations through the shared protocol middleware instead of bespoke header parsing. - Kinesis — sequence numbers were derived from the shard's current record count, which regressed after any record deletion and could silently overwrite an existing record with a colliding sequence number; they now come from a persisted monotonic per-shard counter. The same fix removes a full-shard scan-and-decode that ran on every
PutRecord(and once per record in aPutRecordsbatch), andGetRecordspolling now reads only the requested range after the iterator instead of the whole shard — put cost and steady-state poll cost are now independent of shard depth. All of this applies to the default JSON wire protocol as well as CBOR: the JSON handlers (includingSplitShard/MergeShards) duplicated the shared logic and now delegate to one implementation. - Pagination — invalid continuation tokens passed to CloudFormation
DescribeStackEvents, CloudFront's listing operations, and SSMDescribeParameters/GetParametersByPath/GetParameterHistorynow return each operation's documented AWS error (ValidationError/InvalidArgument/InvalidNextToken) instead of silently restarting from the first page — the silent restart caused duplicate delivery for any client paging with a stale or corrupted token; SSM'sMaxResultsdefaults and caps now match AWS's documented per-operation values. DynamoDBQuery/Scanhad the same duplicate-delivery bug through a different mechanism:ExclusiveStartKeywas resolved by searching for an item exactly equal to the cursor, so a cursor item deleted between pages silently restarted pagination from page 1 — the cursor now resolves by key-order position (matching real DynamoDB's degradation), responses are capped per page (explicitLimit, or an implicit 1000-item cap approximating AWS's documented 1 MB page bound), a plainScannow reads only the requested page from storage instead of the entire table on every call, andListTablesnow honors its documentedLimit(default/cap 100) andExclusiveStartTableNameparameters and returnsLastEvaluatedTableNameinstead of ignoring them and returning every table. S3ListObjectsV2had the same silent-restart bug (a garbledContinuationTokennow returnsInvalidArgumentlike real S3), S3ListParts/ListMultipartUploadspreviously ignoredMaxParts/PartNumberMarker/MaxUploads/KeyMarker/UploadIdMarkerentirely and omitted every truncation field (IsTruncated,NextPartNumberMarker,NextKeyMarker/NextUploadIdMarker) from their XML responses — both now paginate per AWS's documented semantics (defaults/caps 1000, compound key+uploadId resume rule, malformedpart-number-markerrejected withInvalidArgument) — and S3 object listings (ListObjects/ListObjectsV2) now stream the bucket in bounded internal pages instead of materializing and sorting every object on every list call, making per-call cost independent of bucket size beyond the requested page. CloudWatch LogsGetLogEvents— the worst offender found by the audit — parsed but ignoredLimit/NextToken/StartFromHeadand synthesized its tokens from the stream's event count, so a client tailing with the returned forward token (the standard CloudWatch Logs polling pattern) re-received the entire event set on every call, forever; it now implements real positionalf//b/tokens,StartFromHeaddirection semantics, AWS's 10,000 default/cap, the documented same-token-when-exhausted termination convention, andInvalidParameterExceptionfor garbled tokens — andFilterLogEvents, which previously ignoredlimitand returned nonextTokenat all, now honors both. These fixes apply to the default JSON wire protocol, whose handlers previously ran a separate broken copy of the logic and now delegate to the shared implementation. - Storage — SQLite-backed persistence has been running without WAL mode since the project's inception: the connection strings used another driver's parameter spelling, which
modernc.org/sqlitesilently ignores, leaving every persistent deployment in rollback-journal mode — whose commits take a reader-blocking exclusive lock. Under a sustained ~2,000 writes/sec load this starved concurrent reads into multi-second waits and eventual timeout errors. WAL (and the intendedsynchronous=NORMAL) are now genuinely enabled, pinned by tests that readPRAGMA journal_modeback, plus an end-to-end regression test proving reads stay fast under paced write load. Three hardening changes land with it: flush transactions are now chunked (500 ops / 1 MiB per transaction, safe to replay after a crash) so no single flush holds the write lock for a burst-sized window; a storage read that still times out under extreme pressure now returns each service's real AWS throttling error (SlowDown503 for S3,ThrottlingExceptionfor JSON services,Throttlingfor Query services) — which SDKs retry automatically — instead of a generic 500InternalError; and startup now probes the data directory's fsync latency, logging a warning that recommends a named volume when it looks like a slow bind mount, with the probe result, plus new read-retry/read-timeout counters, exposed inGET /_debug/metrics. - Storage — a per-service
OVERCAST_STATE_<SERVICE>override (e.g.OVERCAST_STATE_S3=memory) could silently switch an unrelated service's persistence to memory-only instead of only the overridden service — confirmed for DynamoDB, whose items/streams lost persistence across restarts with no warning whenever any other service had an override configured; every code path that specially detects the storage backend (persistence-capability detection, startup-readiness waiting, persistent-health reporting, CloudFormation's explicit flush) now resolves the correct underlying store first instead of silently losing that capability. A crash or unclean stop leaving a torn final line in the WAL-mode pending log previously refused to start the daemon at all; startup now tolerates and warns instead of aborting, matching the hybrid store's existing behavior. A corrupt or unopenable SQLite database previously poisoned every subsequent read/write with the same error forever; the store now degrades to memory-only (reporting itself unhealthy via the health endpoint) instead of failing every request, and a single undecodable row encountered while seeding memory at startup is skipped with a warning instead of aborting the whole seed. Requests arriving during a startup schema migration previously returned an incorrect empty/not-found result in hybrid mode, or hung indefinitely in persistent mode; they now get an immediate, AWS-shapedServiceUnavailable503 (already retried automatically by AWS SDKs) until migration finishes. Shutdown now bounds the final store flush to the configured shutdown timeout instead of potentially hanging past it, and the server-error shutdown path runs the same cleanup as a normal signal-triggered shutdown instead of skipping it. CloudWatch metric-data retention enforcement, previously memory-mode only, now also runs periodically in persistent/hybrid modes. Separately: hybrid-mode writes that crossed the size-trigger flush threshold while the background seed was still running could miss their early-flush wakeup and sit unflushed until the next timer interval; S3, Kinesis, CloudWatch Logs, and SNS list operations now skip a malformed persisted record instead of failing the whole listing, and large listings use single-scan reads instead of per-key lookups; hybrid-mode reads of high-volume data (queue messages, log events, metric datapoints) no longer wait behind an in-flight flush transaction — they run on a dedicated read connection pool; bulk deletes (queue purge, log-group deletion, stack teardown) record one ranged tombstone and execute one ranged SQL delete instead of one log entry and one statement per key; API Gateway's REST and HTTP API resource/stage/deployment/route/integration listings now also skip a malformed persisted record instead of failing the whole listing, and their per-API delete-all-by-prefix helpers use a single ranged delete when the store supports it instead of one delete per key (ECS's task-definition-family listing was audited too and confirmed already free of the per-key-lookup pattern, so it needed no change); a store degraded to memory-only now caps its pending-log file at 64 MiB instead of growing it on every write for the rest of the run, and startup log replay streams the file instead of loading it whole. Per-service overrides for CloudFormation, API Gateway, and EventBridge (whose storage prefixescfn/apigw/ebdiffer from their config names) and for SSM, KMS, Step Functions, AppSync, and CloudFront (whose storage namespaces contain no:separator) were previously accepted but silently never took effect; both routing gaps are fixed and the overrides now work — note that a previously-set override for one of these services activates for the first time against its own fresh per-service store, while data written during the inert period stays in the default store. Overrides that can never have an effect (dynamodbstreams— a facade over DynamoDB;sts— its state lives under IAM;bedrock/organizations— stateless stubs) now log a startup warning instead of failing silently. - CloudWatch —
PutMetricDatano longer rescans and re-decodes the metric's already-retained datapoints on every write (a cost that grew with the number of points in the retention window, making sustained bursts to one metric progressively slower on the hybrid/persistent backends); retention is unchanged — reads still filter expired points and the periodic background sweep still deletes them; metric datapoint reads (GetMetricStatistics,GetMetricData, alarm evaluation) now fetch only the requested time window via a key-range read instead of scanning and decoding the metric's whole retained history (allocations per read are now independent of how many points exist outside the window), and the retention sweep decides expiry from key timestamps without decoding values. - CloudWatch Logs — log event storage moved from one read-modify-write JSON blob per stream to a dedicated indexed table, with existing data converted by the one-time startup migration (after the automatic pre-migration database backup); this fixes append/read cost that previously grew with a stream's history — measured at 1,000,000 pre-existing events, appends went from ~1.46s/903MB to ~1.2µs/216B per call, and a full-stream read went from ~1.88s/401MB to ~10ms/32MB.
RetentionInDaysis now enforced in persistent/hybrid modes via a periodic ranged delete instead of being stored but never acted on outside memory mode. The same periodic sweep now also deletes a log stream'sDescribeLogStreamsmetadata once its last event has aged out of the retention window and no events remain for it (persisted or buffered) — previously an expired stream's metadata was never cleaned up and stayed listed forever with stale timestamps. Log events remain visible via the Raw State Debugger and resettable via/_debug/resetdespite no longer living in the generic key-value store.GetLogEventsandFilterLogEventsnow read only the requested time window and page via indexed range queries (FilterLogEventsissues one group-wide indexed query instead of one full-history read per stream) while still merging each stream's unflushed write buffer, so just-ingested events stay visible — allocations per call are flat from 100 to 1,000,000 events per stream at a fixed window. - SQS — message storage moved from one JSON blob per message in the generic key-value store to a dedicated indexed table (existing messages converted by the one-time startup migration, after the automatic pre-migration backup);
ReceiveMessagepreviously scanned and JSON-decoded the entire queue on every poll — a consumer polling a 10,000-deep queue paid ~85ms and210,000 allocations per empty receive on the default backend — and now fetches only a bounded batch of visible candidates via a+9–19 allocations, a synchronous row insert replacing an async key-value write). Messages remain visible in the Raw State Debugger and resettable viavisible_atindex, making standard-queue receive cost independent of queue depth (FIFO receive is bounded by visible messages rather than total, since FIFO ordering requires a sort; group-locking, per-call group dedup, and DLQ semantics are unchanged).GetQueueAttributesmessage counts use an indexed count instead of decoding every message. Sends pay a small flat cost for this (/_debug/reset. - DynamoDB — Number-typed key attributes now order numerically, matching AWS's documented contract ("If the data type of the sort key is Number, the results are returned in numeric order"): previously every ordering comparison used the key's raw decimal text, so a Number sort key holding 5, 10, 50 returned
Queryresults and pagination pages in the order 10, 5, 50. The fix coversQuery/Scanresult order, pagination cursors, and the stored key order on both backends — existing persisted items with Number-typed keys are re-encoded by the one-time startup migration (after the automatic pre-migration backup). As part of the same fix, two textual representations of the same numeric key ("5","5.0","5e0") now correctly address the same item on write instead of creating separate items, matching real AWS's numeric key equality. Anything that depended on the old lexicographic order, or on distinct-text-same-value Number keys being separate items, changes behavior with this release. String-keyed tables are unaffected and pay no new cost. - CloudFormation — stack events moved from one read-modify-write JSON blob per stack (cost grew with a stack's event history, and concurrent appends could silently drop events) to one row per event, with an existing stack's history converted automatically the first time it's read.
- Web UI — the system map log-stream peek drawer now switches directly to a newly clicked lambda instance or log stream row while already open instead of swallowing the click and just closing.
Release: https://github.com/Neaox/overcast/releases/tag/v0.0.1-alpha.24