fix(desktop): defer event sync off setup path

Finding: L2 event_sync boot reconcile did best-effort JSON reads, SQLite retention checks/writes, and event signing synchronously during Tauri setup after identity resolution.

Snapshot the resolved owner keys, then spawn the reconcile after setup-critical boot work begins. The reconcile runs on spawn_blocking so no filesystem, SQLite, or signing work occupies the GUI setup path or an async worker. Secret-store identity resolution remains synchronous and out of scope by design.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf
2026-07-08 10:45:39 -04:00
co-authored by Tyler Longwell
parent 13578737fe
commit 7d2cdba6f0
2 changed files with 26 additions and 3 deletions
+18
View File
@@ -19,6 +19,24 @@ pub fn run_event_sync(app: &tauri::AppHandle, owner_keys: &nostr::Keys) {
crate::managed_agents::reconcile::reconcile_agents_to_events(app, owner_keys);
}
/// Spawn the best-effort event reconcile off the synchronous Tauri setup path.
///
/// The owner keys are cloned before spawning so the task never touches the
/// `AppState::keys` mutex. The reconcile itself is still synchronous JSON,
/// SQLite, and signing work, so it runs on the blocking pool rather than an
/// async worker.
pub fn spawn_event_sync(app: tauri::AppHandle, owner_keys: nostr::Keys) {
tauri::async_runtime::spawn(async move {
if let Err(e) = tauri::async_runtime::spawn_blocking(move || {
run_event_sync(&app, &owner_keys);
})
.await
{
eprintln!("buzz-desktop: event-sync: spawn_blocking failed: {e}");
}
});
}
/// Reconcile `personas.json` into the persona-event retention store.
///
/// Must run AFTER `migrate_packs_to_teams` (depends on field renames being
+8 -3
View File
@@ -226,14 +226,13 @@ pub fn run() {
resolve_persisted_identity(&app_handle, &state)
.map_err(|e| -> Box<dyn std::error::Error> { e.into() })?;
// Sync team-dir edits and reconcile persona/team events. Needs the
// resolved owner keys, so it runs after identity resolution.
// Snapshot owner keys after identity resolution; the best-effort
// event reconcile itself runs off the synchronous setup path below.
let owner_keys = state
.keys
.lock()
.map(|k| k.clone())
.map_err(|e| -> Box<dyn std::error::Error> { e.to_string().into() })?;
event_sync::run_event_sync(&app_handle, &owner_keys);
// Backfill the pinned persona snapshot for any pre-existing agent
// that predates the record-authoritative-spawn cutover (persona_id
@@ -332,6 +331,12 @@ pub fn run() {
try_regenerate_nest(&app_handle);
// Sync team-dir edits and reconcile persona/team/agent events after
// setup can continue. It is best-effort retention backfill, unlike
// identity resolution above, so JSON/SQLite/signing work must not
// hold the boot path hostage.
event_sync::spawn_event_sync(app_handle.clone(), owner_keys);
if let Some(mgr) = huddle::models::global_model_manager() {
mgr.start_stt_download(state.http_client.clone());
mgr.start_tts_download(state.http_client.clone());