Opening upgrades and retained-source recovery
With storage and server.mode: "partial_replica", the browser uses a partial replica with on-demand sync. Rust owns supported format upgrades and full-to-partial conversion during opening. Current-format partial-replica opening installs bounded metadata without a repository-wide migration scan.
Storage plus server selects a partial replica; server alone selects remote execution. Applications do not need a separate migration call or a mode flag. When server is supplied, opening waits for authenticated authority readiness, including supported upgrades. Omit server to reopen an existing partial replica offline from its durable admission; covered reads and local writes remain available. Explicit conversion and recovery helpers remain available to operator tooling through @lix-js/sdk/migration.
The current runtime uses format 87; see the cutover contract. Registered migration chains support source formats 72–86 and target format 87, running automatically when opening supported older storage. Format 83 adds author-bearing current-state records, format 84 records nearest-checkpoint summaries, format 85 adds authenticated semantic fingerprints, and format 86 registers epoch-scoped negative coverage for partial native checkpoint-conversation metadata. The v85-to-v86 step changes only the repository marker; it does not rewrite packed history. It does not infer canonical checkpoint descriptions from generic historical conversation targets; those rows remain unchanged, and pre-feature checkpoints have no canonical description. The v86-to-v87 step registers private expiring read-operation scratch with a marker-only migration; it does not rewrite authored rows or durable replay identities. The format-80 repair described below remains part of that historical chain. Previously unsupported formats, including the existing format-68 hard cut, still fail explicitly; this does not introduce a compatibility shim for them. Released format-72 and format-75 fixtures retain their tested schema, file/checkpoint and cold-reopen semantics after migration.
Format migration upgrades a version-2 durable read-interest journal in the isolated candidate epoch before publishing it. The rewrite preserves each recipe and maps the former optional file_ids field exactly: missing or null means all paths, while an explicit list (including an empty list) remains a file-ID scope. Unsupported or malformed recipes still fail closed, leaving the source epoch available for repair and retry. The source journal and pending repository rows remain retained for rollback; no fresh bootstrap is required for a valid version-2 journal.
The upgrade rebuilds native primary-key lookup catalogs from authoritative state, including explicit tombstones. This repairs incomplete catalogs left by earlier merge and columnar-storage paths while preserving current rows, history and pending branch controls. The repair runs in the migration epoch, before format 79 is published; ordinary partial-replica opening does not scan repository rows.
The format-80 migration separates complete checkpoint incorporation from selected commit membership. It preserves existing mutation members and converts each legacy header with explicit unknown incorporation. Existing complete-state aliases retain their separate source proof without promoting the new field. Resident rows, graph records and checkpoint-source sidecars cannot change this immutable encoding, so full and sparse replicas produce identical headers for the same commit. Unknown provenance is not evidence that edits were never accepted; reconciliation that requires unavailable proof fails explicitly instead of replaying them. Sparse format-79 upgrades preserve available native inputs and exact pending synchronization coordinates. Observable row lifetimes and selected members are unchanged. Migration and recovery costs are separate from ordinary current-format opening.
The same atomic v79-to-v80 publication repairs omitted semantic-owner markers for previously installed, durably certified replica snapshots. It reads only their certified coordinates and independently materialized complete-state roots, not arbitrary HOT rows. Both traversal and marker output share the migration entry and byte budgets; failure publishes neither markers nor the new format. The mutable markers omitted-local, omitted-global, and omitted-unknown record authenticated omission without inventing a graph record or body. Scope remains unknown unless authenticated metadata establishes it; unknown never defaults to local. This repair does not refine immutable LegacyUnknown incorporation fields or excuse unmarked missing data in standalone repositories.
Historical selected-locator repair validates resident canonical change records before removing an alias. Canonical reads are batched by scan page; classification groups candidates across the whole migration by owner and segment, avoiding repeated transfer of the same inventory. Raw point-read keys and returned bytes are charged before decoding, alongside scanned locator metadata and staged publication limits. Budget failure leaves the old format and locators intact; malformed canonical records remain errors, not evidence of a usable replacement.
The same retained-source conversion used by opening is available to operator tooling. For a closed existing replica, JavaScript exposes convertReplicaToPartial({ storage, server, branchId? }); Rust exposes convert_replica_to_partial(storage, server, branch_id). Conversion owns storage exclusively, authenticates repository/account identity, and retains the original source. Its reconciliation journal preserves exact upload targets, attempt IDs, restart receipts, and native merge outcomes across interrupted replies. A fresh monotonic baseline lease proof is checked before publication. Migration costs occur only when conversion is required; current-format opening remains bounded.
If an older partial replica format cannot supply native pending history, format migration retains its captured source for recovery. Conversion can return LIX_PARTIAL_REPLICA_CONVERSION_RECOVERY_REQUIRED after that format migration. Open the current full recovery repository locally, inspect replicaRecoverySources(), and export available work with exportReplicaRecovery(id).
recoverReplicaWithServer(id, server) / Rust recover_replica_with_server(id, server) restores supported rows on separate recovery branches. The server must authenticate the same repository and account. The operation uses an isolated writer context and exact missing-history/chunk requests, with bounded retry and transfer pages. It does not attach a background worker, pull current state, or upload recovered branches. Existing provider and session ownership remains in force; closing the JS handle waits for its active operation. recoverReplica(id) remains the local-only variant.
Recovery preserves file identities and starts branches at the repository root, so unrelated caller rows are not inherited. Original bytes remain retained after success. Receipts are durable and idempotent; they describe local restoration, not authority acknowledgment. Always inspect unresolved entries and export limits before treating a recovered source as complete.
Conversion reconciles pending work on all existing ordinary branches, including interrupted native merge replies. It also supports new/restored branches whose pending global changes consist only of branch-descriptor additions. The native global merge and all new references publish atomically; original local heads, fork/checkpoint references and commit identities are preserved. The requested selected branch may be one that does not yet exist on the authority.
A later descriptor-only global advance is handled through a terminally fenced successor attempt, or by recovering an already-committed exact merge receipt. The successor preserves the original source coordinates and accepted uploads, proves authenticated ancestry and revalidates against the new global state. Lost replies before and after those advances are covered by a real HTTP test. No source head is rewritten to make it appear acknowledged.
Other pending global changes (such as account/schema/plugin/default-branch changes), resets and checkpoint changes remain unsupported by automatic reconciliation. Conversion fails explicitly and retains the original source in those cases. A local recovery receipt alone does not clear the retained-source recovery requirement.
After successful conversion, cleanup of temporary authority pins is best effort. For a closed partial storage provider, call retryReplicaMigrationCleanup({ storage, server }): Promise<number>; Rust exposes retry_replica_migration_cleanup(storage, server). The returned count identifies cleanup journals finalized by that call; repeated successful calls return zero. Exact durable merge receipts, surviving branch ancestry and control guards must prove the pins are redundant before removal. A lost response preserves retry coordinates and does not roll back the usable partial replica. This explicit maintenance operation may inspect bounded journal pages; ordinary opening never scans migration journals.