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:
1 parent
5bc0633cdd
commit
019d8d76a3
29 files changed
+6443
-136
No files matched your search
+75
-1
@@ -352,7 +352,8 @@ seals a new vault record, every time, so none carries it;
|
||||
`idempotent_tools_change_nothing_when_repeated` calls each candidate twice
|
||||
to check). Every tool's
|
||||
`openWorldHint` is false: the books are a closed world, and no tool
|
||||
reaches beyond them. `books.export_cpa_package`, `books.vendor_1099`
|
||||
reaches beyond them, but `books.back_up_now`, which pushes the books to
|
||||
the owner's backups (true). `books.export_cpa_package`, `books.vendor_1099`
|
||||
and `books.get_w9` keep a file for the agent (an export) and record the
|
||||
ones with TINs, so they are not read-only. No tool carries
|
||||
`requiresUserInteraction` (owner decision
|
||||
@@ -584,6 +585,79 @@ through the policy.
|
||||
every stored file. The server runs the same check, and heals the same
|
||||
way, every time it starts.
|
||||
|
||||
## Tools — backups
|
||||
|
||||
Books served from the data root back themselves up (plan 3.7, D-30,
|
||||
D-31): each keeps `archive.git`, a bare repository whose commits are its
|
||||
journal, stored files, vault, keys and access log as they were (one
|
||||
commit per backup, made under the store's lock, its message
|
||||
`seq N head H` and, below, `book <id>`), pushed fast-forward only to the targets the settings
|
||||
name: `backup.git_remote` (the owner's private repository) and
|
||||
`backup.folder` (a repository `<book>.git` in a folder on another disk).
|
||||
Not encrypted: the destinations keep it private. Backups run a little
|
||||
after the books are last written (`--backup-quiet`, 30 s), when they
|
||||
open, before they close, and on request; a failed one is retried after
|
||||
15 minutes. Each target is drilled weekly: cloned, its chain replayed,
|
||||
every stored file re-hashed, every TIN record the books name there with
|
||||
its key, its head the one its commit records, its journal a byte prefix
|
||||
of the live one (a failed drill is tried again at the server's next
|
||||
hourly look, once 15 minutes have passed). A backup's tree is checked
|
||||
before any of it is written out: plain files under the backed-up paths
|
||||
only, never a link or a submodule. git runs with no hooks (in a
|
||||
repository on this computer it pushes to as well), no ignore or
|
||||
attributes rules of anyone's (a `.gitattributes` in a book's folder
|
||||
refuses the backup) and no command-running transports; a repository on
|
||||
this computer is used only if plain (a bare repository at that very
|
||||
path, no settings of its own but where a copy came from, nothing sending
|
||||
git elsewhere, no link where it or its parts are), and a backup folder that took backups and is gone is
|
||||
reported as a disk not connected, never made again (restored books count
|
||||
the backups of the books they were). Books restored beside the books they
|
||||
were (under another id) don't push to those books' repository, which
|
||||
would stop theirs: `books.backup` says it is paused for them until
|
||||
another is named. A damaged backup record is set aside and begun again;
|
||||
it never keeps the books shut. One book's drill holds the others'
|
||||
backups until it ends (a backup at close waits at most a minute for
|
||||
it). A target
|
||||
whose push failed is left out of the backups after writes for 15
|
||||
minutes, then tried again, written to or not; the other targets don't
|
||||
wait for it. Every
|
||||
commit, push and drill is a record in the book's `ops/backup.jsonl` (a
|
||||
chain log; a backup never moves the books' head), each target's records
|
||||
keyed by the target itself. Setting `backup.*` (books.set_setting, or
|
||||
books.import_settings with a `[backup]` table) needs books:admin, as
|
||||
backing up does: a backup is the whole book, its vault and key included.
|
||||
Over stdio neither tool is offered.
|
||||
|
||||
- `books.backup {}` (books:read) → `{ head_seq, targets: [{ target:
|
||||
"repository" | "folder", at, last_ok_at, through_seq, stale,
|
||||
last_error, last_drill_at, drill_error, paused }], note }`. `paused`:
|
||||
not backed up there, these books being restored as a copy of others
|
||||
(its `last_error` says so). `stale`: the target
|
||||
lacks the head, and the first try to back up what it lacks (since its
|
||||
last good backup, or ever) is more than a day old. `last_error`: its
|
||||
last push's, or a later failed commit's. `note`
|
||||
says what to set when no target is. `books.start` adds a next step when
|
||||
none is set up, or one is stale, failing, paused or failed its drill.
|
||||
- `books.back_up_now { new_disk? }` (books:admin) → the same, after
|
||||
backing up to every target now. `new_disk`: the backup folder's disk is
|
||||
new, so a folder that took these books' backups and holds none now is
|
||||
begun again (otherwise it is a disk not connected). The one tool whose
|
||||
`openWorldHint` is true. A target paused for books restored as a copy
|
||||
of others shows `paused` (and is no failure to retry).
|
||||
|
||||
Restoring is the owner's, from a terminal: `wanderbooks books restore
|
||||
BACKUP [--id ID] [--replace] [--yes]` (the admin socket's `restore`,
|
||||
previewed then confirmed against the previewed head; without `--id`, the
|
||||
books keep the id of the books the backup holds, which the preview names,
|
||||
else the repository's name; under another id they are a copy, and don't
|
||||
push to those books' repository). The backup is
|
||||
cloned into the root, proven whole as a drill proves it (but for the
|
||||
live-journal check: there may be no live books), and built as an
|
||||
adoption builds; it arrives as new books, or replaces books (`--replace`,
|
||||
which are let go and moved whole into `legacy/<id>-<time>/`). The
|
||||
restored books keep the backup's history, so their next backups carry
|
||||
on from it.
|
||||
|
||||
## Tools — payouts
|
||||
|
||||
- `books.record_payout { date, description, deposit_account, deposit,
|
||||
|
||||
Reference in new issue
Block a user