From 69b9315e1ad658bf2540abe383a2d759ba18f696 Mon Sep 17 00:00:00 2001 From: zackees Date: Thu, 23 Jul 2026 07:15:27 -0700 Subject: [PATCH] fix(windows): make recovery decisions observable and CLI guidance visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - daemon: log the composed typed recovery request (instance, problem code, flash_completed) or the none-composed summary (device counts) — recovery decisions drive the CLI --admin flow and must be auditable after the fact - cli: recovery guidance printed through output::warn rode the tracing WARN channel, which the default filter suppresses — the user saw no known-unhealthy diagnosis and no --admin hint. Route all recovery messaging through output::diagnostic, whose contract is exactly final operation diagnostics that must stay visible Live-verified on the wedged board: default-policy deploy now prints the exact devnode + problem code and the --admin/manual-BOOTSEL guidance without elevating. Refs #1152 #1149 Co-Authored-By: Claude Fable 5 --- crates/fbuild-cli/src/cli/deploy.rs | 16 +++++++------ .../src/handlers/operations/deploy.rs | 24 +++++++++++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/fbuild-cli/src/cli/deploy.rs b/crates/fbuild-cli/src/cli/deploy.rs index 63b82f46..25aaac6d 100644 --- a/crates/fbuild-cli/src/cli/deploy.rs +++ b/crates/fbuild-cli/src/cli/deploy.rs @@ -275,7 +275,7 @@ async fn maybe_recover_and_retry( let Some(request) = resp.usb_recovery.clone() else { return Ok(resp); }; - output::warn(format!( + output::diagnostic(format!( "deploy target {} is a known-unhealthy Windows devnode{}", request.instance_id, request @@ -312,20 +312,22 @@ async fn maybe_recover_and_retry( match outcome { RecoveryRunOutcome::ManualGuidance => { if policy == fbuild_core::usb::UsbRecoveryPolicy::Default { - output::warn( + output::diagnostic( "rerun with --admin to attempt a scoped one-shot Windows PnP recovery (UAC), or physically re-enter BOOTSEL (hold BOOT, tap RESET) and retry", ); } else { - output::warn("--no-admin: privileged recovery skipped by request"); + output::diagnostic("--no-admin: privileged recovery skipped by request"); } Ok(resp) } RecoveryRunOutcome::RefuseNonInteractive => { - output::warn("scoped PnP recovery needs an interactive Windows session; not elevating"); + output::diagnostic( + "scoped PnP recovery needs an interactive Windows session; not elevating", + ); Ok(resp) } RecoveryRunOutcome::Cancelled => { - output::warn("UAC prompt was cancelled; no recovery was attempted"); + output::diagnostic("UAC prompt was cancelled; no recovery was attempted"); Ok(resp) } RecoveryRunOutcome::Completed(result) => { @@ -345,7 +347,7 @@ async fn maybe_recover_and_retry( result.after, )); if !result.success { - output::warn(format!( + output::diagnostic(format!( "recovery helper reported {}; physical BOOTSEL replug remains the fallback", result .error_code @@ -381,7 +383,7 @@ async fn maybe_recover_and_retry( )); } None => { - output::warn( + output::diagnostic( "no healthy runtime CDC endpoint appeared after recovery; physical BOOTSEL replug remains the fallback", ); } diff --git a/crates/fbuild-daemon/src/handlers/operations/deploy.rs b/crates/fbuild-daemon/src/handlers/operations/deploy.rs index 74a5ec60..8f1820ee 100644 --- a/crates/fbuild-daemon/src/handlers/operations/deploy.rs +++ b/crates/fbuild-daemon/src/handlers/operations/deploy.rs @@ -966,7 +966,7 @@ pub async fn deploy( .await .unwrap_or_default(); let generation = super::deploy_port::rp_generation_for(board.as_ref()); - super::recovery_request::compose_rp2040_recovery_request( + let request = super::recovery_request::compose_rp2040_recovery_request( &devices, &problem_devices, &request_id, @@ -983,7 +983,27 @@ pub async fn deploy( generation, ) }, - ) + ); + // Recovery decisions must be observable after the fact: the request + // (or the reason none was composed) drives the CLI's --admin flow. + match &request { + Some(request) => tracing::info!( + instance = %request.instance_id, + problem_code = ?request.problem_code, + flash_completed = request.flash_completed, + "rp2040 recovery: composed typed exact-device recovery request" + ), + None => tracing::info!( + devices = devices.len(), + unhealthy_devices = devices + .iter() + .filter(|device| device.port_health.is_known_unhealthy()) + .count(), + problem_devices = problem_devices.len(), + "rp2040 recovery: no eligible exact-device recovery target in fresh scan" + ), + } + request } else { None };