Skip to content

v0.0.1-alpha.24

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 25 Jul 07:45
aa46fa8

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.24

Native 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_STATE now defaults to auto instead of a baked-in memory (Docker image) or hardcoded default (native binary). Unset or set to auto, Overcast resolves the mode at startup from evidence of persistence intent: a mounted volume or bind mount at the data directory, an explicitly configured OVERCAST_DATA_DIR, or an existing database file — any of these selects hybrid; none selects memory. Concretely: docker run -v myvolume:/data ghcr.io/neaox/overcast now persists automatically (it previously ran silently in-memory — mounting a volume had no effect unless you also set OVERCAST_STATE), and fresh unconfigured runs — CI containers especially — get fast in-memory mode with zero configuration. Existing databases are always honored, so no previously-persisted data is ever silently stranded in memory mode. The startup log states the auto decision and which signal drove it, /_health reports both the configured and resolved modes, and the Metrics & Health page shows an actionable notice when auto lands on memory. Set OVERCAST_STATE explicitly to override in either direction — see docs/storage.md.
  • Events — the web UI Event Stream now replays recent history on connect (a rolling server-side buffer of the last 10,000 events) instead of starting blank, so events from before the page was opened — the ones someone debugging actually wants — are visible; request-telemetry events are evicted from the buffer first when it fills and stay hidden by default in the UI. Also fixed on the Events page: the "Pings" toggle previously had no visible effect (a second, hidden source filter dropped what the toggle revealed), and the source filter now shows events from newly-added sources by default instead of silently hiding any source it didn't already know about.
  • Logging — a new trace level now sits below debug (OVERCAST_LOG_LEVEL=trace), and log levels were reclassified repo-wide so each tier is readable: health-check and /_debug/* polling request lines (Docker HEALTHCHECK and the web UI poll these continuously) moved from info to trace along with periodic flush/sweep/vacuum cycle logs, per-invoke Lambda pool internals moved to debug, storage degradation to memory-only is now an error, and SQLite migration failures now reach structured logs instead of only stderr. Also fixed: OVERCAST_LOG_LEVEL previously never actually gated verbosity (it only switched the encoder) — warn/error settings now work, and debug output is quiet enough to attach to bug reports.
  • Web UI / Debug endpoints — the metrics page is now Metrics & Health: a health strip (storage mode, health/degraded badge, live SQLite journal mode, last flush, uptime) and a recommendations section driven by a new advisories array in GET /_debug/metrics — the server now computes storage advisories (journal mode not WAL, store degraded to memory-only, store unhealthy, slow data-directory filesystem with a named-volume suggestion, sustained read pressure, memory-mode notice) and reports the live PRAGMA journal_mode readback per store, so silently-misapplied storage configuration is visible at a glance instead of discoverable only by symptom.
  • Docs — the published documentation (web UI docs browser, docs search, embedded builds) now contains only user-facing pages; contributor documentation (development setup, step debugging, wire-protocol architecture, storage internals, performance baselines, the compatibility-review harness) moved to docs/dev/, which is excluded from publishing the same way docs/plans/ already was — slightly shrinking the embedded docs in binaries and images. The web UI docs browser no longer renders a page's internal frontmatter block or duplicates the page title, supports section deep links (heading anchors scroll correctly on navigation — storage advisories link directly to the relevant tuning section), and notes where contributor docs live; the performance guide gained copy-paste docker run/docker compose examples for the recommended named-volume setup including a host layer-cache mount; and the README's badges were repaired, with a new disclaimer section covering AWS non-affiliation and the project's as-is, best-effort status. Two new user-facing guides landed in the split: a performance-tuning page (storage-mode selection including OVERCAST_STATE=memory for CI, per-service overrides, named-volume-vs-bind-mount guidance with the new startup probe warning, the hybrid flush tuning knobs, and client-perceived-latency expectation-setting) and a storage-modes page (per-backend durability comparison and what survives a restart).
  • Debug endpointsGET /_debug/metrics now reports storage diagnostics (recent flush history, seed duration, pending-log size, and opt-in per-namespace row counts via ?includeRowCounts=true) instead of a "not yet implemented" stub; GET /_debug/state/{namespace} is now paginated, returning {values, nextKey} pages (?after= exclusive cursor, ?limit= capped at 5000, default 500) instead of a single flat key→value map — a response-shape change for anything scripting against it (?key= single-value fetches are unchanged); the web Raw State Debugger now pages incrementally (fetching further pages only as the user scrolls near the end of what's loaded) instead of eagerly merging every page, virtualizes both the flat key table and the key tree (which also gained per-node collapse/expand) so large namespaces render a bounded number of DOM rows, lazily fetches a deep-linked key's value via the single-key endpoint when it hasn't loaded yet, and restricts search to key-only matching over loaded rows.
  • Storage — SQLite-backed storage now applies versioned schema migrations automatically on startup instead of ad-hoc CREATE TABLE IF NOT EXISTS calls, writing a one-time backup file before the first pending migration runs against an existing database, and periodically checkpoints its WAL and reclaims free pages in the background instead of only growing the database file over time; new OVERCAST_HYBRID_SYNC/OVERCAST_HYBRID_SYNC_INTERVAL (pending-log fsync policy — the hybrid pending log is now fsynced on a 100ms interval by default, where it was previously never fsynced and an OS crash could lose the whole unflushed window), OVERCAST_HYBRID_DIRTY_ENTRY_THRESHOLD/OVERCAST_HYBRID_DIRTY_BYTE_THRESHOLD (size-triggered early flush, so write bursts flush ahead of the timer instead of accumulating unboundedly), and OVERCAST_HYBRID_MAINTENANCE_INTERVAL (WAL checkpoint/vacuum cadence) config options.

Fixed

  • Events — events published without a timestamp (31 call sites across 14 services) rendered as 00:00:00.000 in the Events page history; the event bus now stamps its clock on publish whenever a timestamp is missing. Separately, the wal storage backend no longer issues an fsync on shutdown (or on idle sync intervals) when nothing has been written since the last sync — on hosts with degraded fsync (Docker Desktop under pressure) that pointless fsync could stall shutdown for minutes.
  • Wire protocols — AWS Query-protocol requests (IAM, EC2, RDS, CloudFormation, STS, SNS, SES, Auto Scaling, ElastiCache, ELBv2, Route 53) now resolve their operation name during protocol identification like every other protocol, making the typed operation registries the primary dispatch path for Query traffic — previously that entire path was unreachable and a second, legacy copy of every operation served all requests. Divergences surfaced by the switchover were fixed: CloudFormation's typed stack operations used the server's default region instead of the request's region, IAM SimulatePrincipalPolicy skipped its required PolicySourceArn validation, and SNS Subscribe skipped cross-region endpoint validation. EC2 (all operations), CloudFormation DeleteStack/ExecuteChangeSet/DeleteChangeSet, and SNS Publish/PublishBatch stay on the legacy path for now — each with a documented reason. New OVERCAST_PROTOCOL_STRICT option: by default a request claiming a wire protocol its service doesn't declare is attempted anyway with a loud protocol drift warning (the reactive posture for AWS's 2026 no-notice protocol-change policy); setting the flag restores strict rejection. CloudWatch (metrics) now resolves operations through the shared protocol middleware instead of bespoke header parsing.
  • Kinesis — sequence numbers were derived from the shard's current record count, which regressed after any record deletion and could silently overwrite an existing record with a colliding sequence number; they now come from a persisted monotonic per-shard counter. The same fix removes a full-shard scan-and-decode that ran on every PutRecord (and once per record in a PutRecords batch), and GetRecords polling now reads only the requested range after the iterator instead of the whole shard — put cost and steady-state poll cost are now independent of shard depth. All of this applies to the default JSON wire protocol as well as CBOR: the JSON handlers (including SplitShard/MergeShards) duplicated the shared logic and now delegate to one implementation.
  • Pagination — invalid continuation tokens passed to CloudFormation DescribeStackEvents, CloudFront's listing operations, and SSM DescribeParameters/GetParametersByPath/GetParameterHistory now return each operation's documented AWS error (ValidationError/InvalidArgument/InvalidNextToken) instead of silently restarting from the first page — the silent restart caused duplicate delivery for any client paging with a stale or corrupted token; SSM's MaxResults defaults and caps now match AWS's documented per-operation values. DynamoDB Query/Scan had the same duplicate-delivery bug through a different mechanism: ExclusiveStartKey was resolved by searching for an item exactly equal to the cursor, so a cursor item deleted between pages silently restarted pagination from page 1 — the cursor now resolves by key-order position (matching real DynamoDB's degradation), responses are capped per page (explicit Limit, or an implicit 1000-item cap approximating AWS's documented 1 MB page bound), a plain Scan now reads only the requested page from storage instead of the entire table on every call, and ListTables now honors its documented Limit (default/cap 100) and ExclusiveStartTableName parameters and returns LastEvaluatedTableName instead of ignoring them and returning every table. S3 ListObjectsV2 had the same silent-restart bug (a garbled ContinuationToken now returns InvalidArgument like real S3), S3 ListParts/ListMultipartUploads previously ignored MaxParts/PartNumberMarker/MaxUploads/KeyMarker/UploadIdMarker entirely and omitted every truncation field (IsTruncated, NextPartNumberMarker, NextKeyMarker/NextUploadIdMarker) from their XML responses — both now paginate per AWS's documented semantics (defaults/caps 1000, compound key+uploadId resume rule, malformed part-number-marker rejected with InvalidArgument) — and S3 object listings (ListObjects/ListObjectsV2) now stream the bucket in bounded internal pages instead of materializing and sorting every object on every list call, making per-call cost independent of bucket size beyond the requested page. CloudWatch Logs GetLogEvents — the worst offender found by the audit — parsed but ignored Limit/NextToken/StartFromHead and synthesized its tokens from the stream's event count, so a client tailing with the returned forward token (the standard CloudWatch Logs polling pattern) re-received the entire event set on every call, forever; it now implements real positional f//b/ tokens, StartFromHead direction semantics, AWS's 10,000 default/cap, the documented same-token-when-exhausted termination convention, and InvalidParameterException for garbled tokens — and FilterLogEvents, which previously ignored limit and returned no nextToken at all, now honors both. These fixes apply to the default JSON wire protocol, whose handlers previously ran a separate broken copy of the logic and now delegate to the shared implementation.
  • Storage — SQLite-backed persistence has been running without WAL mode since the project's inception: the connection strings used another driver's parameter spelling, which modernc.org/sqlite silently ignores, leaving every persistent deployment in rollback-journal mode — whose commits take a reader-blocking exclusive lock. Under a sustained ~2,000 writes/sec load this starved concurrent reads into multi-second waits and eventual timeout errors. WAL (and the intended synchronous=NORMAL) are now genuinely enabled, pinned by tests that read PRAGMA journal_mode back, plus an end-to-end regression test proving reads stay fast under paced write load. Three hardening changes land with it: flush transactions are now chunked (500 ops / 1 MiB per transaction, safe to replay after a crash) so no single flush holds the write lock for a burst-sized window; a storage read that still times out under extreme pressure now returns each service's real AWS throttling error (SlowDown 503 for S3, ThrottlingException for JSON services, Throttling for Query services) — which SDKs retry automatically — instead of a generic 500 InternalError; and startup now probes the data directory's fsync latency, logging a warning that recommends a named volume when it looks like a slow bind mount, with the probe result, plus new read-retry/read-timeout counters, exposed in GET /_debug/metrics.
  • Storage — a per-service OVERCAST_STATE_<SERVICE> override (e.g. OVERCAST_STATE_S3=memory) could silently switch an unrelated service's persistence to memory-only instead of only the overridden service — confirmed for DynamoDB, whose items/streams lost persistence across restarts with no warning whenever any other service had an override configured; every code path that specially detects the storage backend (persistence-capability detection, startup-readiness waiting, persistent-health reporting, CloudFormation's explicit flush) now resolves the correct underlying store first instead of silently losing that capability. A crash or unclean stop leaving a torn final line in the WAL-mode pending log previously refused to start the daemon at all; startup now tolerates and warns instead of aborting, matching the hybrid store's existing behavior. A corrupt or unopenable SQLite database previously poisoned every subsequent read/write with the same error forever; the store now degrades to memory-only (reporting itself unhealthy via the health endpoint) instead of failing every request, and a single undecodable row encountered while seeding memory at startup is skipped with a warning instead of aborting the whole seed. Requests arriving during a startup schema migration previously returned an incorrect empty/not-found result in hybrid mode, or hung indefinitely in persistent mode; they now get an immediate, AWS-shaped ServiceUnavailable 503 (already retried automatically by AWS SDKs) until migration finishes. Shutdown now bounds the final store flush to the configured shutdown timeout instead of potentially hanging past it, and the server-error shutdown path runs the same cleanup as a normal signal-triggered shutdown instead of skipping it. CloudWatch metric-data retention enforcement, previously memory-mode only, now also runs periodically in persistent/hybrid modes. Separately: hybrid-mode writes that crossed the size-trigger flush threshold while the background seed was still running could miss their early-flush wakeup and sit unflushed until the next timer interval; S3, Kinesis, CloudWatch Logs, and SNS list operations now skip a malformed persisted record instead of failing the whole listing, and large listings use single-scan reads instead of per-key lookups; hybrid-mode reads of high-volume data (queue messages, log events, metric datapoints) no longer wait behind an in-flight flush transaction — they run on a dedicated read connection pool; bulk deletes (queue purge, log-group deletion, stack teardown) record one ranged tombstone and execute one ranged SQL delete instead of one log entry and one statement per key; API Gateway's REST and HTTP API resource/stage/deployment/route/integration listings now also skip a malformed persisted record instead of failing the whole listing, and their per-API delete-all-by-prefix helpers use a single ranged delete when the store supports it instead of one delete per key (ECS's task-definition-family listing was audited too and confirmed already free of the per-key-lookup pattern, so it needed no change); a store degraded to memory-only now caps its pending-log file at 64 MiB instead of growing it on every write for the rest of the run, and startup log replay streams the file instead of loading it whole. Per-service overrides for CloudFormation, API Gateway, and EventBridge (whose storage prefixes cfn/apigw/eb differ from their config names) and for SSM, KMS, Step Functions, AppSync, and CloudFront (whose storage namespaces contain no : separator) were previously accepted but silently never took effect; both routing gaps are fixed and the overrides now work — note that a previously-set override for one of these services activates for the first time against its own fresh per-service store, while data written during the inert period stays in the default store. Overrides that can never have an effect (dynamodbstreams — a facade over DynamoDB; sts — its state lives under IAM; bedrock/organizations — stateless stubs) now log a startup warning instead of failing silently.
  • CloudWatchPutMetricData no longer rescans and re-decodes the metric's already-retained datapoints on every write (a cost that grew with the number of points in the retention window, making sustained bursts to one metric progressively slower on the hybrid/persistent backends); 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. RetentionInDays is now enforced in persistent/hybrid modes via a periodic ranged delete instead of being stored but never acted on outside memory mode. The same periodic sweep now also deletes a log stream's DescribeLogStreams metadata once its last event has aged out of the retention window and no events remain for it (persisted or buffered) — previously an expired stream's metadata was never cleaned up and stayed listed forever with stale timestamps. Log events remain visible via the Raw State Debugger and resettable via /_debug/reset despite no longer living in the generic key-value store. GetLogEvents and FilterLogEvents now read only the requested time window and page via indexed range queries (FilterLogEvents issues one group-wide indexed query instead of one full-history read per stream) while still merging each stream's unflushed write buffer, so just-ingested events stay visible — allocations per call are flat from 100 to 1,000,000 events per stream at a fixed window.
  • SQS — message storage moved from one JSON blob per message in the generic key-value store to a dedicated indexed table (existing messages converted by the one-time startup migration, after the automatic pre-migration backup); ReceiveMessage previously scanned and JSON-decoded the entire queue on every poll — a consumer polling a 10,000-deep queue paid ~85ms and 210,000 allocations per empty receive on the default backend — and now fetches only a bounded batch of visible candidates via a visible_at index, making standard-queue receive cost independent of queue depth (FIFO receive is bounded by visible messages rather than total, since FIFO ordering requires a sort; group-locking, per-call group dedup, and DLQ semantics are unchanged). GetQueueAttributes message counts use an indexed count instead of decoding every message. Sends pay a small flat cost for this (+9–19 allocations, a synchronous row insert replacing an async key-value write). Messages remain visible in the Raw State Debugger and resettable via /_debug/reset.
  • DynamoDB — Number-typed key attributes now order numerically, matching AWS's documented contract ("If the data type of the sort key is Number, the results are returned in numeric order"): previously every ordering comparison used the key's raw decimal text, so a Number sort key holding 5, 10, 50 returned Query results and pagination pages in the order 10, 5, 50. The fix covers Query/Scan result order, pagination cursors, and the stored key order on both backends — existing persisted items with Number-typed keys are re-encoded by the one-time startup migration (after the automatic pre-migration backup). As part of the same fix, two textual representations of the same numeric key ("5", "5.0", "5e0") now correctly address the same item on write instead of creating separate items, matching real AWS's numeric key equality. Anything that depended on the old lexicographic order, or on distinct-text-same-value Number keys being separate items, changes behavior with this release. String-keyed tables are unaffected and pay no new cost.
  • CloudFormation — stack events moved from one read-modify-write JSON blob per stack (cost grew with a stack's event history, and concurrent appends could silently drop events) to one row per event, with an existing stack's history converted automatically the first time it's read.
  • Web UI — the system map log-stream peek drawer now switches directly to a newly clicked lambda instance or log stream row while already open instead of swallowing the click and just closing.

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