feat: end-to-end integrity checks with self-healing; website; installer and Windows fixes
Store::verify compares the whole SQLite projection, every table row for row, against a fresh projection of the journal; verify_and_heal rebuilds a drifted database (keeping the old file) at every server start and via the new books.verify tool, which also re-hashes every blob. The GUI checks the server's database read-only and every stored file, and shows the result. rebuild() also drops stale WAL/SHM files. site/: the wanderbooks website (just site, just publish). install.sh: OS detection (Windows points to WSL2), C compiler and git checks, WANDERBOOKS_REPO. Windows: no directory fsync off Unix; .exe-aware lookup. Repo justfile: check, build, install, demo, site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
1 parent
07f5b8e154
commit
2a976e68a2
25 files changed
+808
-41
No files matched your search
@@ -260,6 +260,43 @@ pub fn find(b: &Books) -> Vec<Attention> {
|
||||
}
|
||||
}
|
||||
|
||||
use ledger_store::DiskCheck;
|
||||
let verify_prompt = "In wanderbooks, run books.verify and tell me what it found.".to_string();
|
||||
match &b.integrity.disk {
|
||||
DiskCheck::Differs(why) => out.push(Attention {
|
||||
severity: Severity::Problem,
|
||||
title: "The database doesn't match the journal".into(),
|
||||
detail: format!("{why}. The journal is the record; the server rebuilds the database from it (books.verify, or on its next start)."),
|
||||
target: Some(Route::Journal),
|
||||
prompt: verify_prompt.clone(),
|
||||
}),
|
||||
DiskCheck::Unreadable(why) => out.push(Attention {
|
||||
severity: Severity::Problem,
|
||||
title: "Couldn't check the database".into(),
|
||||
detail: format!("{why}."),
|
||||
target: Some(Route::Journal),
|
||||
prompt: verify_prompt.clone(),
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
if !b.integrity.file_failures.is_empty() {
|
||||
let ids: Vec<String> = b
|
||||
.integrity
|
||||
.file_failures
|
||||
.iter()
|
||||
.map(|(id, _)| id.to_string())
|
||||
.collect();
|
||||
out.push(Attention {
|
||||
severity: Severity::Problem,
|
||||
title: format!("{} damaged or missing", fmt::count(ids.len(), "stored file is", "stored files are")),
|
||||
detail: format!(
|
||||
"Attachment {}: the bytes on disk don't match their recorded hash. Restore them from a backup (the books dir's git history).",
|
||||
ids.join(", ")
|
||||
),
|
||||
target: Some(Route::Documents),
|
||||
prompt: verify_prompt,
|
||||
});
|
||||
}
|
||||
out.sort_by_key(|a| a.severity);
|
||||
out
|
||||
}
|
||||
Reference in new issue
Block a user