feat(host): the daily update check and the staged update (P11a step 4)

While the books are open the host looks at the release feed at most once
a day (knob update_check, noted in updates/checked so a restart does not
look again), and only downloads and verifies a newer release into
<root>/updates/staged/ (sha256 AND minisign signature, as for update).
It never swaps: the staged program is applied (a) by an opener just
before it starts a host, when no host runs, so no call is cut off, after
a second check against the key (a staged file that fails or does not
start is dropped), and (b) by wanderbooks update, the owner's say-so,
which reuses the staged copy instead of downloading again. A local
release can be named in <root>/updates/feed (file:// only, with its key
file, same rule as --trusted-key). The program's version is set by main
since packages rewrite only the cli's. No new dependency.

Tests (crates/ledger-cli/tests/update.rs): the_host_stages_a_daily_update_and_the_next_start_applies_it,
a_daily_update_that_is_not_ours_is_not_staged,
update_installs_the_copy_the_host_staged_without_downloading_again,
a_staged_update_that_was_tampered_with_is_dropped_not_applied.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 16:23:40 -06:00
1 parent 562b893c64
commit 6e7deb15c7
6 files changed
+454 -15

No files matched your search

+4
View File
@@ -205,6 +205,10 @@ pub fn start_host(
let found = ledger_store::root::Root::find(root).map_err(|e| e.to_string())?;
let run = found.run_dir().map_err(|e| e.to_string())?;
let home = ledger_store::root::account_home().map_err(|e| e.to_string())?;
// A staged update is applied here and nowhere while books are open:
// no host runs now, so no call is cut off (update.rs INVARIANT 9). A
// failure leaves the program as it was.
let _ = crate::update::apply_staged(&found, crate::update::program_version());
let exe = this_executable()?;
let mut args: Vec<std::ffi::OsString> = vec!["host".into()];
// An explicit root goes on as found (absolute): the host starts in /.