Repository navigation
v5.3.0-beta.1
Pre-releaseHarper 5.3.0-beta.1 is the first beta of the 5.3 line. It covers everything merged since the
last stable release, v5.2.13 — both 5.3 pre-releases and the fixes that also shipped on the
5.2 patch train.
Branched databases
An application can declare branchedDatabases: [data] and get a private, durable fork of that
database — its own rows, invisible to the base and to every other application, reached through
the same databases import (#2352). Several variants of an application can run against isolated
copies of the same data. The fork lives at a deterministic path derived from the application and
base database names, so it is adopted again on restart and resolves to the same place on every
node. Branches declare their own tables through @table,
ensureTable and defineTable (#2523), get their own blob store cloned with hard links so a
fork is cheap (#2426), and are removed with the application on drop_component (#2517). Two
correctness fixes came out of the same work: a branched database no longer silently discards
its latest writes when restarted after a crash or hard kill (#2414), and an empty
branchedDatabases declaration now loads under LMDB (#2413).
Record locks
table.lock(id) gives an application an exclusive lock on a single record. It is serialized
across worker threads on one node (#2462), then across the cluster with amortized per-record
ownership so a hot record does not pay a round trip per lock (#2498). Multi-threaded nodes
relay a local lock() to the owning worker (#2667). For cluster safety, successor freshness
is fenced by a lockBarrier control entry (#2627), successors are kept fresh across
cross-node handoffs (#2613), and a node can prove it has quiesced rather than waiting out its
delegation lease (#2663).
Vector search: native HNSW
HNSW indexes now run on a file-primary memory-mapped graph backend built on a shared
derived-index runtime (#2430, #2567). An eligible index defaults to that native plane when its
table has durable audit enabled, or enables audit: true in the same declaration; a table that
only inherits global audit configuration stays on the JS implementation, and nativePlane: false
is the explicit opt-out (#2657). Queries rerank only the rows they actually return and resolve hits from keys stored
in the plane, instead of reranking the whole candidate set (#2665). Index lag is bounded and
callers can wait for prior writes to be visible (#2658), including a durability barrier that
runs during catch-up rather than only when the write queue drains (#2676).
HTTP, REST and components
- REST supports total-count pagination through
Prefer: count=, answered withContent-Range
(#2147), so a client can page without a second counting query. withNodeAdapternow rebuilds its response on a realWritable, so Node middleware —
Next.js,compression,send— runs against it correctly (#2528).- Streaming REST responses return structured errors instead of a truncated body, with the
terminalharper-errorframe pinned by regression coverage (#2614, #2617). On Bun, a
streamed response is closed when the client asks for it, fixing hung iterable responses
(#2351). - A new
putoperation was added, and the target-database authorization mismatch it exposed
was fixed (#2347). The Operations API resolves@relationshipattributes instead of
returning null or rejecting them (#2302), and a component-registered operation can be
granted in a role's operations allowlist (#2260). - Array
PUTfollows collection semantics, dispatches per element, and is atomic per batch
(#2097). The REST query-string parser no longer falls through fromgroup-byintosort
(#2397), and the SSE serializer distinguishes an absentdata/id/retryfrom a falsy one
(#2096). - An application can be run in its own dedicated worker thread (#2524).
- Component deploys got safer: a replacement build is assembled aside and validated before the
swap (#2345), the deploy waits for the restart it triggers and tells MQTT clients why a
publish was refused (#2341), a build can be staged withactivate:falseand deployed later
bydeployment_id(#2605), dormant staged builds are bounded by
deployment.stagingRetention.maxCount(#2531), the activation swap retries through a
transient rename lock (#2570), and components are restored after a failed deployment
preparation (#2066). An invalid GraphQL schema now fails on its parse error instead of
gating every application for 30 seconds (#2432).
Security
- An operation is authorized against the authenticated principal that invoked it, closing a
path where the wrong identity was checked (#2217). - SQL honors the permission denial that
processASTcomputes rather than continuing past it
(#2202). create_authentication_tokensaccepts an inline role, so a token can be scoped to less than
the creating user's full authority (#2176).- Components can be deployed from CI with no stored credential, using OIDC trusted publishing
(#2173). - mTLS clients whose revocation status cannot be checked are rejected under fail-closed, and
the issuer is recovered from Harper's trusted CAs when the socket chain does not carry it
(#2457). TLS state is published transactionally, so a failed rebuild can never downgrade the
node below its last-good configuration (#2384). liveSubscriptionAuthcan revoke one subscriber without ending its subscription (#2039).
Data integrity and durability
- Transaction-log replay fail-stops at a corrupt frame and discards the transaction that frame
truncated, instead of applying a partial transaction (#2087). copy-dbno longer produces a silently corrupt, non-restorable copy (#2098), and a backup
captures only whole blobs, substituting a marker for one still being written (#2265).- Resolved attribute values are kept out of durable records at every layer that writes them
(#2368) — a stored record no longer inherits a computed value. - Cluster-origin table definitions are additive-only, so a peer's partial schema snapshot
cannot destroy locally declared attributes (#2258), and a table being created is invisible to
catalog scans on other threads, so replication cannot announce a partial attribute list
(#2381). - Multi-worker counter increments are no longer lost, and a resequenced write reusing a version
no longer produces stale reads (#2259). - Record version and
txnLogKeyare exposed as separate audit clocks (#2497), and an audit
entry's field offsets are independent of itspreviousVersionvalue (#2499). A follow-up
fixed LMDB partial-record history resolution (#2526). - Transaction handling: the RocksDB transaction handle is released on abort and on a failed
direct commit (#2128), a transaction parked in its commit phase is not poisoned (#2086), an
ImmediateTransactioncommits the handle it opened during its own commit (#2291), a real
transaction scope is started when the context slot holds a self-committing transaction
(#2325), and a request-path commit's conflict retries are bounded by its queue-time budget
(#2459). - Same-key write order is preserved for explicit saves (#2561), and a supplied record root is
rejected rather than silently replacing the instance's staged changes (#2578). - Transaction-log space reclamation is guarded after a restart (#2529),
log_files_deletedis
no longer discarded (#2472), audit retention is applied continuously to RocksDB transaction
logs (#2338), and a write-ahead retention floor is recorded and raised before every prune
(#2458). - Harper keeps booting when its storage volume is full or at quota (#2245), and fails the boot
when the data-version stamp was not recorded (#2398) rather than running against an
unconfirmed upgrade.
Indexing
- A secondary-index backfill resumes from its checkpoint and yields the event loop, so a large
table converges instead of stalling the node (#2539), and every index write a checkpoint
covers is awaited so a failed write cannot be skipped (#2563). isIndexingis stamped on the schema load path, so no thread serves a partially built index
(#2543).- Indexed array-element scans return each record once, before paging (#2493), and
element-scoping semantics for queries over array-valued properties are pinned by tests
(#2437). searchByIndexand the custom-indexsearch()contract take an options object instead of a
positional tail (#2187). A stale positional fifth argument tosearchByIndexnow throws a
TypeErrorrather than silently permitting a scan it meant to forbid, and a custom index's
search()receives{ filter, minResults }withcontextstill second positional.
Query planning and performance
- The query planner uses storage-level statistical range estimates (#2163), guards estimates
that a zero RocksDB entry-count would turn intoInfinityor a negative (#2479), and
describesizes record counts from a bounded estimate instead of a second full key scan
(#2612). - The RocksDB
WriteBufferManagerno longer stalls every writer by default (#2492), and active
scans stay alive during eviction (#2556). - Post-ordering sort keys are resolved once per row, and a multi-store chain renews its idle
window at each native commit (#2675). - MQTT shares payload and QoS 0 packet encoding across the subscribers of a topic (#2040).
sql.engine,allowFullScan,maxSortRowsandmaxHashRowsare wired to real config
(#2484), and per-table record-structure dictionary size is observable (#2250).- Opt-in deflate compression for file-backed blobs (#2460).
Operations and diagnostics
- Startup and restart: a swallowed uWS listen failure is surfaced instead of hanging (#2112),
HTTP starts correctly after a pre-ready worker restart (#2129), a pre-ready worker's event
loop stays alive through startup (#2314), a node stays bootable whenthreads.maxHeapMemory
is set below what a worker needs (#2290), worker respawns no longer block container restarts
(#2316), a worker counts as replaced when its replacement is serving rather than when it exits
(#2363), and a job worker cannot disable the rolling-restart throttle (#2600). - Configuration: the root config is read synchronously with rename retries bounded by wall
clock (#2191),set_configurationrejects unrecognized parameters instead of reporting
success (#2272), themodelsblock hot-reloads on file change (#2377), and intermittent
Windowsset_configuration500s are fixed (#2339). - Diagnosis: the long-lived transaction holder behind a wedged commit is reported (#2473), a
stuck worker's OS thread state is logged when an ITC broadcast times out (#2521), replicated
apply failures are reported before later events are consumed (#2630), and the startup options
banner reports the Harper version (#2263). - Logging: external and component loggers inherit the main rotation config (#1880),
logging.rotation.maxSizeis enforced on the write path rather than only on the 60-second
audit tick (#2475), and the window before config load no longer drops every log line (#2467). - Node identity and URLs: a non-bare-host node identity is rejected and IPv6 replication URLs
are formed correctly (#2223), and startup URLs are not double-wrapped whennode.hostnameis
already a URL (#2219). - Windows: watch paths are canonicalized so an 8.3 short path cannot abort the process (#2309),
and a deleted watched path no longer raises an uncaughtEPERM(#2364). list_agent_sessionsis ordered by activity time instead of primary-key order (#2271), MCP no
longer manufacturescreate_*tools for Resources with no real create verb (#2405), an
analytics sample stays recorded while its window is being flushed (#2662), and
install_node_moduleshonors the documenteddry_runflag (#2340).
Also in this release
Test, CI and maintenance work: promoted QA regression anchors and new integration coverage for
blob lifecycle, audit history, MQTT, SSE, TTL and caching; unit-test isolation from the
installed Harper root; per-suite CI steps with measured time budgets; Windows and Node 26 job
fixes; AI-review-coverage enforcement and review-workflow tuning; release-backport targeting by
milestone instead of the patch label; and dependency updates including rocksdb-js 2.9.1.