fix(startup): surface filesystem failures and verify Sapling param integrity

Three verified daemon-startup edge-case fixes centered on the config/params
filesystem path:

- F7: new non-throwing Platform::ensureDirectory(dir, outError) with one
  consistent "Cannot create <dir>: <reason>. Check permissions / free space."
  message. Replaces the unchecked/throwing create_directories sites at main.cpp
  (pre-init: log + Windows MessageBox + return 1), connection.cpp's
  autoDetectConfig (was the *throwing* overload -- could raise an uncaught
  filesystem_error through its callers; now sets the new
  ConnectionConfig::dir_error), and both app.cpp daemon-dir sites (surface via
  daemon_status_ + return false). The primary connect path (app_network.cpp)
  checks dir_error and shows it instead of mislabelling it "waiting for config".
  embedded_resources.cpp already checked its error_code, so it is left as-is.

- F6: verifySaplingParams() now hash-verifies each param against its pinned
  canonical SHA-256 (source of truth: scripts/build-lite-backend-artifact.sh)
  instead of only checking existence, so a truncated / corrupt-but-present param
  is rejected up front rather than failing later on a shielded operation. A
  <params_dir>/.sapling_verified marker keyed on size:mtime avoids re-hashing
  ~48MB on every startup. Logic extracted to the injectable, unit-testable
  verifySaplingParamsIn(dir, digests); reuses util::sha256Hex (no new hash impl).

- F5: startEmbeddedDaemon() now checks extractEmbeddedResources()'s return and
  the previously-dropped copy_file error_code in the daemon-binary fallback loop,
  aborting with a clear status (sb_daemon_extract_failed / sb_daemon_files_failed)
  instead of failing opaquely at spawn. An absent source file stays non-fatal.

Adds testPlatformEnsureDirectory and testVerifySaplingParams to test_phase4.cpp.
i18n keys added to i18n.cpp (English source of truth); the res/lang/*.json
back-fill via add_missing_translations.py is deferred to a single run at the end
of the batch. Progress tracked in docs/daemon-startup-hardening.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 11:07:32 -05:00
parent b3444e0a89
commit 2675b8ab93
10 changed files with 340 additions and 35 deletions

View File

@@ -241,6 +241,16 @@ void App::tryConnect()
// Auto-detect configuration (file I/O — fast, safe on main thread)
auto config = rpc::Connection::autoDetectConfig();
if (!config.dir_error.empty()) {
// The data directory could not be created (read-only home, permission denied,
// disk full). Retrying won't fix it, so surface it in the status line instead of
// mislabelling it as "waiting for config" below.
connection_in_progress_ = false;
connection_status_ = config.dir_error;
VERBOSE_LOGF("[connect #%d] data dir error: %s\n", connect_attempt, config.dir_error.c_str());
return;
}
if (config.rpcuser.empty() || config.rpcpassword.empty()) {
connection_in_progress_ = false;