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.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
Features¶
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: 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 checked[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.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:[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
Fixes & Improvements¶
lore 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 - 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
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 alonelore-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¶
- 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 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