v5.3.0-alpha.1
Pre-releaseFirst alpha of the Harper 5.3 line. It covers everything merged to main since v5.2.4, which is where the v5.2 patch branch was cut — fixes released as 5.2.5 through 5.2.9 originated here and are included.
Application database branches
An application can now take a private, durable fork of a database, work in it, and have it torn down with the component.
- A component gets its own branch of a base database, with the base's blob tree cloned in by hard link rather than copied (#2352).
- Tables can be declared into a branch through
@table,ensureTableanddefineTable(#2523). drop_componentremoves the branched databases the application created (#2517).- A branched database no longer silently discards its most recent writes when the node is restarted after a crash or hard kill (#2414).
- Branch identity is now owned from disk rather than from memory alone: a branch can reclaim an identity held by its own stranded roots, survives a bad marker instead of bricking, holds identity through cleanup, and deletes only the roots it recorded.
create_databaseis closed to a branch identity so it cannot clone the wrong volumes, and an incomplete blob-volume clone is refused rather than adopted. - An empty
branchedDatabasesdeclaration loads under LMDB (#2413).
Data integrity
copy-dbno longer produces a silently corrupt, non-restorable copy (#2098) — a copy that looked successful could not be restored.- Transaction-log replay fail-stops at a corrupt frame and discards the transaction it truncated, instead of replaying past the damage (#2087).
- A backup captures only whole blobs, substituting a marker for a blob still being written (#2265), so a backup taken during a write is no longer half a blob.
- Resolved attribute values are kept out of durable records at every layer that writes them (#2368) — previously a computed value could be persisted as if it were stored data.
- An audit entry's field offsets no longer depend on its
previousVersionvalue (#2499), and record version and transaction-log key are exposed as separate audit clocks (#2497). getRecordAtTimewalks back from the audit-store key rather than the record version, so partial-record history resolves correctly under LMDB.- Blob content created from a plain
Uint8Arraydecodes as text rather than as byte values (#2415). - Boot fails when the data-version stamp was not recorded, rather than continuing against an unknown on-disk version (#2398).
Transactions, locking and storage
- Writes issued after a mid-scope commit are atomic with the scope that owns them (#2239); a self-committing transaction in the context slot now starts a real scope (#2325); an
ImmediateTransactioncommits the handle it opens during its own commit (#2291). - A transaction parked in its commit phase is no longer poisoned (#2086) — this was destroying the deploy payload blob.
- The RocksDB transaction handle is released on abort and on a failed direct commit (#2128).
- Request-path commit conflict retries are bounded by the request's queue-time budget (#2459), and the exclusive
update-attributeslock wait is bounded and released structurally (#2252). - A wedged commit now reports the long-lived transaction holding it up (#2473).
table.lock(id)provides exclusive record locks serialized across worker threads (#2462).- The RocksDB
WriteBufferManagerno longer stalls every writer by default (#2492). - Audit retention is applied continuously to RocksDB transaction logs (#2338), and
log_files_deletedis reported instead of discarded (#2472). - Harper boots when its storage volume is full or at quota, instead of failing at startup exactly when you need it to diagnose the problem (#2245).
- File-backed blobs support opt-in deflate compression (#2460).
- A database can be opened into a caller-owned table graph instead of the global map (#2285).
Query engine and APIs
- The query planner uses storage-level statistical range estimates (#2163), with a guard for the condition estimates a zero entry-count estimate breaks (#2479).
- SQL configuration is wired to the real config:
sql.engine,allowFullScan,maxSortRows,maxHashRows(#2484) — these settings previously had no effect. - Indexed array-element scans return each record once, before paging (#2493); element-scoping semantics for queries over array-valued properties are pinned (#2437).
- REST supports total-count pagination via
Prefer: count=with aContent-Rangeresponse (#2147), and the query-string parser no longer falls through from group-by into sort (#2397). - New
putoperation, plus a fix for the target-database authorization mismatch (#2347). - The Operations API resolves
@relationshipattributes instead of returning null or rejecting them (#2302). set_configurationrejects unrecognized parameters instead of reporting success (#2272);install_node_moduleshonors the documenteddry_runflag (#2340);list_agent_sessionsorders by activity time (#2271).- An invalid GraphQL schema fails on its parse error instead of gating every application for 30 seconds (#2432).
- MCP no longer manufactures
create_*tools for Resources with no real create verb (#2405). - HNSW auto-scales
efConstructionand the search-ef ceiling with graph size, for graphs of 1M+ nodes (#2181). - Per-table record-structure dictionary size is observable (#2250).
Security and authentication
- A SQL permission denial computed by
processASTis honored rather than dropped (#2202). - Under fail-closed mTLS, a client whose revocation status cannot be checked is rejected, and the issuer is recovered from Harper's trusted CAs when the socket chain lacks it. A certificate is never resolved as its own issuer, and an unchanged CA set is not republished.
- TLS state is published transactionally, so a failed rebuild cannot downgrade below the last-good configuration (#2384).
- Scoped authentication tokens: an inline role on
create_authentication_tokens(#2176). - A component-registered operation can be granted in a role's operations allowlist (#2260).
- An unrecognized app-port credential is rejected only once route ownership is known (#2419).
liveSubscriptionAuthcan revoke a single subscriber without ending its subscription (#2039).- OIDC trusted publishing: deploy from CI with no stored credential (#2173).
Clustering and replication
- A table being created stays invisible to catalog scans on other threads, so replication can never announce a partial attribute list (#2381).
- Cluster-origin table definitions are additive-only, so a peer's partial schema snapshot cannot destroy locally declared attributes (#2258).
- A non-bare-host node identity is rejected, and IPv6 replication URLs are formed correctly.
sourcedFromcache-fill conflict convergence is fixed (#2065).- RocksDB subscription events are delivered for source fills whose version differs from the log key.
Server, workers and deployment
- HTTP startup recovers after a pre-ready worker restart (#2129), and a pre-ready worker's event loop stays alive through startup (#2314).
- A worker counts as replaced when its replacement is serving, not when it exits (#2363); worker respawns no longer block container restarts (#2316).
- A node stays bootable when
threads.maxHeapMemoryis set below what a worker can start on (#2290). - A swallowed uWS startup-listen failure is surfaced instead of hanging silently (#2112); startup URLs are no longer double-wrapped when
node.hostnameis itself a URL (#2219); the startup banner reports the Harper version. get_statuscounts all live worker threads in its cross-thread aggregation (#1952), and a stuck worker's OS thread state is logged when an ITC broadcast times out (#2521).- Component installs no longer hang at startup on zombie process groups (#2085).
- A component deploy builds the replacement aside and validates it before the swap (#2345), waits for the restart it triggers, and tells MQTT clients why a publish was refused (#2341); components are restored after a failed deployment preparation (#2066).
- The concurrent PATCH route fails fast when it is not ready (#2204).
- On Bun, a streamed HTTP response closes when the client asks for it, fixing hung iterable REST responses (#2351).
- The SSE serializer distinguishes an absent
data/id/retryfrom a falsy one (#2096). - MQTT shares payload and QoS 0 packet encoding across topic subscribers (#2040).
- The
modelsconfig block hot-reloads when the config file changes (#2377).
Logging
- The no-config window at startup no longer drops every log line (#2467).
- External and component loggers inherit the main rotation config (#1877, #1880), and rotated archives are named after their source log so they cannot collide.
Windows
- Intermittent
set_configuration500s caused by the config write's rename retry are fixed (#2339). - Watch paths are canonicalized so an 8.3 short path cannot abort the process (#2309), and a deleted watched path no longer raises an uncaught
EPERM(#2364). - Component archive routing preserves explicit directory links, stays nonblocking, and avoids unnecessary archive installs.
Also in this release
Test and CI work: unit-test runs isolated from the installed Harper root (#2299); promoted QA regression anchors (MQTT connect wedge, EAV blob type drift, TLS cert-table swap, deployment payload operations, SSE finite-generator completion, transaction-log purge blast radius, copy-db blob store); deflaked caching, HNSW routing, record-count and indexed-TTL suites; a hardened Windows unit-test gate; CI job and step timeouts; AI-review workflow and concurrency updates; release backports targeted by PR milestone instead of the patch label; and dependency updates (alasql 4.19.0, a deduped nested msgpackr under rocksdb-js, and non-major bumps).
Full changelog: v5.2.4...v5.3.0-alpha.1