P2 slice 7: backups and restore (D-30, D-31)

The server backs each book up itself: a per-book git archive, pushed
fast-forward only to a git remote and to a folder, when the books open,
after a quiet spell, before they close and on request; every commit,
push and drill is a record in ops/backup.jsonl. Drills clone each
target and verify the chain, the blobs, the vault and the head, weekly
and after a failure. `wanderbooks books restore` previews, then
restores beside or (with --replace) over, never moving books something
holds. The archive refuses anything but a plain bare repository and
pins git against the owner's configuration. books.backup and
books.back_up_now for the agent.

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-20 11:14:21 -06:00
1 parent 5bc0633cdd
commit 019d8d76a3
29 files changed
+6443 -136

No files matched your search

+18
View File
@@ -803,6 +803,24 @@ impl BooksServer {
"An open item is due within 30 days, or overdue: raise it with the human.",
));
}
// The books' backups (backup.rs): none set up, or one behind or
// failing.
if let Some(b) = &self.backups {
let st = b.standing(store);
if st.note.is_some() {
next_steps.push(step(
"books.backup",
None,
"No backups are set up: ask the human for a private repository and a folder on another disk, then set backup.git_remote and backup.folder (books.set_setting).",
));
} else if st.needs_attention() {
next_steps.push(step(
"books.backup",
None,
"A backup is behind or failing: books.backup says which and why; tell the human.",
));
}
}
match focus {
"close" => next_steps.push(step(
"books.conventions",