v0.41.0-beta.5
Pre-release-
Local full-text search in
atomic_lib(lib/src/search/): a KV inverted
index on the existing redb / sled / BTreeMap (OPFS) store. Indexes title,
description, and Loro document body on every commit; queries AND tokens,
rank with BM25, and match 1-edit prefix-fuzzy (avacadofindsavocado).
Exactproperty:"value"filters reuse the PropValSub index (emptyq+
isAis the file picker); there is no query language and no key escaping.
Db/AtomicNode/ WASMClientDb.searchexpose it. Hosted/search
is the same engine — Tantivy is removed.
The browser uses this index (MiniSearch is gone) and merges with/search
when online. Scale benches:
cargo bench -p atomic_lib --bench search_bench --features db-redb.
Seeplanning/local-search.md. -
Live collaboration is one binary frame on both transports. Edits in
progress, cursors and drive presence cross the WebSocket as
EPHEMERAL (0x40), the frame the Iroh link already used, in both
directions; the text framesLORO_SYNC_UPDATE,LORO_EPHEMERAL_UPDATE
andPRESENCE_UPDATE(base64 in JSON) are removed, the four subscribe /
unsubscribe text frames stay. The server ignores the agent a client puts
in the frame and stamps the connection's authenticated identity on every
copy it fans out. Between nodes the payload is now the raw bytes rather
than their base64 text, so cursors from a pre-2026-09-04 peer do not
decode on this build (and vice versa); nothing persistent is affected.
WsClient::send_loro_sync_update/send_loro_ephemeral_update/
send_presence_updatesend the binary frame. Capabilityephemeral. -
Drive sync is one binary frame on both transports. The browser's
hash-first probe and its reduced reconcile now arrive asSYNC (0x30)
with"probe": trueor"subjects": [...]in the JSON tail
(encode_sync_probe,encode_sync_filtered), handled by the shared sync
engine; a stale probe is answered with the newSYNC_RESEND (0x38).
The textSYNC_VVrequest, its textSYNC_RESEND <drive>answer and the
server handler's private copy of the reconcile are removed. Capability
sync-probe.WsClientreportsWsMessage::SyncResend. -
One WebSocket subscription frame.
SUB <subject>registers a
drive-wide subscription when the subject is a drive and a per-resource one
otherwise; the per-resource text frameSUBSCRIBE <subject>(which also
registered a spurious drive fan-out entry that delivered URL subjects'
commits twice) and the filter subscriptionSUBSCRIBE_QUERY(sent by
nothing outside the Rust integration tests) are removed, along with the
commit monitor's third subscription map, theMembershipNotification
fan-out andWsClient::subscribe_query.WsClient::subscribe_resource
sendsSUB. The lib's watched-query index (QueryFilter::watch,
DbEvent::QueryMembershipChanged) is unchanged; the Flutter event stream
still consumes it. -
atomic_lib::runtime::AtomicNodeis cut down to what binds to it. The
WASMClientDbis the only adapter on the node and usesfrom_db,db,
queryandapply_commit; those, plus the agent accessors and
IngestPolicy::{Hub, Peer, LocalCache}, are what remains.open/
NodeConfig/NodeStorage,get,mutate/ResourceEdit,subscribe
andsync_with_peerare removed (each was one line overDb, with no
consumer three days after landing). The seam stays and grows again from
what the next binding actually calls (planning/atomic-lib-runtime.md). -
Sync stack cleanup. No wire changes.
WsMessagelost itsCommitand
Resourcevariants and the client no longer parses the pre-v2COMMIT,
RESOURCE,AUTHENTICATEDandERRORtext frames (nothing has sent
them since the binary protocol);IngestPolicy::Replicawent with them.
CommitIngestOpts::hub/::peerare the two validation presets that were
copied in three places;ws_apply::apply_destroy_checked(a byte-identical
twin ofapply_destroy) is merged into it; the server's three copies of
"encode anUPDATE/DESTROYfor a change" are oneencode_change_frame.
Iroh: the deadsync_drive_with_peerwrapper and the private_forced
indirection are gone, the live-peer registry is aLazyLock<Mutex<HashMap>>
holding the pinned QUIC connection alongside the stream (a peer registered
before the registry was initialised used to be dropped silently), and the
sync-event debounce map is pruned instead of growing for the life of the
process. Test-only helpersengine::drive_items/drive_sync_hashand
OwnerPolicy::{owner_agent, hosted_drive_subjects}are removed. -
Sync protocol follow-ups (see
docs/src/websockets.md, "Changed in
2026-09"):- Challenge-bound
AUTHover WebSocket: the server's first frame is
CHALLENGE (0x42)with a per-connection nonce; a client signs
requestedSubject = "{origin}#{nonce}"and the proof is good on that
socket only, so a captured frame can no longer be replayed elsewhere
inside the five-minute window. Nonce-less proofs are still accepted
(engine::AuthChallenge::Issued);AuthChallenge::Requiredis the
strict mode, not yet wired to an option. Capabilityauth-nonce. - The WebSocket handler reads a client
HELLO(capabilityclient-hello)
and answersCOMMITwith a slimCOMMIT_OKof[request_id] [commit_id]
for a client that listedcommit-ok-slim(the browser and the Rust
WsClientboth do).protocol::decode_commit_okreads both forms. - Subscriptions follow the connection's identity: when an
AUTHchanges
it, the commit monitor re-runscheck_readfor every subject, drive and
filter subscription the connection holds and drops the unreadable ones
(RebindAgent, capabilityrebind-on-auth). Until now aSUBaccepted
as the owner kept delivering after the socket re-authenticated as a
stranger. WsMessage::Erroris a struct (request_id,code,message);
WsClient::post_commitsettles only on its ownrequest_idand returns
the commit id, so several commits can be in flight on one connection.
Unknown text frames surface asWsMessage::Unrecognizedinstead of an
error.WsClientsends aHELLOon connect and answers the server's
challenge automatically (WsClient::auth_subject).- New error code
INVALID_SIGNATURE (9)for aCOMMITwhose signature
does not verify; it went out asUNKNOWNbefore. - Iroh:
LIVE_CONNECTIONSis keyed by peer and pruned when the peer is
removed. It was an append-only list that pinned every QUIC connection
ever dialed for the life of the process. ws_apply::apply_commit_json(the replica applier that skips rights
checks, audit finding F6) is gone: its only route was the pre-v2COMMIT
text frame, which no server sends. The server's rights-checked
handlers::commit::apply_commit_jsonis the only function of that name.- New integration tests:
ws_errors(ERROR format,request_idecho and
codes), the nonce, the identity re-bind, and the slim ack.
- Challenge-bound
-
Sync protocol hardening (WebSocket and Iroh; wire reference rewritten in
docs/src/websockets.md, see its "Changed in 2026-09" section):AUTHproofs expire: a signed authentication older than five minutes
(atomic_lib::authentication::AUTH_MAX_AGE_MS) is refused, on WebSocket,
Iroh and the HTTP auth headers alike. Until now only future-dated
timestamps were rejected, so a captured proof never expired.- Over WebSocket,
AUTH.requestedSubjectmust name the server: either the
origin the socket was opened on or the configured server URL
(AuthBinding::Origins, the same tolerance the HTTP auth headers have); a
proof signed for another server, or for the agent's own subject, no
longer opens a session. A refusedAUTHanswers
with the new error codeAUTH_FAILED (8). The RustWsClientnow signs
the server origin (it signed the agent subject before). The Iroh
initiator only accepts an auth-back signed for its own node id. - The hash-first
SYNC_VVprobe and theRBSR_FP/RBSR_ITEMSframes
are gated bycheck_readper subject, like the fullSYNC_VVexchange.
Previously any socket could enumerate every subject and version vector of
any drive it could name. - On the Iroh live link, destroys travel as the signed destroy
COMMIT
(DbEvent::Destroyednow carriescommit_json); a nakedDESTROYframe
from a peer is ignored instead of applied on the connection's drive-level
write verdict. - Capability advertisement:
AUTH_OKcarries a JSON array of capability
names (atomic_lib::sync::protocol::CAPABILITIES),HELLOcarries the
same list after the device name. Both are optional trailing bytes. UNSUB (0x21)now actually cancels a drive subscription (it previously
edited a set nothing read).KEEPALIVE (0x41)is echoed over WebSocket so
browsers can detect a dead socket.- Shared golden wire vectors (
lib/src/sync/protocol_vectors.json, mirrored
atbrowser/lib/src/protocol_vectors.json) pin the Rust and TypeScript
codecs byte-for-byte. - Integration tests bind the test server to
127.0.0.1and fall back to an
OS-assigned port whenportpickerfinds none (hosts without IPv6).
-
Tauri Android: ship
arm64-v8aonly. The sideloadable universal APK was ~369 MB because it bundled four copies oflibatomic_server_tauri.so(armeabi-v7a / x86 / x86_64 as well). Phones and tablets we install on are arm64; override withcargo tauri android build --target …for an Intel emulator.