P3: the books' format moves only with the owner's go-ahead (D-39)

Books an update could write a newer format for open on their old format
and are asked at every open; anything needing a new feature is refused
until the owner agrees with the passphrase, after a verified backup that
includes the keys. New books start on the current format. Each projection
schema has its own database file, so an older version still opens the
books until the owner agrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-21 14:04:11 -06:00
1 parent b0a9fd8ad4
commit 3a9aa92315
62 files changed
+5309 -628

No files matched your search

+18 -5
View File
@@ -665,7 +665,9 @@ impl Root {
/// built in a staging folder: every stored file re-hashed against its
/// name, the copy opened (its database rebuilt from the journal) with
/// the source's head, every file it names present, verified end to
/// end, given its settings if the source keeps none (and verified
/// end, moved to this version's journal format if the source's is
/// older (the copy is new books of this version; the source keeps its
/// own), given its settings if the source keeps none (and verified
/// again), synced, and only then renamed into place.
pub fn adopt(
&self,
@@ -781,15 +783,22 @@ impl Root {
}
store.verify_and_heal()?;
let blobs_verified = store.verify_blobs()?;
// The copy is new books of this version, in its journal format
// (the original, left as it was, keeps its own): it waits for
// no format upgrade, and its settings can move in.
let format_upgraded = store.upgrade_format()?.is_some();
let (settings_imported, settings_ignored) = match (&options.settings, keeps_settings) {
(Some((changes, source)), false) => {
let n = store.adopt_settings(changes, source)?.len();
store.verify_and_heal()?;
(n, false)
}
(Some(_), true) => (0, true),
(None, _) => (0, false),
};
// Verified again for what was written on the way in.
if store.head() != head {
store.verify_and_heal()?;
}
let final_head = store.head();
drop(store);
sync_dir(&staging)?;
@@ -806,6 +815,7 @@ impl Root {
settings_imported,
settings_ignored,
source_lock_created,
format_upgraded,
})
})();
let renamed = built.and_then(|report| {
@@ -1660,9 +1670,9 @@ pub struct AdoptReport {
pub id: BookId,
/// The source's head, `(seq, hash)`, verified before copying.
pub source_head: (u64, String),
/// The adopted books' head: the source's, plus (when settings were
/// moved in) their `settings.set`, after the journal format they need
/// if the source's is older.
/// The adopted books' head: the source's, plus this version's journal
/// format if the source's is older (`format_upgraded`), plus (when
/// settings were moved in) their `settings.set`.
pub head: (u64, String),
/// Stored files copied, each re-hashed against its name.
pub blob_files: u64,
@@ -1686,6 +1696,9 @@ pub struct AdoptReport {
pub settings_ignored: bool,
/// The source had no lock file, so taking its lock made one.
pub source_lock_created: bool,
/// The copy was moved to this version's journal format (the source's
/// was older; the source keeps its own).
pub format_upgraded: bool,
}
/// The source's writer lock: its lock file opened read-only (so a