vault-snapshot
Linux-only snapshot/promotion skill for the vault. Materializes root projections through the live vault, syncs direct S3 content into the Git worktree, strips active delete-intent paths before staging, commits, and pushes to GitHub with rebase-based merge. Enforces the single-writer-git invariant: only the designated snapshotter host per fleet may run this.
When to use
- On the designated snapshotter host (e.g., sg01) during hand-migration from the legacy Hermes cron script
- Verifying snapshot guardrails before enabling the timer
- Testing snapshot logic on a non-production Linux host
Guardrails
platform_require linux— hard block on non-Linux hosts. The snapshot script depends on Linux-specific tools and the rclone FUSE mount path convention.--max-delete 10— NON-NEGOTIABLE clamp onrclone sync(and by extensionrsync --delete) to prevent mass deletion during S3 inconsistency events. Reference:raw/transcripts/2026-05-23-bug-sg01-snapshot-destructive-rclone-sync.md.- Single-writer-git — only one host per fleet may act as snapshotter. Fleet role enforcement via
fleet.yaml. - Delete-intent no-resurrection — active Git tombstones always strip matching paths from the snapshot worktree before
git add, even when the optional S3 prune is unavailable. - Bound attempts, not successes —
WIKI_SNAPSHOT_MAX_TOMBSTONE_PRUNESdefaults to 10 and caps exactdeletefilecalls. The snapshot intersects active tombstones with a successful recursive remote inventory, so already-absent keys cause zero delete calls. - Unknown is not absent — a failed remote inventory skips optional tombstone pruning with an explicit warning; it never becomes an empty-set proof.
- Projection parity is a promotion barrier — after live
index.mdandlog.mdmaterialization, the script freezes those bytes, reads both objects through the direct S3 remote until their SHA-256 values match underWIKI_SNAPSHOT_PROJECTION_PARITY_TIMEOUT_SECONDS(default 120), with each object read bounded byWIKI_SNAPSHOT_PROJECTION_READ_TIMEOUT_SECONDS(default 15), then syncs. It requires byte-identical worktree files plus a read-onlyskillwiki projections materialize <worktree>preview withindex_drift=falseandlog_drift=falsebefore staging. A fixed sleep, FUSE cache refresh, object existence, or queue-empty observation is not sufficient. - Git proof stays in the convergence worktree — the script records the canonical mutation/convergence roots, Git dir/common-dir, and frozen base OID; rechecks that receipt before staging; and refreshes
origin/mainafter push or no-change before emittingSNAPSHOT_COMPLETE result=success.
Steps
- Platform check —
platform_detect_os; platform_require linux. - Read fleet.yaml via
fleet_load. Confirm this host's role issnapshotter. If not, abort with message. - Run guard verification — call
wiki_snapshot_assert_guardsagainst the snapshot script body to verify--max-deleteis present. - Execute snapshot script — call
wiki-snapshot.sh(in$(platform_share_dir)/bin/). Pass--dry-runif the skill was invoked with--dry-run. - Verify projection proof — on a live run, require
projection direct-store parity confirmed,projection worktree parity confirmed, andprojection semantic preview confirmedbefore the terminal success record. Hashes are non-sensitive byte-integrity evidence; do not log object contents. - Verify delete-intent summary — expect one stable line with
active,inventory_ready,remote_present,attempted,pruned,already_absent,failed, anddeferredcounters. In a zero-live-key steady state,attempted=0and no tombstoneobject not foundblock should appear. - Log results — write to
$(platform_log_dir)/wiki-snapshot.log. Include start timestamp, exit code, parity evidence, and summary.
Execution
# Interactive (Claude Code / Codex) — dry-run by default
/vault-snapshot --dry-run
# Interactive — execute
/vault-snapshot
# Terminal (after install)
bash $(platform_share_dir)/bin/wiki-snapshot.sh --dry-run
bash $(platform_share_dir)/bin/wiki-snapshot.sh
Dead managed-write lock recovery
If root projection materialization fails with SYNC_LOCK_HELD, do not edit the
snapshot worktree or remove the lock by age. Follow
docs/managed-write-lock-reclaim-runbook.md
for the attended dead-PID-only backup, reclaim, systemd restart, and doctor
closeout procedure.
Hand-migration checklist
When migrating from sg01's legacy wiki-snapshot-v3.sh:
- Copy the rsync+git+push body from
/root/.hermes/scripts/wiki-snapshot-v3.shinto$(platform_share_dir)/bin/wiki-snapshot.sh. - Verify
--max-delete 10is present in the rclone/rsync delete call. - Replace
flockwithlockfile_acquirefromlib/lockfile.sh. - Replace hardcoded paths with
platform_*helpers. - Source
lib/platform.shat the top of the script. - Test on sg01:
sudo -u hermes bash wiki-snapshot.sh --dry-run. - Enable the systemd timer:
systemctl enable --now wiki-snapshot.timerfor system scope, orsystemctl --user enable --now wiki-snapshot.timerfor user scope. Default snapshotter cadence is every 30 minutes at*:02and*:32.