Release Notes¶
Release notes for the open source Lore project. Releases before v0.8.4 predate this file; see the GitHub releases page for the published record.
Nightly¶
Breaking changes¶
lore-server: with authorization configured, operations that change a partition check for thewritepermission. Any permission on a partition still allows reading it, but no longer allows writing. Servers without authorization configured allow every caller to read, write and administer repositorieslore-server: changing branch protection, through branch protect and unprotect or by setting theprotectkey in branch metadata, needsowneroradminon the repository, orwritefor its creator. Previously anyone with access to the repository could set the branch protection ruleslore-server: vending a presigned URL needs thepresignpermission on the repository instead of theis_service_accounttoken claim. Servers without authorization configured still allow every caller to vend- Server hooks:
Hook::pre_handleris nowasync, so a pre-handler can call other services without blocking a worker thread. Custom hooks must make theirpre_handlerasync and rebuild. A pre-handler is cancelled at its timeout (1 second, or the hook's ownpre_handler_timeout), and a timeout now fails the request withUNAVAILABLEinstead ofINTERNAL - A client connected to a
lores://remote refuses to connect, failing withNotSupported, if the server advertises a storage, revision, lock, repository, or notification endpoint whose scheme doesn't authenticate the server, such aslore://. It used to connect to that endpoint without validating its certificate, and for gRPC without TLS. A server that advertises these endpoints must give them a scheme that authenticates the server, such aslores://
Features¶
lore-server: forward the lock service'sQueryto a remote Lore server, opt-in vialock_queryunder[server.grpc_public_services.forwarded_requests.enabled_rpcs]. The peer serves it only when it has a lock store- QUIC connections to replication peers configured under
[immutable_store.composite.replica_factory]start with a 1 MiB initial congestion window, set withinitial_cwnd_bytes. Values below 2400 bytes are rejected at startup. A periodically refreshed connection starts at no less than this window lore-server: connections accepted by[server.quic_internal]start sending responses with a 1 MiB congestion window. Set it per endpoint withinitial_cwnd_bytes;[server.quic]keeps its congestion controller's default unless set. Values below 2400 bytes are rejected at startup
Fixes & Improvements¶
- A revision named by number, such as
<branch>@<number>, resolves from the first page the remote returns for it, rather than walking the branch's history back from its latest revision one page at a time - Following a branch's history through the remote no longer takes the revisions a page holds above the one it was asked for as that revision's ancestors. A page the server serves from a cached segment holds them, and finding the revision two branches share could answer with one of them, newer than the one they really share
- On Windows, a Lore service started while another serves the same socket exits without serving, as it does on Linux and macOS. It used to take the socket over and leave the first service running unreachable, holding its SWFS mounts until killed, so commands that each started a service at the same moment could leave one behind per extra command
- A branch push reads each mutable store key once, loads the branch alongside the pushed revision, and checks the branch name while it verifies the revision's fragments rather than before, so a push spends fewer store round trips before it moves the branch. A push the server refuses because the branch is protected no longer reinstates the name of a deleted branch first
- The deprecated
urc.rpcBranchPushRPC treats a missing or deleted branch aslore.revision.v1BranchPushdoes: a branch without metadata isNOT_FOUNDrather than an internal error, and a push to a deleted branch reinstates its name, or fails withALREADY_EXISTSwhen another branch holds the name, rather than failing withNOT_FOUND
v0.12.0 (Oct 26th 2026) [#1966]¶
Breaking changes¶
Features¶
lore service runruns the Lore service in the current process, returning once it stops, whether bylore service stopor bylore::service::quitfrom within the same process.lore::service::quitasks a service thatlore::service::runstarted in this process to shut down cleanly, and does nothing when none runs there. Neither has a C API equivalent: there is nolore_service_runorlore_service_quit, so they're available from Rust only.lore service stop(C API:lore_service_stop) is the way to stop a service from another process.- An OIDC login whose access token has expired is renewed with its refresh token (OAuth 2.0 refresh grant, RFC 6749
§6) on the next operation, without a new
lore auth login. The provider's rotated refresh token replaces the used one, and a refresh token the provider refuses is dropped, so the next operation asks for a new login. A client credentials login stores no refresh token lore auth logout(C API:lore_auth_logout) revokes an OIDC login's refresh token at the provider (OAuth 2.0 token revocation, RFC 7009) if the provider advertises a revocation endpoint. A failed revocation, such as at an unreachable provider, now fails the logout, after the login is removed from this machine
Fixes & Improvements¶
-
Clone and sync continue materializing available files after an individual write fails, restoring the behavior established by UCS-14675. Failures are reported and the command still exits nonzero.
-
lore file obliteratewarns before deleting content needed by an active branch’s latest revision, including shared child fragments. Incomplete checks report unknown impact; the administrative deletion still proceeds. -
lore repository verifyfails where it can't read part of the local store, where it passed over it, and checks and heals every fragment a flush in the same process moves to other index buckets meanwhile lore stageof several paths under a directory missing from the working directory stages a delete of only those paths, where it staged a delete of the whole directorylore branch switch <branch> <revision>takes a revision that names no branch, such as1234or@1234, on<branch>, where it took it on the current branch and refused it as not on the target branch--dry-runwrites nothing to the working tree.lore file reset --dry-runrestored, deleted and purged files as a reset without it does, and other commands could write files the same way--dry-runchanges no branch, revision, staged or layer state and creates no repository.lore branch create,branch reset,branch archive,revision amend,unstage,file dependency add,layer addandrepository createmade their changes with--dry-runas without it, andrepository create --force --dry-runremoved the existing repositorylore repository clone --dry-runinto an existing directory leaves nothing in it, where it left a.loredirectory.repository clone --force --dry-runover an existing repository fails instead of replacing it, andrepository clone --dry-run --vfs swfsfails instead of mounting the repositorylore shared-store create --dry-runcreates and registers no shared store, and with--forcekeeps the existing one.repository clone --dry-run --use-shared-storefails where it would create the shared store, and a dry run no longer moves a shared store's legacyglobal.tomlconfig toshared_store.toml--dry-runwrites no missing.lore/instancefile and saves no identity or remote URL to.lore/config.tomllore unstage --dry-runandreset --dry-runof a staged link addition keep the link's files, where they removed them, andlore file write --dry-runwrites no output outside the repository--dry-runchanges nothing on the remote.lore branch create --dry-runcreated the branch in linked and layer repositories and with--forcedeleted the existing branch on the remote,link addandlayer addcreated the current branch in the linked or layer repository,branch metadata setandrepository metadata setstored their values,branch merge intopushed the merge to the target branch, andcommituploaded the commit's contentlore shared-store set-use-automatically,service set-use-automaticallyandservice set-executablechange no setting with--dry-run, and the service setters say what they would setlore file obliterate --dry-runremoves no content, where it obliterated the content in the local store and on the remotelore repository gc --dry-runcollects no garbage, where it evicted and compacted the local store, andrepository verify --heal --dry-runheals nothing, reporting what it finds asrepository verifydoeslore auth login --dry-runauthenticates and stores no token, andauth logout --dry-runandauth clear --dry-runremove no token, where all three changed the token store- Processes running commands on one repository or shared store at once, such as several
lore history, no longer wait on each other forever. A command whose process still had the immutable store open from the previous command, and not the mutable store, took the mutable store's lock before the immutable store's, against a process holding the immutable store's lock and waiting for the mutable store's. Every process using the store needs this version: an older one still takes the two locks in that order - TLS connections refuse a server certificate issued through one of the eMudhra intermediate CAs whose private keys can be derived from their public keys (RUSTSEC-2026-0382), whichever built-in, operating system or custom root its chain ends in, where they accepted it. The built-in roots no longer include emSign ECC Root CA - G3, emSign Root CA - C1 or emSign ECC Root CA - C3
- TLS connections abort on a TLS 1.3 handshake message sent at the wrong encryption level, where they accepted it (RUSTSEC-2026-0285). A forged ServerHello no longer makes later connections to the same server offer only a classical key share, which a server could accept without post-quantum key exchange (RUSTSEC-2026-0349). Debug output of a client session leaves out its secrets (RUSTSEC-2026-0346)
lore stage --case keepof several paths under a directory spelled in another case than the repository's stages them as modified under the repository's case, where on a case-sensitive file system it staged them as deleted
v0.11.0 (Oct 21st 2026) [#1892]¶
Breaking changes¶
- C API: the new
LORE_EVENT_FILE_AUDITevent adds a member tolore_event_t, so C consumers must rebuild against the newlore.h [environment.endpoint] user_urlnow names alore.user.v1.UserService(UserGet,UserFind), replacing theUrcAuthApiGetUserInfoandGetUserIdcalls. Withoutuser_url, clients askauth_urloverUrcAuthApias before. Withuser_url, the server also lists repositories there (PartitionList) by default, through the new[server.auth] repository_catalog = "user_service"mode, and registers the repositories it creates and deletes there (PartitionAdd,PartitionRemove) instead of callingRebacApiatauth_url- A client and the Lore service it relays to must come from the same build: the message protocol changed (version 2), and mismatched builds refuse each other's messages. Restart the service after upgrading
- Data is verified by the trust of its source (ADR-00023):
reads from the local store no longer verify hashes, and data fetched from a remote is verified on receipt unless the
remote is trusted (
--trust-remote) or a C API reader takes fragments as stored (fragments). C API:skip_verifyonlore_storage_open_args_tis now an alias oftrust_remotefor its handle, so it skips verifying fetched data only - C API:
lore_branch_merge_start_args_tgainslink_pathsandlink_messages, changing its size; rebuild against the newlore.h. A zero-initialized struct keeps today's behavior - C API:
lore_auth_login_with_token_args_tgainsclient_idandclient_secret, changing its size; rebuild against the newlore.h. A zero-initialized struct keeps today's behavior - The server refuses to start if
[server.auth] resource_id_templateor[server.auth.oidc]resource_template/scope_templatelacks{id}, as such a template let one grant match every partition. The error names the setting
Features¶
lore file audit <path>is a new command that shows, for each line of a file, the revision that last changed it, grouped into ranges of consecutive lines. The C API offers the same query aslore_file_auditandlore_file_audit_async--trust-remote(C API:trust_remoteonlore_global_args_t) skips hash verification of data fetched from the remote, keeping only a size check, for machines inside the remote's own infrastructure where performance comes first, such as build servers. The field sits in the struct's padding, so callers must zero the whole struct, padding included, or stray bytes turn it on- C API:
fragmentsonlore_storage_get_resolved_item_tdelivers content piece by piece asLORE_EVENT_STORAGE_GET_FRAGMENT, each with itslore_fragment_tand payload as stored, instead ofLORE_EVENT_STORAGE_GET_DATA. The reader decodes and verifies the payloads itself, and can keep them compressed as stored instead of recompressing. Zero-initialized items read as before lore auth loginagainst a server with an OIDC issuer logs in with the OAuth 2.0 device authorization grant (RFC 8628), requesting the server's default scopes andoffline_access: it opens the verification URL in the browser, or prints it with--no-browser, shows the code to confirm, and stores the access and refresh tokens.--token-type <TYPE> --token <TOKEN>logs in with an existing token through RFC 8693 token exchange, passing the type to the issuer verbatim. Partition operations, repository metadata get/set and repository delete send the access token, or a per-partition token from token exchange when the server advertisesresource_templateorscope_template. A token exchange issuer on another host gets the login token only if the token'sroot_domainsclaim, or elseaud, names that host. ID tokens are never stored or sent. The refresh grant isn't implemented yetlore auth login --client-id <ID>against a server with an OIDC issuer logs in as a confidential client registered at the provider, with the OAuth 2.0 client credentials grant (RFC 6749 §4.4), for CI. The secret comes fromLORE_CLIENT_SECRET, or from--client-secret. The client authenticates with HTTP Basic, or in the form when the provider's discovery document lists onlyclient_secret_post. No scope is requested. The C API takes the same login throughclient_idandclient_secretonlore_auth_login_with_token_args_t--token,--identity-tokenand--access-tokenfall back toLORE_TOKEN,LORE_IDENTITY_TOKENandLORE_ACCESS_TOKENwhen not given. A secret passed as a flag value, these three or--client-secret, now prints a warning, since other users can read it in the process table and the shell history keeps it- The user identity is read from the token by the same rule on client and server: the
identityclaim (orhttps://lore.org/claims/identity), elsesub. An issuer whosesubisn't a usable identity maps one intoidentity, such as Entra'soid. Tokens with an empty or non-string identity are refused. The[server.auth] identity_claimsetting, and its advertisement in the environment response, are removed lore branch mergeandlore branch merge starttake--link-message <PATH> <MESSAGE>, aslore commitdoes, to give a linked repository its own message in an auto-committed merge. A path that isn't a link fails the merge before it startslore clone --offlineor--cacheprepares the clone for working offline: staging and committing then work without a connection, and the clone fails if it can't fetch what they need. The clone itself still needs the remote. Without either flag, the first stage or commit fetches that data from the remotelore service statusreports the status of the running Lore service, if anyLORE_STORE_RETAIN=1(ortrue) keeps a process's stores loaded between repository calls and afterlore_storage_close, without holding their locks, forstore_keep_alive_seconds(default 10), so calls in that window skip reloading unless another process changed the store. While it's set, don't share a store directory with processes of an earlier build: their changes go unnoticed and stale data is served. Without it,store_keep_alivekeeps stores loaded and locked for the window- Server:
[server.http] health_only = trueserves only/health_check, for deployments that don't serve repository HTTP routes lore-servermetrics: gRPC requests recordrpc.server.handler.duration, which leaves out the time a client takes to send its request and work done before the body is read, such as authorization, unlikerpc.server.durationandhttp.server.request.duration(bidirectional streams record the same value in all three).BranchPushrecords its fragment verification load per push underlore.branch_push(fragment_lookups,peak_fragment_lookups_in_flight,collected_addresses,concurrent_query_batches), with the same counts in debug logscontrib/bazel/: a Bazel remote execution server on Lore storage, as a standalone workspace built from within that directory: a Remote Execution API v2 endpoint and scheduler keeping the action cache and content-addressable storage in Lore partitions, executors that share content through a loreserver, and a script that builds, runs and stops them
Fixes & Improvements¶
- Working tree:
lore sync --resetreplaces a directory with a file at the same path instead of failing with "Is a directory". Syncs, branch switches, resets, merges and view changes sendLORE_EVENT_REVISION_SYNC_FILEfor each deleted file and directory, so callers tracking the working tree, such as editors, no longer keep deleted paths. Files and directories named.urcor.lore, in any case, are ignored by every command that reads the tree, including the virtual filesystem lore commitandlore clonereport the error that stopped them instead ofRecursion task failed. A failed clone no longer printsClone complete, and the C API sendsLORE_EVENT_REPOSITORY_CLONE_ENDonly on success;LORE_EVENT_COMPLETEreports the outcome either waylore status:--scangiven many paths is faster, scanning them in parallel instead of one after another. Files are listed in path order, stable from run to run.lore statusandlore stage <directory>without--scanno longer miss a modified file after a scan that dropped a reverted add. A file saved again at the same size right after a command read it no longer shows as unchanged- Merges of divergent history, such as a sync when the local and remote branch both have new revisions, or a diverged
link in a same-branch
lore branch merge:lore file historyattributes the remote's changes to the right files, a file both sides added keeps the remote's addition, and earlier remote changes are no longer lost. Committing such a merge no longer fails in some cases where the remote side added files, andlore file historylists files added locally before it. The merge keeps the remote's latest revision as its first parent when anything is changed on top before committing, including underlore commit --force, and once committedlore statusreports the branch ahead of the remote instead of diverged - Layers:
lore commitwith changes staged in a layer commits the layer even when the parent tree holds only unstaged dirty changes, which stay dirty, instead of failing withNothing staged for commit. Alore status --scanthat finds a parent tree change while only a layer is staged no longer makes the next commit fail withStaged state has stale parentor reuse the current revision's metadata.lore file dependency add/remove,lore file metadata clearwith nothing staged, andlore branch merge abort --ignore-linksno longer copy the current revision's metadata, such as its reviewer, onto the next revision - Merge conflicts: a merge no longer drops one branch's change to a file without reporting a conflict, in some cases
where the other branch added that file after another was deleted. Resolving conflicts to one side
(
lore branch merge resolve mine/theirs, and the cherry-pick and revert resolves) no longer uses memory in proportion to the size of the tree - Links: committing through a link pinned at a merge revision no longer records a spurious merge in the linked
repository, and staging changes inside a nested link more than once no longer pushes the intermediate staged states as
revisions.
lore commit --link-messageaccepts any spelling of the link path, such as./vendor/lib/; a C API commit naming a path that isn't a link fails as invalid arguments, and a message for a link with nothing staged is ignored instead of refused lore dirtyinside a link, or on its mount, marks the change in the linked repository, where it recorded nothing, solore statusreports it without a scan and staging picks it up. The marks survive commits and branch switches,lore status --check-dirtysaves what it verifies inside links, and a link holding only dirty marks is committed at the revision it's pinned to.lore file dirty moveandcopyrefuse paths inside a link- Lore service: commands it runs use the caller's configuration and credentials (
LORE_GLOBAL_PATH,LORE_AUTH_PATH) instead of the service's.lore_storage_put,lore_storage_put_resolved,lore_storage_getandlore_storage_get_resolvednow work through the service. A shared store or instance it can't read at startup is logged and skipped, instead of leaving no SWFS instance mounted or creatable. On Linux and macOS, two services started at once no longer both run, so the commands that started them reach the same service - Auth: the CLI sends tokens only to hosts named in their
root_domains,https://lore.org/claims/root_domainsoraudclaims, and no longer to theisshost except for token exchange. With[server.auth] resource_claimset, a repository lookup by name or id answers any verified caller with the repository's id, name and metadata hash, solore repository clone,lore link add,lore layer add,lore repository infoandlore repository deleteno longer fail as not found before the caller holds a grant; reading metadata or contents still needs one. Servers connect to aucs-auth://auth_urlover TLS, as clients do - Server write checks: pushing to a protected branch requires the
push-protectedaction, and theis_service_accounttoken claim no longer bypasses protection, so grantpush-protectedto mirroring accounts first. The genericMutableStoreandMutableCompareAndSwaprequests, including C APIlore_storagecalls withremoteset and servers withmutable_store.mode = "remote", can no longer write repository metadata, branch metadata or branch latest pointers, which skipped the server's checks; useRepositoryMetadataSet,BranchMetadataSetandBranchPush. The refusal isInvalidArgumentover gRPC andFailedover QUIC. C API calls on a handle's local store are unchanged RevisionList: listing from a revision above the branch's latest returns no forward cursor instead of crashing the server, and paging forward through older history on a deep branch no longer times out withCancelled- Pushing and committing: some large files, depending on how they split into fragments, no longer fail to commit or
push. A
lore pushretried after an interrupted push, and a gRPC push copying the same content to several files, no longer fail on every retry with a missing fragment - Verification: caches, prefetches and
lore repository store immutable queryverify fetched data unless the remote is trusted, and data that fails is fetched again once after the server is asked to repair it, then neither returned nor stored (the query logs it and skips it). Corrupt local data is fetched again and replaced instead of crashing an unverified read, and a corrupt local root is replaced once instead of fetched again on every read. A fragment list naming itself or an ancestor fails caching, and local stores refuse to store fragments with inconsistent flags or sizes. Pushing content held in the local store no longer decodes and re-hashes it first - Reads: streaming reads and reads into files enforce the content size limit as other reads do, and oversized remote content is refused before it's stored locally. A corrupt fragment list with out-of-order offsets fails the read instead of returning the wrong data. Reads of fragmented content do less redundant work: one local lookup instead of two on a miss with no remote, a busy local store retried before going to the remote, and no extra copy of each piece
- Fetching: concurrent reads of the same missing content, including identical content in different files, download it
once, and a read of content being stored meanwhile waits and reads it locally. Caching runs at most 512 fetches at
once per remote connection (
LORE_CACHE_GETSsets another limit,0none), so reads a command needs right away don't queue behind a long cache. Caching shares downloads with reads, fetches what a local fragment list is missing and replaces a corrupt local copy, and what it caches can be evicted from the client store like other fetched data - Servers using a
remotestore verify data they fetch from the backend, not only its size, and ask the backend to repair data that fails. Data in a codec the server is built without, such as Oodle, is refused instead of passed on unchecked. The server's HTTP content and presigned redeem endpoints treat data that still fails, or an unreachable backend, as missing. Servers also size-check data from replicas and replicating peers, and no longer crash on a truncated replica response - Oodle: the local store's Oodle migration finishes sooner, leaving Oodle data the remote also holds in place. A build
without Oodle that finds such data replaces it with the remote's re-encoded copy, and reports
NotSupportedinstead of missing data where there's no remote copy - Commands start faster, as opening a repository lists each store's index once instead of checking for every possible
index group, twice.
lore repository createsyncs to disk only with--sync-data, as other commands do - Fix a deadlock between processes using one repository, such as the Lore service and a storage handle. Stores are now locked only while in use, so an idle handle or kept-alive store no longer blocks other processes or holds its files open, and background work, such as the flush when a handle closes, no longer leaves other operations on the store running without its lock
- Local stores no longer lose data after transient I/O errors: a store directory or file that can't be read, as after an
I/O error or running out of file descriptors, fails the command for a later retry, where it was treated as never
written and index entries and local-only data were discarded. A listing of the local mutable store fails instead of
missing entries, and after failing to open a new data file, the next write retries instead of every write failing with
No more packfiles - A listing of the local mutable store no longer misses entries that a flush in the same process moves to other index buckets meanwhile
- A local store whose
versionfile can't be read fails to open, where it was marked for migration again on every open, making every process using it reload it at each use. A store affected by this recovers at its next command connected to the remote; until then, commands without a connection fail with "Unable to upgrade local store to current client, need a connection to server" - Local store flushes: entries a flush reported written are no longer lost when a concurrent index write fails, the process stops mid-write, or a mutable store's background flush fails. Entries removed by obliterate, eviction or compaction no longer come back after a reopen, notably on servers, serving another file's data. An entry a machine failure left pointing past the end of its data is detected and fetched again instead of reading as other content, and a data file whose sync fails takes no more writes
- Background index flushes: local immutable stores schedule one after every change, including marking content durable and copying it to another partition, and the Lore service's disk stores flush 30 seconds after any write, including content it fetched (non-durable writes previously flushed after 5). A crash before the next explicit flush lost these changes
- Local stores: a flush or obliterate cancelled part way, as when a command stops waiting for it, no longer loses entries or lists a key twice after a reopen, and no longer leaves the store locked until its next use. A cancelled write no longer causes needless reloads or a "No such file or directory" error on the next write
- Local stores: an index reorganization that fails or is interrupted no longer serves other content's data, brings back obliterated content, makes most entries unfindable after a reopen, or leaves temporary files behind. One that failed late is completed by the next flush
- Obliterate in local stores: the obliteration is saved to disk before the data is zeroed and waits for reads in
progress, so a crash or concurrent read no longer sees zeroed or partly zeroed data, and a failed obliterate leaves
the content intact. Obliterated content no longer comes back empty when stored again under another context or
re-encoded by the Oodle migration, and is restored only by storing it again with its payload. Obliterating content
only partly held locally fails instead of leaving some of it readable.
LORE_EVENT_STORAGE_GET_FRAGMENTflags report correctly whether the local store holds the payload - Compaction does far less disk work: it rewrites a data file only once enough of its space has gone unused, copies each
payload once, and no longer stalls server stores for minutes by rewriting the whole index for each file. Client stores
count local-only data toward
max_sizeandmax_capacitybut never evict it.lore repository gcloads the full index before evicting and no longer swapsmax_size(compaction) andmax_capacity(eviction) - Storage sessions: a process making many storage calls to one remote no longer has every later call refused until it restarts, as handles reuse one server session per partition instead of leaving one open per call, and failed or cancelled connection setups no longer leave sessions open. When the server refuses a storage session as unauthorized, as after revoked grants, the client discards its cached authorization and retries once with a fresh one, where every storage call failed until it expired, in the Lore service even after logging in again
- Reconnects: storage handles and servers' remote stores reconnect after a failed connection instead of failing every
call until reopened or restarted. A failed reconnect is retried with a backoff of up to 30 seconds, calls failing at
once meanwhile, though a credential refusal doesn't start the backoff. After a QUIC reconnect, commands no longer fail
on sessions from the old connection, and a stream that stopped carrying requests reconnects instead of failing or
hanging every later request on it. A request cancelled while being sent no longer corrupts the next request on its
stream. Reads and uploads use all the connections
--max-connectionsallows instead of one - QUIC: the vendored
quinn-protomoves to 0.11.19, fixing CVE-2026-25800 (remote memory exhaustion, no credentials needed) and other upstream memory-exhaustion, stream-limit and Retry-token issues, a crash a peer could trigger with its ACK settings, and path MTU detection that dropped bulk transfers to the minimum packet size - QUIC throughput: congestion control no longer over-grows while flow control holds the sender back, or loses throughput
for the rest of a connection after one low round-trip sample, and a complete message no longer waits behind an earlier
incomplete one on the same stream. The client asks for a larger UDP receive buffer; on Linux, set
net.core.rmem_maxto524288or more to allow it - C API: a call reading into
data_outthat stops part way no longer writes the caller's buffer after the call completes.data_outmust stay valid until completion, andlore_shutdownis safe only once every call has completed, aslore.hnow documents, correcting its earlier claim that argument memory could be freed once an asynchronous call returned lore-server: server-to-server QUIC traffic uses Cubic congestion control throughout. The internal QUIC server, the replicated immutable store's client and replication clients to peers in every region now use Cubic, where the server and cross-region clients used BBR. Cubic only shrinks the congestion window on packet loss, so a connection that sits quiet between bursts keeps its window rather than having it drained. The public QUIC server keeps BBR
v0.10.1 (Oct 5th 2026) [#1475]¶
Breaking changes¶
lore.thin_client.v1:DiffChange.content_from/content_toandContentDiffRequest.address_from/address_to/address_basebecome alore.model.v1.Address, the formTreeNode.addressalready carried, and move to field numbers 10 and 11, and 8, 9 and 10; the hash-onlybytesfields they replace (5 and 6, and 1 through 3) are reserved. A committed file's content resolves under a context generated per file, so a consumer keying content and metadata lookups onhash-contextfound nothing for any file in a diff, and had nowhere to put the context when askingContentDifffor the file's text. A side that does not exist reports the field unset: an ADD has no from side and a DELETE no to side. A link pin change addresses each of its revisions under the linked repository. A consumer built against the old fields reads the new ones as absent rather than as a hash, so rebuild against the new bindings- C API: every storage
*_ITEM_COMPLETEevent replaceserror_codewith anerrordetail carrying the failure's own FFI code, message and trace, asCompletealready did, and the call'sstatusbecomes the dominant item failure's code. Re-check any branch on a per-item code: a missing payload reportsPayloadNotFound(81) rather thanAddressNotFound(80), a buffer short of the content reportsOversized(118) rather thanInvalidArguments(3), and most failures previously reported asInternalnow report their own code. The structs grow and are no longer trivially copyable, so copy the detail's strings before the callback returns and rebuild against the newlore.h. The revision-tree per-item events are unchanged: they still carryerror_codeas alore_error_code_t, so the five-value folding still applies to them, and moving them to a detail will need a second rebuild in a later release - C API: an empty
lore_string_tthe library emits now carries a NULLstringpointer rather than a pointer to a zero-length NUL-terminated buffer, which is whatlore_string_thas always documented and what an emptylore_array_talready answers. A consumer that readstringwithout first checkinglength—strlen(s.string),printf("%s", s.string)— must checklength, or treat NULL as the empty string. This applies to every empty string on every event and every verb, not one field - C API:
lore_revision_sync_args_tappendsview, a path to a view filter file, empty to keep the view the instance holds. Rebuild against the newlore.h - Build: the C library is built by the new
lore-capipackage.cargo build -p lorebuilds only the Rust library; build-p lore-capiforliblore.soandliblore.a(liblore.dylib, orlore.dllandlore.libon Windows) andlore.h, under the same names and paths as before. Building the CLI no longer compiles and links the C library - Build: the build version is no longer compiled in from
LORE_BUILD_VERSION_NAME, and a build no longer runslore revision infoto name itself. Every build reports<package version>+localuntil the newlore-stamptool writes a build name into the finished binaries and libraries, which then report<package version>+<name>(seeCONTRIBUTING.md). A pipeline that setsLORE_BUILD_VERSION_NAMEmust runlore-stamp --build <name>after the build and before signing instead. A changing build number no longer invalidates the build cache for every crate lore-server: a[server.auth]without[environment.endpoint] auth_url, which authorizes from token claims, refuses to start without[server.auth.oidc], since clients have no other way to log in. Add the table with the provider's publicclient_id. Tier 2 (resource_claimset) also requiresresource_templateorscope_templatein it, and Tier 1 refuses bothlore-server: the server-to-server replication protocol movesImmutableCopyto opcode 21, its request carrying a byte of behavior flags where it carried a durability byte alone. Opcode 19 is left unused, so a server on one side of the change answers a peer on the other withUnknownOpcoderather than reading the request under the wrong shape. Copies between servers of different builds fail while a deployment holds both, so upgrade the servers of a replication set together. The client-facing storage protocol is unchanged
Features¶
- C API:
lore_revision_cherry_pickandlore_revision_cherry_pick_asynccherry-pick a revision onto the current branch, aslore revision cherry-pickdoes - C API:
lore_branch_latest_listandlore_branch_latest_list_asynclist the revisions a branch LATEST has held, most recent first, and relay to the Lore service when one is in use.lore branch latest listtakes the same path, so with the service in use it no longer waits indefinitely on the local store the service holds lore sync --view <file>changes the view filter an instance materializes its working files under, carrying the working tree to what the new view holds rather than re-cloninglore-server: a copy the durable store accepts is read back and recorded, with its content, in the local store and at the write replicas, so a later read of the destination is answerable without a round trip to the durable store. The read and the writes are detached, so they do not hold up the copy.[immutable_store.composite] record_copy_out_of_band = falseturns it off for a deployment where the extra read and write per copy is not worth that, leaving the durable store the only holder of the destinationlore-server: the disk space available to the local stores is checked on a timer and a warning is logged once it falls below a threshold.[server.local_store_monitor]carriescheck_interval_seconds(default 30) andlow_space_threshold_bytes(default 10 GiB); an interval of 0 turns the check off. Every local store the server writes at is watched, the immutable store and the mutable store among them, and the reading is taken per volume: stores sharing a filesystem draw one warning naming them all, rather than one warning each. A server whose stores are not local is not checkedlore file reset --last-merged-from <branch>takes--mineor--theirs, restoring the version the reset branch held going into that merge or the version the named branch brought in, rather than the version the merge's conflicts were resolved with. A merge revision holds the resolved content, so the side that lost a resolution was otherwise unreachable; where no merge from the branch is found the branch point is restored as before, whichever flag is given. C API:lore_file_reset_to_last_merged_args_tappends amerge_side(0= resolved,1= self,2= other) in the struct's tail padding, so its size and its other fields' offsets are unchanged and a zero-initialized struct keeps today's behavior[environment.endpoint] user_urladvertises a user directory that's decoupled from the auth service. Clients resolve user IDs to display names using this service'sGetUserInfoandGetUserIdoperations. For backward compatibility,auth_urlis used as the user service URL, ifuser_urlis not defined. Client-side, the lookup moves off theAuthenticationtrait onto aUserServicetraitlore-server:lore repository listanswers through aRepositoryCatalogimplementation. The implementation is selected based on server configuration. AUrcAuthApideployment asksLookupUserPermissionsas before, a server with no[server.auth]lists everything it holds. A deployment authorizing from token claims uses a catalog implementation basedrepository_catalogandrepository_catalog_urlconfiguration.- C API:
lore_repository_deleteandlore_repository_delete_asyncdelete a remote repository, and are carried out by the Lore service when one is in use.lore repository deleteis relayed the same way, where it previously always ran in the calling process lore-proto: introduceslore.user.v1.UserService, the user directory in Lore's own terms:UserGetresolves user IDs to users,UserFindresolves a name to a user, andPartitionListstreams the partitions the caller is allowed to see. These take over whatGetUserInfo,GetUserIdandLookupUserPermissionsdid inUrcAuthApi. Only the pure OIDC-compliant authn/authz operations are left inUrcAuthApiso that we can use OIDC authorization as a drop-in replacement for the Auth API.lore-server: the new[server.auth.oidc]table configures OIDC info shown to clients, through the newoidcfield oflore.environment.v1.Environmentlore-server:[server.auth] jwt_typlists the accepted JWTtypheader values, as a string or an array. When set, a token whose header carries notyp, or atypthat doesn't match any of the entries, is rejected. If unset, the tokentypvalue is not checked, as before- C API:
lore_revision_bisectandlore_revision_bisect_asynctake one step of a bisect between two revisions, synchronizing the working directory to the revision halfway between them, and relay to the Lore service when one is in use.lore revision bisecttakes the same path, so with the service in use it no longer waits indefinitely on the local store the service holds - C API:
lore_shared_store_listandlore_shared_store_list_asynclist the registered shared stores and, when asked, the instances using each, aslore shared-store listdoes
Fixes & Improvements¶
lore link list --staged, and an interactivelore commitlisting the links it asks a message for, run in the Lore service when one is in use, as other commands do, rather than opening the repository in the client, where they waited on the lock of a service holding it. C API:lore_link_list_stagedandlore_link_list_staged_asyncexpose the listinglore commitstops with the failure of listing the links or layers its per-link and per-layer messages are resolved against, such as a Lore service in use that cannot be reached, where it rejected a--link-messageor--layer-messagepath as matching nothing, or committed without asking for those messageslore repository instance pruneno longer removes an SWFS instance when run without the Lore service, where the instance is not mounted and its path is absent or an empty directory. Its external.lorein the global data directory shows it still exists, solore repository instance listreports it as live and prune keeps its branch and revision anchors- Fix resolving another user's name failing against an auth service reached by IP address over
http://orhttps:// - A
lore_string_torlore_binary_ta caller passes in with a NULL pointer and a non-zerolengthreads as empty rather than as undefined behaviour, which is what an emptylore_array_talready did. The library answers NULL for every empty string it emits, so one can come back in an argument struct beside a length the caller kept itself, and the pointer alone decides whether there are bytes to read lore-server: every local store the configuration asks for is reported at startup, composite tiers included. A store the configuration names no path for is reported as unconfigured, wherever the generated fallback points, and a store inside a system temporary directory is reported as ephemeral whether it was configured there or generated there; the two are independent, since either can hold without the other. A local immutable tier naming a path other than the first local tier's is reported as unused instead, every local tier being handed the store the first one creates. The ephemeral report previously fired on a path being absent rather than on where the resolved path pointed, so the shippedlocal.toml, which sets/tmp/lore-serverexplicitly, drew nothinglore-server: logs default towarnrather thanerror, so a server started withoutRUST_LOGreports the conditions an operator has to act on.RUST_LOGstill wins where it is set, andRUST_LOG=errorrestores the previous output- Fix a moved file being deleted when the change that moves it is realized over a working tree that already holds it,
which is what re-running an interrupted
lore syncdoes. The rename finds nothing at the source it was already carried from, and the recovery that follows removed the destination and then skipped rewriting it, because whether the content was in place was inferred from the view filter rather than read from the rename. It is now read from the rename, so a move whose source is not there is written from the store, and one the rename carried is left alone - Bound how many subtree tasks the walk between two revisions runs at once, which
lore sync,lore statusandlore diffall drive. It previously ran one task per directory it descends into, all of them live at the same time, so peak memory followed the size of the tree rather than the work in flight; a subtree that finds no room in the budget is now queued for the task that found it to walk, so neither the tasks a walk holds nor the stack it stands on grows with the tree - Fix a command carried out by the service being answered without part of its output. A relayed call returned its result
while its events were still being delivered to the caller's callback, so
lore statuscould report a repository with no staged changes. The events are now delivered before the call returns - A call relayed to a Lore service that cannot be reached now reports its status and error detail on the
Completeevent, as a failing command does, rather than on a legacyErrorevent, so a caller of an_asyncentry point receives it - Fix a relative
--repositoryresolving against the Lore service's working directory rather than the caller's for a command carried out by the service.lore repository info,lore repository delete,lore auth info,lore auth loginandlore auth logoutreported no repository or no remote where the path named one. The service now resolves the path against the directory the command was run in - Ignore and view filter files are read as UTF-8, and as UTF-16 of either byte order with a byte-order mark or, where the rules are mostly ASCII, without one. UTF-32, truncated UTF-16, and mark-less UTF-16 that cannot be detected are refused rather than read as UTF-8 into rules carrying NULs, so UTF-16 whose rules lie outside ASCII requires a mark
- Fix the wrong files being removed when a directory leaves the working tree. Where the directory's node could not be read, the removal was decided from the incoming revision's tree and filter rather than the ones the working tree was materialized under, so it could name files that were never on disk and leave behind files that were
- A change to a file's executable bit alone is now reported by
lore status --scan, taken bylore stage, and recorded on the revisionlore commitproduces. A chmod moves neither the content, the size nor the modification time, which were all the comparison read, so the change was invisible from the moment it was made lore syncandlore branch mergekeep a locally changed executable bit on a file whose content they carry, rather than silently reverting it to the revision's. The bit stands as a local modification and blocks nothing;--resetand--forceapply the revision's bit as before- Fix
lore stageover a large tree failing withgrab_node_unused returned INVALID on a freshly-allocated block. A freshly allocated block of node slots was published for every other thread to grab from before the thread that allocated it took its own, so under enough concurrency the block could be emptied out from under it. It takes its slot before it publishes the block lore sync <revision>advances the branch latest to a revision standing ahead of the one the branch holds, the remote answering for it. It was left where it stood, so a sync to the remote's tip drew alore statusreporting the branch behind the remote and a second sync that did not see it was already there. A named revision moves the latest forward only,--localand--dry-runmove it not at all, and a divergent branch keeps the one it has- Fix a moved file being left behind at the path it came from where the move crosses a mount boundary inside the working tree. No rename carries a file between filesystems, and the recovery read a destination that was not there yet as a failure of its own, so the file was written at its new path from the store and the old one never removed. A move no rename can carry now copies the content and removes the source, and a directory is carried a child at a time
- Fix a branch that replaced a committed folder with a link being invisible to a merge, which silently deleted the link
and dropped its content. A directory paired with a link is the type change it is:
lore branch diffreports the replacement, and a merge that changed anything below the mount conflicts at the mount - Changes to configuring the Lore service: The service executable field can be left unset if the service was started with lore service run separately. There is no longer a fallback that attempts to use the current binary as the service binary.
lore-server: permission checks are refused withPERMISSION_DENIEDwhen access is checked against the grpc auth service, rather thanINTERNAL- Fix
lore pushslowing quadratically with the number of entries in a changed directory. The push and its server-side verification pair entries in a single pass over name-sorted lists - Fix
lore stagestaging a delete for a removed file or directory that was never committed, whichlore statusreported asDand, afterlore unstage, as an untrackedA.lore syncno longer refuses to run afterlore file dirtydrops such a file - Fix
lore stage --scanandlore status --scanreading a link mount the working tree does not hold as a deletion of the link, which a commit then dropped from the branch. A mount stays absent while its linked repository cannot be read, and is kept along with the directories on its path
v0.10.0 (Sep 17th 2026) [#1170]¶
Breaking changes¶
- Partial hash revision identifiers are refused as
NotSupported. A revision is named by its whole 64-character hash signature, by[branch]@<number>, by[branch]@LATESTor by<branch>@<hash>. Three consequences:- The
@is optional, a target given without it applying to the branch the instance is on:lore sync 42islore sync @42,lore sync LATESTislore sync @LATEST. Digits alone are a revision number at every length but 64, where they are a signature - A revision identifies the branch it was created on, and a branch point can name the child branch instead, which
<branch>@<hash>,[branch]@<number>and[branch]@LATESTall do.lore sync,lore clone --revision,lore history,lore link update --pinandlore link add --disable-branching --pinfollow it, and a sync moves onto that branch together with its layers --search-limitboundslore revision bisectand layer revision matching alone
- The
lore-server: asignaturefield that is not a whole hash is answeredFAILED_PRECONDITIONonRevisionInfo,RevisionTree,RevisionDiffandRevisionList. An unset (empty) field is unaffected- C API:
lore_revision_resolve_event_data_treplaces itsrevisionstring with atargetnaming what is resolved (NUMBER,LATESTorSIGNATURE) and arevisionhash carrying the signature forSIGNATURE, and reports every form resolved on a branch rather than the numbered one alone. Rebuild against the newlore.h - C API:
LoreGlobalArgs.no_atimeis removed; it gated a behavior that was never wired up. Rebuild against the newlore.h; a caller that zero-initializes the struct needs no other change - C API:
lore_auth_local_user_info_args_trenameswith_tokentowith_identity_tokenand appendswith_access_token, which emits the repository's authorization (access) token as anAuthIdentityevent. The struct's size and the surviving fields' offsets are unchanged — the new flag occupies tail padding lore-server:connection_message_limitunder[server.quic]/[server.quic_internal]is renamedstream_message_limitand applies per stream rather than per connection, so withmax_bidi_streams = 8a value of 500 allows 4000 requests in flight per connection instead of 500lore-transport:user_agent()moves fromlore_transport::grpcto the crate root, andset_user_agent()is replaced byset_fallback_user_agent_product(), which supplies the productLORE_USER_AGENT_PRODUCToverrides. The fallback has to be supplied before anything opens a transport connection, since the product resolves on first read and is fixed from then onlore-server:[server.auth]refuses to start without bothjwt_issuerandjwt_audience, andauth_urlwithout a[server.auth]block is refused too; both previously started a server that verified no token at all.lock_service.max_encoding_message_sizemoves underlock_service.generallore-storage: Oodle is refused for new fragments asNotSupported, and an Oodle environment compression mode is remapped to Zstd with a warning. Content already stored under Oodle still readslore repository createno longer expands a bare name fromLORE_REMOTE_URL: a name with no host needs--offline/--local.lore link addresolves a bare name against the current repository's own remote- C API:
LoreSharedStoreListItemEventDatais renamedLoreSharedStoreListItem, the_event_datasuffix being reserved for real event types. Rebuild against the newlore.h - C API: a call made after
lore_shutdownreports the newShutDowncode (193) rather than hanging, and a secondlore_shutdownreports an error - C API:
lore_storage_open_args_tappendsskip_verify, taking the struct from 64 to 72 bytes, andlore_storage_get_t/lore_storage_get_resolved_tappenddata_out. Rebuild against the newlore.h; a zero-initialized struct keeps today's behavior
Features¶
lore auth info --with-access-tokenprints the repository-scoped authorization (access) token for the current user, the output-side sibling of the global--identity-token/--access-tokeninput flags.auth info --with-tokenis renamed--with-identity-token, with the old spelling kept as an aliaslore commit --stats/lore push --statsreport files by the action each was staged with, and fragments by what became of them — deduplicated, compressed, written to the local store, copied as an association by the peer or uploaded — through theRevisionCommitStatsandBranchPushStatsevents, on the failing path as on the succeeding one.--stats=2adds the per-fragmentFragmentWritestream;--event-interval <milliseconds>paces the progress eventslore-storage: the local store records each entry's last access, so eviction and compaction rank by least recently accessed rather than by write time. A stamp dirties a bucket only when the recorded time moves by an hour or more, so a burst of reads costs at most one index write per bucket; the stamp advances regardless, so a smaller move rides to disk with whatever writes the bucket next, and dirtying schedules no flush of its ownlore-server: request admission is per QUIC stream rather than per connection, so a saturated stream no longer stalls the others. A connection-wide ceiling answersSlowDownimmediately once requests in handling — those still waiting for a stream permit included — reachconnection_inflight_limit, and a permit wait is bounded bypermit_timeout_ms(default 100ms) rather than by the request deadline. The two refusal paths report separateAdmissionLimitandPermitTimeoutmetric labelslore:lore_storage_get_file_resolved/lore_storage_put_file_resolvedare the file-backed pair oflore_storage_get_resolved/lore_storage_put_resolved, streaming a fragment at a time so peak memory follows the fragment rather than the content. Publishing costs one round trip at any size, and a tree that did not reach the remote whole leaves its key unpublished;lore_storage_put_resolvedgets that saving too.offset/lengthselect a range as they do forlore_storage_get_filelore-server: forwardRepositoryGetto a remote Lore server, opt-in viarepository_getunder[server.grpc_public_services.forwarded_requests.enabled_rpcs]- QUIC clients announce a user agent through a
ClientIdentifymessage — opcode 14 onlore-storage/0.4, opcode 20 on the replication protocol — sent on connect and on each reconnect and stamped on theuser_agentfield of every per-request span. The string comes fromLORE_USER_AGENT, defaults to the product and library version (lore-transport/<version>unlessLORE_USER_AGENT_PRODUCTor a supplied fallback names another product), and is normalized through theuser_agent_patternsallow-list: an agent matching no pattern records<unknown>and is sampled, a connection that announced nothing records<no_user_agent>. An empty, oversized or non-ASCII value is discarded rather than rejected - A child directory holding its own
.lore/is a nested repository and bounds every walk of the parent's working tree:status,diffandstagedo not index its contents. Naming one onlore stage, or a file inside one, is refused; a directory the current revision holds stays tracked lore-server: every public gRPC service takes anenabledflag under[server.grpc_public_services], so one binary can serve a restricted subset — a read-onlyThinClientServicedeployment among them.lock_store.mode = "none"builds no lock store, andlore-server/config/thin.example.tomlis a worked examplelore-server: authorization moves onto OIDC and OAuth 2.0, designed indocs/proposals/2026-08-20-oidc-oauth2-authentication.md.[server.auth]gainspermission_claim,resource_claim,identity_claim,resource_id_template,resource_wildcardandbaseline_access;jwt_issuertakes a list, so an issuer can change itsisswithout an outage; the JWKS URI comes from OIDC discovery unless[server.auth.jwk].endpointoverrides it; andenv,name,preferred_usernameandidpbecome optional, so a stock provider's token parses. The authorizer is chosen from the configuration, and the defaults match today's behaviorlore repository createunder--offline/--localtakes no URL, naming the repository after the current directory, and a command against a remote-less repository reportsNoRemoterather than an internal faultlore shared-store listreports the registry of shared stores Lore now keeps, with the repositories each store backs- Partial support for SWFS, a virtual filesystem implementation: cloning a repository only, gated behind the
swfsfeature, and requiring the not-yet publicly available SWFS drivers lore:lore_set_compression_modeandlore_set_compression_levelchoose how stored payloads are compressed, for a caller whose data is already compressed.LORE_COMPRESSION_LEVELstill wins, and the first write fixes the levellore:skip_verifyonlore_storage_open_args_tturns off per-read payload re-hashing on a storage handle, for a caller that assures integrity at a higher layerlore:data_outonlore_storage_get_tandlore_storage_get_resolved_treads content into a buffer the caller already owns, saving an allocation and a copy. The read then emits onlyGET_HEADERandGET_ITEM_COMPLETE, ignoresstreaming, and fails an item over the stated capacity rather than truncating itlore branch diffreports the branches and revisions it resolved on theBranchDiffBeginevent, which the CLI prints from, rather than only in a log linelore revision syncwarns when the remote is reachable but its revision could not be read, instead of silently syncing against local history:LoreRevisionSyncTargetEventDatagainsremote_availableandremote_authorizedloreservercontainer images are published from each GitHub release's own binaries toghcr.io/epicgames/lore/loreserver, signed with cosign.X.Y.Z,X.Yandlatestarelinux/amd64; the-gravitontags arelinux/arm64tuned for Graviton3 and newer. SeeDOCKER.mdaarch64-unknown-linux-gnubuilds portable by default rather than always tuning forneoverse-512tvb, which SIGILLs on older arm64. Opt in with--config .cargo/neoverse-512tvb.toml --features lore-base/neoverse-512tvb
Fixes & Improvements¶
lore syncandlore cloneretain the target revision's state and tree records in the local cache, solore --offline statusworks after moving to a revision whose records lack the local-cache-priority flag- Fix an empty array crossing the C API allocating a block that was never freed, so a long-lived client leaked one per
event it received — an empty
trace_locationson the error detail of a successful outcome being the common case. The zero-sized allocation was also undefined behaviour - Fix revisions committed with
lore commit --offlinebeing unreadable from every other clone once they were pushed, where a clone reported a revision's signature and parent but no branch, date or message andlore revision metadata getansweredAddress not found. Push now walks the second parent of a merge revision, oldest first, uploading what each revision owns without offering it as a new latest revision; the walk stops at the peer's latest for that branch, or at the branch point for a branch the peer has never seen, and a revision on that line that is itself a merge is followed the same way. The server collects a merge against both parents rather than only the first, so a push missing the tip of the merged line is refused rather than accepted silently lore revision infoandlore historywarn when a revision names metadata they cannot read and report it without the branch, date and message that blob carries, rather than reporting empty fields with no indication anything was missing (revision info) or failing outright (history). A revision read offline out of a cached revision list has nothing to take those fields from, which is ordinary rather than damage- Fix
lore revision metadata set --binaryleaving its payload on the machine that set it, where every other clone resolved the recorded address toAddress not found. Push now collects the fragments an address-typed value names, as it already did for file metadata, and the server verifies them with the revision's other new fragments - Fix
lore repository instance listcarrying several entries for one root directory. Registering an instance retires any earlier registration at the same path. A.lore/instancenaming another instance is reportedsuperseded(stale = 2in theRepositoryInstanceevent) and retired by any command that reads the list; a path that has gone (stale = 1) or holds no.lore/instance(stale = 3, printedno checkout) is reported but left forlore repository instance prune; an instance file that exists but cannot be read leaves the registration alone entirely, since only positive evidence makes a registration stale. Stale entries never raise the warning that another instance holds the branch, and a lost.lore/instanceis recovered from the newest registration for the path lore-server: a forwarded RPC whose peer is unreachable now answers with the origin's ownINTERNALstatus and logs the transport failure, instead of passing tonic'sUNAVAILABLE/tcp connect errorthrough. AffectsBranchCreate,BranchDelete,BranchGet,BranchList,RepositoryCreateandRepositoryGetlore-server: the channel a forwarded RPC travels over now sends HTTP/2 keep-alive pings while idle and bounds its connect and each request, so a silently dropped connection is redialled and a peer that never answers no longer holds the forwarding handler. Tuned byconnect_timeout_seconds,request_timeout_seconds,tcp_keepalive_seconds,http2_keepalive_interval_secondsandhttp2_keepalive_timeout_secondsunder[server.grpc_public_services.forwarded_requests.client], all optionallore:lore_storage_get_fileno longer creates, truncates and replaces the destination whenoffsetstarts past the end of the content; the target is left alone and the call reportsINVALID_ARGUMENTS. A start exactly at the end is still a legitimate empty read and writes the empty filelore:lore_storage_put_fileandlore_storage_put_file_resolvedreject a missing or non-regularpathasINVALID_ARGUMENTSon the first open rather than after the ten-second transient-failure back-off, so a directory can no longer retract a published key by reporting zero sizelore-storage: a write that waits on another task already storing the same address reports the placement the store settled at, so content whose fragment tree repeats a leaf — a file of identical blocks — is no longer reported as partly absent from the remote, and a key naming it is published instead of withheldlore-storage: a stopped garbage collection pass gives up within one packfile instead of after a whole compaction step, and the stop at the end of every repository command is gone, so background eviction and compaction continue across commands- Fix a directory staged as an add and then removed before any commit being reported as a delete no command could clear; a scan now discards the entry with its whole subtree
- Fix
stage --scankeeping an entrystatus --scandiscards, which left the two walks with different trees lore branch merge,branch merge intoandrevision cherry-pickcarry nothing from the source revision's metadata unless--inherit-metadata <KEY>(repeatable) names it, socreated-by,reviewed-byandchange-requestno longer follow the source onto the new revision.message,timestamp,branch,committed-byandmerged-byare written by the operation that creates the revision, andcherry-picked-from,reverted-from,restored-fromandfast-forward-mergerecord an operation the new revision is not; neither set is inheritable, including under the*sentinel- Fix
lore branch mergerefusing files that carry no local changes; the working file is measured against the node the current revision holds rather than the base of the three-way diff, and a file is read and hashed at most once however many revisions it is measured against LoreFileHistoryEventDataandLoreRevisionInfoDeltaEventDatagain afrom_pathfield, set for moved files and empty for added, modified and deleted actionslore branch diff,lore revision diff,lore file historyandlore revision info --deltaprint both paths for a move, asV old -> new- Fix
lore revision diffreporting a move as a delete and an add instead of one change lore-storage: fix local store entries becoming unreachable — reported asAddress not foundwhile the payload is still in the packstore — when the background flush wrote a group's bucket files without writing its level marker, leaving the next open to read the group at the pre-fan-out layout of 256 buckets, where nothing a lower level wrote is looked up again. Every path that writes a bucket file outside the two-phase commit now commits the group's levellore-storage: fixlore branch archivereporting success while leaving the branch's name-to-id mapping on disk, so it kept appearing inlore branch list. Concurrent flushes of one group could publish an older snapshot over a later write; a per-group flush lock makes the path selection and the writes one unitlore-revision: the wait for the filesystem clock to pass a recorded modification time is bounded at 10 ms, so a coarse or fixed clock cannot block a command on itlore-revision: fragments the peer reports it already holds are marked durable locally, rather than accumulating as non-durable entries eviction cannot reclaim and every later push re-querieslore repository deletehonors--dry-run. Name and ID resolution still runs, so a bad URL is still caughtlore lock acquire,releaseandstatusreport the server's own denial reason —resource already lockedamong them — instead of a generic failure, and a refused batch rolls back the locks it took- Read verbs no longer connect to the remote where local data is the answer:
lore revision history --branch,lore file history, abranch@LATESTresolve, and the CLI's user-name lookup. Against an unreachable server a--localhistory falls from about 4 s to 150 ms lore stage --dry-runno longer persists the staged anchor, which made a later real stage reportNo changes stagedand let a commit pick up the preview statelore-revision: the Unreal package tag check compares all four bytes rather than three against a four-byte value, which made it answer false unconditionally. No file is classified differently today- Fix merge conflict markers being glued onto the content lines when both sides end the file without a trailing newline,
which left the file unparsable.
merge resolve mineandtheirsrestore the committed bytes exactly lore-server: fix revision-list acceleration sealing the boundary that contains the new revision number rather than the one crossed, so a push from 99 to 105 sealed 200 instead of 100 and any lookup from 106 to 200 aborted. Stale step keys are discarded by a key renamelore-server:RevisionInfo,RevisionTreeandRevisionDiffresolve abranch@numberidentifier through the history step acceleration instead of walking the parent chain from the branch head per request. One implementation replaces three, so an unusable step key falls back to a walk everywhere rather than reporting an existing revision as missinglore-server: fixRevisionListreporting no newer page whenever the next revision number was not exactly one past the page's first item, which a merge or fast-forward makes routine. The next page is found through the branch's own step boundaries, and a store failure is reported rather than read as the end of historylore dirtyon a path inside a layer records the marker against that layer rather than the parent. A modified layer file was recorded as an untracked add in the parent and a deleted one dropped silently, so neitherstatusnor a directory-scopedstagesaw the changelore-base: a call made afterlore_shutdownfails rather than hanging forever. The event forwarder was spawned onto a runtime already torn down, so the wait for itsEndevent never endedlore-revision: a walk steps the filter one component at a time instead of re-folding the whole path per node — 2.3x at depth 12, 8.6x at depth 40 — and a forced walk consults it not at all. Two link crossings were also wrong:resetcarried the link node's own verdict into the content below it, andunstagematched a linked file by the linked repository's spelling rather than the mount pathlore link addrefuses a mount whose source path strictly nests inside another mount of the same repository. Both mounts placed the same content under separate pins, so an edit through one went stale in the other and a stage walk kept whichever it reached last. Identical and disjoint source paths stay allowedlore-server: the storage layer'sSlowDownbackpressure reaches the client asRESOURCE_EXHAUSTEDinstead ofNOT_FOUNDorINTERNAL, and a throttled cache read no longer falls back to a full history walk against an overloaded store. A throttled branch head ends aBranchListstream rather than omitting the branchlore-server: a fragmentputcarrying no payload fails hash validation instead of leaving the address unverified- A
BranchPushrefused for a missing fragment answersFAILED_PRECONDITIONcarrying the address rather thanNOT_FOUND, which the client read as a missing branch and answered by recreating it. The client now reportsmissing fragment <address> - Staging a large changelist no longer logs every path it walks at debug:
Staging path,Stage fileandStage directorymove to trace, and a resolved case is reported at debug only where it had to be corrected.Stage filealso named a path that did not exist lore-server: an HTTP/2 POST whose body carries no decodable gRPC message — what a plaincurlsends — is reported asInvalidArgumentrather thanInternal, soInternalkeeps meaning a genuine server fault- Performance: staging a path at depth n stats the working tree once rather than once per component; a scan allocates 5.9 times per file on an unchanged tree where it allocated 16.2; and a one-item storage batch runs on the calling task rather than spawning
lore-server: a trusted internal QUIC connection'sClientIdentifyuser agent is recorded as sent, so a deployment need not list its own server-to-server agents in the user-agent filter- A staging walk settles a type replacement rather than only reporting the delete and the add: the displaced subtree
takes a staged delete and a directory replacing a file is descended. Staging twice is idempotent, a staged delete is
taken back where the file is still there, and a staged add records the size and mode measured.
branch merge,cherry-pick,revert,resetandunstagehold one filesystem operation per diff instead of freezing the working tree per file - Link and layer paths are reported and resolved from the working tree rather than from the linked repository's own
spelling. A layer mounted away from its source path can be synced and reports changes at the mount, a conflict from a
subtree link is named under the mount rather than dropped,
status --check-dirtyclears and counts markers inside layers, and staging a delete below a shared pre-created ancestor resolves the link chain from there lore unstageandlore resetroute a path inside a layer into the layer's own states, where before both reported success and left the layer untouched — leaving a staged layer blockinglore syncwith no route forward that kept the file.reset --revisionon a layer path is now refusedlore-io:create_dir_allsucceeds wherever the directory is already there, so a Windows container bind-mount root — which answersPermissionDeniedformkdir— no longer failssyncandclone. Rust 1.94 narrowed the standard library to forgivingAlreadyExistsalonelore-storage: healing a failedverify_fragmentdrops the associations naming the bad payload rather than clearing the payload pointer. A payload-less entry answered a full match while serving nothing, so peers were told content was available that the store could not serve, blocking re-upload. Obliterated entries are left alonelore-server: a replicated store'sget_metadataandquerytake the two reserved priority QUIC streams instead of queueing behind bulk fragment traffic — p50 falls from 15.4 ms to 1.6 ms behind 128 concurrent 32 KiB gets- Switching between branches that mount one repository at different source paths no longer coalesces the mount delete and add into a rename, which left the mount holding a subtree its own source path does not name. A link node's identity names the repository it mounts, which every mount shares, so it is no longer read as a file identity
status --scanreports each change as the walk finds it rather than after the whole diff is collected, so the first event fires during the scan and the walk always completes, leaving dirty marks wholelore: a storage session error keeps its classification instead of being wrapped as internal, so a peer'sSlowDownreaches the retry paths in read and write again- Documentation that described
Repository.created,Branch.created, the thin-client commit timestamp andlore revision history --dateas Unix epoch seconds is corrected to milliseconds. No wire or on-disk value changes;--datewas the one place the documented unit made the filter never match
v0.9.0 (Aug 28th 2026) [#782]¶
Breaking changes¶
lore-credential: auth tokens move totokenstore.tomlunder atokenstore_encryption_keykey, with nothing migrated fromtokens.toml; old and new clients keep entirely separate credentials, so expect onelore loginper generation. An unmigrated store reads asNot authenticatedand--remotereads answer empty rather than failing, so scripts gating onlore statusshould check the exit codelore-server(AWS store): the fragment describing a payload now travels on the S3 object as anx-amz-meta-lore-fragmentheader instead of in a DynamoDB record, and the fragment metadata table is replaced by a fragment state table holding lifecycle state alone (a row's presence means the hash exists). Existing objects are not rewritten — they are read through a fallback to the old table, which must stay configured for them to remain readable. Under[plugins.aws.immutable_store]:- Existing deployment: set
dynamodb_fragment_state_table(required, no alias — start fails without it; normally the table that held fragment metadata, since the key schema is unchanged and the two row shapes coexist) anddynamodb_fragment_metadata_table(enables the fallback read, normally the same value; accepts the olderdynamodb_metadata_tablespelling). Roll out as a full stop followed by a full start — old and new servers must never run at the same time. To move the data across and retire the old table, seecontrib/aws-migrate-0.9.0/README.md - New deployment: set
dynamodb_fragment_state_tableonly. Leavingdynamodb_fragment_metadata_tableunset declares that no object predating the change exists, so no fallback read is ever issued and an object without its own metadata is reported as damaged
- Existing deployment: set
lore.model.v1andlore.thin_client.v1:Repository.created,Branch.createdandRevision.timestampnow carry Unix epoch milliseconds instead of seconds- C API:
LoreMetadataTypediscriminants are now stable integers shared betweenlore.hand the on-disk metadata buffer, andlore_revision_tree_metadata_settakes typed(key, LoreMetadata)batches instead of text plus a format tag; callers using the enum names need a recompile, and callers hard-coding the old numeric values must update them lore-server(replication protocol):ExistsBatchis replaced by a batchQuery, andGet/GetMetadataresponses carry the fullStoreGetDataincluding the partition. Retired opcodes are reserved rather than reassigned, so a peer on the previous protocol is rejected instead of misreading a response — roll replicas and their upstream together- C API: failures that previously reported
-1(internal) now carry the specific code where one applies, because an error crossing an internal boundary keeps its variant instead of collapsing.lore_revision_tree_metadata_setand its file equivalent can now returnSlowDown(5),NotAuthorized(7),Maintenance(11),NotAuthenticated(12),NoRemote(14),NotConnected(17) andNotSupported(18); remote store reads and writes addDisconnected(6); and connecting with no stored credential reportsNotAuthenticatedrather than an internal fault. A caller treating every non-zero status alike is unaffected; one that branches on-1, or reads-1asretrying will not help, now sees retryable and re-authenticable codes on paths that previously only ever produced-1, and should handle them before upgrading
Features¶
lore-io: new runtime-independent asynchronous file I/O engine backing Lore's file access — positional owned-buffer operations on a bounded, idle-reaped syscall pool, upgraded automatically toio_uringon Linux and overlapped I/O on Windows, with vectored scatter/gather reads and writes.LORE_IO_BACKENDoverrides the choice; internals indocs/developing/internals/file-io-engine.mdlore:lore_revision_tree_commitfreezes a handle's in-memory tree into a revision and advances the branch tip by compare-and-swap, taking the branch from the handle'sbranchmetadata key. Commit is exclusive and all-or-nothing — a failure leaves the handle where it was with every edit still staged — so services can publish revisions with no working tree on disklore:lore_revision_tree_delete,_modifyand_movejoinaddas batch verbs, each validating the whole batch before any node changes and emitting a*_COMPLETEper entry plus oneBATCH_COMPLETE. A node from the loaded revision is staged for deletion and reversible, one added through the handle is discarded outright, andstaged_actionon the child and node-info events reports what is pending. Moving into a linked repository is not supported yetlore: batchedlore_revision_tree_metadata_set/_get/_clear, withLoreMetadata,LoreMetadataTypeandLoreBinaryreworked into owning, self-describing types so binary metadata can cross event callbacks and both wire formats (see Breaking changes)lore: a revision tree handle holds its own store reference, so closing the parent storage handle leaves it usable; orphaned handles are closed when an IPC connection drops, andlore::shutdowndrains tree handles firstlore:lore_storage_get/lore_storage_get_filetakeoffsetandlengthper item to read only a slice of the content, pruning the fragment tree so the work is proportional to the range rather than the content size (a zeroed pair still reads the whole content; a start past the end is rejected withINVALID_ARGUMENTS)lore:lore_storage_get_resolved/lore_storage_put_resolved, also over QUIC and gRPC, resolve a key belonging to another system — an asset id, a build id — and act on the content it names in one request instead of two. A read resolves local-first and caches the mapping it learns, verifying the root fragment against the resolved hash; a write stores content before publishing the key and is last-writer-wins. Design:docs/proposals/2026-08-02-resolved-storage-operations.mdlore branch archive --include-layers/--layer <path>archives the branch in every configured layer, or in the layer at one mount path; the default still touches only the repository it ran in, since archiving deletes and a layer owns its own branch lifecyclelore --identity-token <token>/--access-token <token>(and the matchingLoreGlobalArgsfields) use caller-supplied tokens instead of the credential store, for CI runs and stateless services. Tokens are ephemeral and never stored, the identity is read from the token, and both conflict with--identity; given only an access token, an operation needing an identity token fails rather than falling back to a stored oneLoreRepositoryCreateArgs/LoreRepositoryCloneArgs:use_shared_storeis replaced by aLoreSharedStoreModeenum (Inherit= 0,Enabled,Disabled), so a caller can explicitly refuse shared-store backing on a machine whereuse_shared_store_automaticallyis set (a zero-initialized struct still follows the machine setting)ImmutableStore:exist/exist_batchare unified into a batchqueryanswering with the best match level found, andget/get_metadatadrop their match-level parameter for oneStoreGetDatacarrying the fragment, the level and an optional payload. The contract — never over-report, obliterated never matches, reads agree with each other and name where a match was found — is written into the trait and enforced by a conformance battery every store runslore-server: fragments arriving Oodle-compressed onputare transcoded to Zstd as the first step of retiring Oodle, gated behind theoodlefeature and opt-out at runtime withLORE_DISABLE_CONVERT_OODLE_ON_PUT; the address is unchanged, since identity is over uncompressed contentlore-aws:MetadataMigratorandrun_migratordrain legacy DynamoDB fragment-metadata rows into the new S3-object-metadata plus state-row model, by segmented parallel scan across a pool of consumers. An accurate non-Oodle codec is re-uploaded as-is to set its S3 headers, Oodle and mismatched codecs recompress to Zstd, and a run is idempotent and resumable. Packaged as a standalone tool undercontrib/aws-migrate-0.9.0lore link add/remove/update/reset/listnow work on a nested link — one mounted inside another link's subtree — mutating the innermost repository's registry and propagating outward, andlore commit --link <nested path>commits each intermediate link as a real revision before repinning. Nested-link merge is not covered yetlore-server: the legacyurc.rpc.RevisionServicediff and tree gainlink_partitionandtrackingfields, so a consumer can tell which repository a changed path resolves under and whether a link follows its parent branch or is pinned (both additive on messages already marked deprecated)lore-server:presigned_url_extra_content_typesandpresigned_url_denied_content_typesunder[server.http]adjust whichContent-Typevalues a redeemed presigned URL may carry. They extend rather than replace the built-in safe set, a type in both lists is denied, and browser-executable types can never be added — the server refuses to start instead of failing openlore-proto: thin-clientTreePathexposes file size and mode, so aThinClientService.RevisionTreecaller gets both without a second requestlore: self-signed certificates installed in the OS trust store are now honored (reqwest'srustls-tls-native-roots), which local development against a self-signed server needslore link info <path>reports one link's mount and source paths, its branch and whether that branch is followed or pinned, the pinned and remote-latest revisions, the link flags, and the staged state and staged file count inside it; the remote revision is omitted when offline
Fixes & Improvements¶
- Thread model: the network transport moves onto its own runtime, the rayon compute pool is gone with compression,
hashing and chunking now inline on the core workers, and the blocking pool shrinks to two threads.
LORE_MAX_THREADis now an absolute cap. Clone throughput 1.4 → 2.5 Gbps, large-commit peak memory 6 → 4 GiB lore-storage,lore-revision: the store buckets, packstore, fragment chunker and remainingtokio::fs/std::fscalls all run throughlore-io, so a common-case bucket load completes in one dispatch and a flush gathers header, index and entries in one vectored write; filesystem locks back off asynchronously instead of parking a threadlore-storage: fix a file shorter than its measured size being taken as an early end, producing a chunk list covering fewer bytes than its root fragment recorded; the read now fails- Windows: no handle the I/O driver opens permits a second writer, while a read-only open still shares reads and
deletion so replace-by-rename works.
ERROR_SHARING_VIOLATIONis now transient and retried, so materializing a file being hashed waits rather than fails - Fix
lore branch mergeunder a sparse view dropping changes outside the view, leaving the branch divergent from the one it recorded as merged; nodes now merge regardless of the view, which gates only on-disk work, and an out-of-view conflict adopts theirs asStagedMergeTheirs(adopting untouched excluded subtrees whole made it 34% faster on 110,000 files) - Fix a link added, removed or repinned on one branch not surviving the merge that brought it over, where
link listreported nothing andlore stagefailed withLink not found; the registry now follows any link node a change set stages or deletes, and a row both sides moved refuses the merge - Fix a revision moving a link's pin producing an empty diff when the subtree was byte-identical, and entries from a
linked repository naming no repository so content could not be fetched from the right partition; a new
link_readpolicy gives a caller authorized only for the parent one change for the mount path - Fix
lore branch createfailing withBranch <name> already existswhen a linked repository held the requested branch id under another name; the cascade adopts it and reports aLinkBranchCreateevent with areusedflag - Fix concurrent link edits overwriting one another:
link add,updateandremovereleased the runtime lock between reading and storing the registry, and now hold one write lock across it - Fix
lore pushon a repository with links reporting the parent's revision against a branch that exists only in a linked repository; push events now carry the repository and branch they report (repository state was always correct) - Fix
lore stage . --scanpinning a staged revision for unchanged layers, which aborted the nextcommitwithNothing stagedand madebranch switchrefuse withLayer has uncommitted staged changes; a layer is pinned only when the walk left its state dirty - Fix
lore syncleaving a layer's staged state on the revision it moved away from, so the next commit refused it as a stale parent or under--forcereverted what the sync brought in; sync now refuses when a layer holds staged work and otherwise rebases onto the new pin - Fix
lore layer removediscarding a layer's staged work silently and leaving staged adds on disk; removal now counts staged files and refuses without--force - Fix an unreadable
.lore/config.tomlorlayer.tomlpresenting as a repository with no remote or no layers, which the next save made permanent; both now default only forNotFoundand save through a renamed.tmpsibling - Content the peer already holds is no longer re-uploaded: where a query reports the hash under another context or
partition, the client asks the peer to duplicate the association instead of sending the payload, on
lore pushand direct writes alike lore-revision: the composite store caches metadata resolved byquery, not onlyget_metadatahits, so aquery-heavy workload stops hopping to the durable store; capped bycache_metadata_semaphore_sizeand limited to durable-sourced results, andshould_cache_query_resultsis renamedcache_metadatalore-server:get_metadatanow fans the durable store out in parallel with read replicas and sends a dedicated QUIC command, instead of falling back to a localquerythat reports only the durably-stored flag; edge replicas answered incorrectly and always paid a cross-region triplore-server: the JWK service picks up key rotation without a restart; a verification failure only a key could explain triggers one refresh, so material replaced behind an unchangedkidno longer rejects every tokenlore-server: JWK fetches are bounded by timeouts, a pooled client, single-flight collapsing of concurrent misses and a minimum interval, with an empty cache never throttled so start-up always proceeds. The document is capped at 1 MiBlore-server: a JWK set is validated key by key — an unusable key is skipped rather than failing the fetch, a key whosealgfamily does not match itsktyis refused, and an empty result errors. RS256 is inferred for an RSA key omittingalg, which previously failed start-up against Microsoft Entra IDlore-server: why a JWT verification failed no longer reaches the caller —bad signature,expiredandno such key idare an oracle for someone unauthenticated; the reason goes to a debug loglore-server: fix a stored-XSS vector on presigned-URL redeems, where bytes could be served from the Lore origin under a caller-chosenContent-Typesuch astext/html; a deny-by-default allowlist rejects at mint, coerces on redeem, and every response carriesnosniffand adefault-src 'none'; sandboxCSPlore-server:binary/octet-streamis allowed in the presign allowlist and served verbatim — S3 assigns it to objects uploaded without an explicitContent-Type, so callers forwarding S3 metadata had theirs coercedlore-server:RepositoryMetadataGetandRepositoryMetadataSetnow perform the real-time authorization check the other repository handlers do, so a client cannot read or write metadata on a repository it has no access tolore-transport: fix a per-item failure on the streaming storage RPCs being reported as a stream trailer, which killed the HTTP/2 stream and every request multiplexed onto it; outcomes now travel in-band, and field numbers are preserved so get responses stay wire-compatiblelore-transport: fix gRPC reconnect not re-reading the epoch per attempt, and storage streaming having no working reconnect at all; a dead stream now rotates once across concurrent callers and replays outstanding requests- Fix
lorecommands taking about 30 seconds when a hostname resolved first to an address family the server was not listening on, commonlylocalhoston::1; the QUIC client now follows Happy Eyeballs (RFC 8305), 30.05 s to 0.38 s on the reproduction lore-transport: fix the per-stream in-flight counter never being decremented on error or cancellation, which degraded priority routing and left the selector round-robining; also fixadd_streamstoring a count one short of the streams it openedlore-aws:queryis backward compatible with fragments described by the legacy metadata table, soBranchPushno longer reports content as missing when it is stored but described by the old rowlore-aws: the mutable store's zero-expected compare-and-swap matches the local store now — a row holding a zero value is treated as absent — so an empty branch pointer cannot make a swap report success without the write landinglore-storage: a corrupt mutable-store bucket is no longer reset to empty on an authoritative store, where it silently dropped branch heads, metadata and instance registrations with no upstream to refill from;authoritative: truemakes it a hard errorlore-credential: fix the credential store rewriting its keyring entry on every stored token, which on macOS prompted for keychain access from every application linking Lore and again after each rebuild; each seal now draws a random nonce instead of persisting a counterlore-credential: fix any keyring error being read asno key, so one denied prompt regenerated the key and wiped the token store for every Lore process on the machine; onlyNoEntrycounts as absent- Fix a notification subscription that had stopped on its own still counting as live, so every later subscribe returned success without subscribing; liveness is now checked by cancellation token and task state
- Fix
lore history --branch <name>returning an empty listing for a branch that exists only on the remote; the local branch is preferred and the remote head used when there is no local history - Fix an infinite loop walking revision history for a deleted file, where a zero parent hash was handed to
State::deserializerepeatedly; a three-way diff base no longer resolves to the zero revision lore-revision: the diff and merge base search no longer errors asDivergentwhen it cannot prove divergence — branch points sharing no revision fall back to the older one, and two points with the same revision number short-circuit instead of spending a full search budget- Fix
lore reset <path> --revision <revision>failing when the path is currently a directory but was a file in that revision - Fix
lore syncundoing a change from a file to a directory; where an add and a delete carry the same node name, the delete now takes thefromnode - Fix a clone failing with
Failed to create directorywhen the parent already existed but was not created by that call — a bind-mounted clone root or a drive root - Fix
lore stage --targets <file>hanging on a large list, wherededup_to_supersetsrescanned every kept path per candidate and timed out around 900,000 paths; sorting in subtree order takes the collapse from quadratic to O(n log n) lore stageno longer grows memory in proportion to the work ahead of it; in-flight tasks are capped at 1000 and drained as they complete, and directory fan-out is bounded by a semaphore, processing a child inline when no permit is freelore-revision: node allocation during staging no longer holds a single-permit mutex across its whole scan, andnode_addskips the 65 KB zero allocation it issued when clearing a recycled slot whose metadata block was never writtenlore-revision: the per-node stage log lines drop from debug to trace; they fired once per file and made--debugunusable during a large stagelore-base: fixHash,Context,PartitionandAddressbeing unreadable under non-self-describing formats such asbitcode, which made any record carrying one fail to decode; a truncated address is also refused rather than becoming the zero addresslore-server: pushing a revision hash that does not exist in the immutable store returnsNotFoundinstead of a genericInternalstatuslore-server:LORE_SERVER_MAINTENANCE=1stops the internal QUIC server serving its port while the internal gRPC server keeps a stub listening, so health checks getunavailablerather than a closed portlore-telemetry:size_histogramgains explicit power-of-two boundaries from 64 B to the 256 KiBFRAGMENT_SIZE_THRESHOLD, soputandgetdistributions are no longer cut off at the OpenTelemetry defaultslore-error-set: newchain_err_fromchains a discrete error onto an error-set enum without destructuring aTraced<E>; call sites that discarded the originating trace now preserve itlore-revision:LoreRevisionDiffFileEventDatagainsfrom_path, so a receiver ofLORE_EVENT_REVISION_DIFF_FILEcan reconstruct where a moved or copied file came fromlore: the batch verbs' id fields are named apart —entry_idper entry,batch_idon the batch args andBatchComplete— andparent_entrybecomesparent_entry_index- SWFS groundwork: the stale commented-out integration is replaced by a real interface whose types are always available
while its methods compile only under the
swfsfeature;InstanceOperationImpllets an operation in a linked or layered repository be finalized alongside the main one - New
iterationCargo profile inheritsreleasewith LTO off, cutting link time when rebuilding frequently lore-revision: reject directory traversal and dot-prefixed segments in repository path componentslore-aws: guard against maliciously large S3 payloadslore-storage: fix data loss in the lazy fan-out redistribute path, where buckets left unmarked after a fan-out could be overwritten from the stale on-disk layout by a reader racing the flushlore-storage: decide the legacy bucket layout per group rather than per store, so one written group no longer pins every other group at the maximum bucket count on the next openlore-storage: file modification detection no longer downloads content —file_matchestransfers the stored header and, when fragmented, its fragment lists, then compares chunks against the file's own byteslore-storage: fix a dropped consumer on a streaming read being reported as an error instead of draining gracefullylore-storage: build zstd compression and decompression contexts in workspaces this crate owns, with the pool holding at most 32 and further concurrency building one per calllore-storage: removeallow_partial_fragmentfrom the local store, so alore-serverlocal store can cacheget_metadataresultslore-storage: obliterate a fragment tree without holding a lock across it, and drop an unreachable sink path from the defragment pipeline dispatchlore-storage: key file modification times by the lowercase path string, unifying them with node-name matchinglore-server:ReplicatedImmutableStoreimplements theCopymessage and returns theContexta query matched under, instead of leaving clients to assume the defaultlore-transport: a persistent connection updates the authn and authz tokens it presents, and authz exchange results for caller-supplied identity tokens are no longer written to the token storelore-auth: a missing token reportsNotAuthenticatedinstead of a generic internal error- Fix clone not applying view filtering inside linked and layered repositories, where
clone_nodewas called with a path relative to the linked repository rather than the mount point lore-revision: refuse alink removethat would destroy local editslore-revision: fix non-ASCII lowercase handling when building relative paths- Fix the originating trace being lost through
::internaland::internal_with_context lore-revision: staging does less work per path — targets resolve concurrently, each walks from its pre-created ancestor rather than the root, a directory's children are read once and matched by binary search, a shared directory's case resolves once, and paths are neither re-stat'd nor rebuilt through extra string allocationslore-revision,lore-storage,lore-base: allocation and locking trimmed on the hot paths — the dirty tree walks from an explicit stack and names into one buffer, node blocks serialize from the lock without a copy and deserialize once rather than once per waiter, merkle tree blocks come from their own heap, short ASCII names fold to lowercase on the stack, the log level reads from an atomic, and successful operations no longer allocate error stringslore-transport,lore-revision: reuse the lazyStorageSessionwrapper across fragment writes instead of rebuilding it per writelore-io,lore-revision: verify a path's case by asking the filesystem for the name instead of listing its directorylore-aws: box SDK error payloads to shrink thelore-awserror types
v0.8.6 (Jul 29th 2026)¶
Breaking changes¶
lore-client: logging CLI arguments are now strictly mutually exclusive; a command line passing conflicting logging flags is rejected instead of silently accepted- Auth
login/infonow returnNotSupported(code 18) when the server has no auth endpoint configured, and surfaceNotAuthenticated/Disconnectedinstead of a generic internal error (code -1); scripts branching on these exit/FFI codes must be updated - Presigned URL vending (
POST /v1/repository/{id}/content/{address}/presign) is now restricted to service accounts; normal user tokens can no longer mint presigned URLs
Features¶
lore-server: forwardBranchListandRepositoryCreateto a remote Lore server when configured (extending the existingBranchCreate/BranchDelete/BranchGetforwarding)lore: add a batch node-add verb (lore_revision_tree_add) to the low-level revision API, landing a whole subtree atomicallylore: run the service process (lore service run) on Linux and macOS, not just Windowslore: validate that all strings passed to the C API are valid UTF-8, rejecting invalid input up front with a named fieldlore-server: support file:// JWKS endpoints in the JWK servicelore-revision: optionaldurable_delayon the composite store so read replicas can answer before the durable tier is queriedlore-storage: self-heal corrupt (torn-write / zero-filled) mutable store buckets instead of failing to open the storelore-server: addCache-Controlheaders to the presigned-URL redeem endpoint for immutable content- Add a self-contained Terraform example for an AWS primary + edge deployment under
contrib/aws/
Fixes & Improvements¶
lore-storage:read_intonow respects the requested byte range for single-fragment reads (partial reads of files ≤ 256 KiB no longer fail)lore-server: fix JWK refresh cache check so a missing/rotated key triggers a fetch, allowing key rotation without a restartlore-credential: require a DNS label boundary when matching a dotless JWTaud, and allow exact apex-domain matches, closing an audience-suffix leaklore-revision: preserve dirty move status throughstatus --scaninstead of degrading it to delete + addlore-revision: bound commit read memory with a travelling fragment permit, and cap directory recursion fan-out during commitlore-revision: drain staging tasks on all error paths so failures propagate cleanlylore-storage: batch FastCDC chunking to cut per-chunk overhead on medium/large writes- Add transport connect timeouts so local reads no longer stall on an unreachable remote
lore: use the calling process's working directory for absolute-path resolution in the service process- Fix
lore_branch_switchandlore_branch_resetto restore the branch name→id mapping (so the branch appears in the local list), with a--forceoverride lore-revision: fixrestorefailing withDirty node remain after nodes were committedlore-base: update vendoredrpmallocto 2.0.1
v0.8.5 (Jul 15th 2026)¶
Features¶
- Implement the low-level revision-tree read verbs on the C API:
tree_load/tree_close,resolve_path,list_children, revision & nodeinfo, andnode_path - Forward
BranchCreate,BranchDelete, andBranchGetto a remote Lore server, opt-in per-RPC under[server.grpc_public_services.forwarded_requests] - Add
repository info --localto read repository metadata from the local store without contacting the remote
Fixes & Improvements¶
- Fix a corrupt-tree race in concurrent
node_addwhere a half-initialized node could be observed on the child chain - Protect non-durable local-only fragments from being orphaned by store GC eviction/compaction
- Fix dirty-add reclassifying remaining files when committing multiple added files individually
- Parallelize staging of multiple explicit paths instead of a serialized per-path loop
- Make CLI paths relative to the current working directory across all path-printing commands
- Bump
anyhow,crossbeam, andmemmap2for security advisories
v0.8.4 (Jun 25th 2026)¶
Features¶
- Add
--dry-runtorevision commitandlock acquire/release - Run incremental store GC by default; replace
--gcwith--no-gcto disable - Expose the mutable store through the low-level storage C API
- Add
ForwardedRevisionServicegRPC endpoint to forwardBranchCreateto a remote server - Carry structured error detail and FFI codes on the
Completeevent - Show staged renames as moves in diff output
lore statusprints paths relative to the current working directory
Fixes & Improvements¶
- Fix use-after-free in
write_fragmentedwhen the chunker future is cancelled - Fix
LoreArray<T>dealloc layout mismatch in Drop - Fix
commit --statspanic and report real fragment stats - Fix link contents surfacing as parent adds in file diff
- Fix clone not materializing view-filtered directories with all-excluded children
- Reject malformed
metadata setargs instead of panicking; default branch metadata to current branch - Reset staged add/remove/update for link nodes
- Propagate dirty to committed ancestor directories on dirty add
- Make dirty add idempotent so a repeat dirty doesn't duplicate the node
- Stage empty dirty-added directories by their own path
- Honor
--dry-runon branch push - Rename
[server.replication]config to[server.grpc_internal](not backwards-compatible) - Map GRPC storage errors correctly and stop classifying
Unknown/EOFerrors as server errors - Handle thin-client
RevisionDiff/RevisionTreeRPCs with a zeroed revision - Tag linked-repo
RevisionDiffchanges with an indexed partition table - Standardise HTTP tracing and log levels
- Seed QUIC clients with an initial CWND on regeneration
- Remove redundant
dry_runfield from lock events - Rename shared store config file to
shared_store.tomlwith auto-migration - Change
statsflag tou8for C ABI consistency