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
@@ -1375,6 +1375,43 @@ impl BooksServer {
|
||||
Ok(Json(SearchEntriesOut { entries }))
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "books.verify",
|
||||
description = "Check the books end to end: the journal's hash chain; a fresh replay against the live ledger; every table of the SQLite database, row for row, against a fresh projection of the journal (rebuilding the database from the journal if it drifted, keeping the old file); and, by default, every stored receipt and document against its SHA-256. Run it whenever a number looks wrong or a human asks whether the books are intact."
|
||||
)]
|
||||
async fn verify(&self, params: Parameters<VerifyParams>) -> Result<Json<VerifyOut>, String> {
|
||||
let mut store = self.lock()?;
|
||||
let report = store.verify_and_heal().map_err(|e| e.to_string())?;
|
||||
let blobs = if params.0.blobs {
|
||||
Some(store.verify_blobs().map_err(|e| e.to_string())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut checked = vec![
|
||||
format!("journal hash chain ({} events)", report.events),
|
||||
"fresh replay matches the live ledger (every statement, assets, classes, payroll)"
|
||||
.to_string(),
|
||||
"SQLite database matches a fresh projection of the journal, every table, row for row"
|
||||
.to_string(),
|
||||
];
|
||||
if let Some(n) = blobs {
|
||||
checked.push(format!("{n} stored files match their SHA-256"));
|
||||
}
|
||||
Ok(Json(VerifyOut {
|
||||
status: if report.healed.is_some() {
|
||||
"healed"
|
||||
} else {
|
||||
"ok"
|
||||
}
|
||||
.to_string(),
|
||||
events: report.events,
|
||||
head: report.head,
|
||||
healed: report.healed,
|
||||
blobs_verified: blobs,
|
||||
checked,
|
||||
}))
|
||||
}
|
||||
|
||||
#[tool(
|
||||
name = "books.digest",
|
||||
description = "What changed since a date: entries posted, per-account movement, and receipt coverage. Built for human review.",
|
||||
|
||||
Reference in new issue
Block a user