Repository navigation
Description
Outcome
Provide an explicit, one-shot Windows recovery path for a known unhealthy USB device without ever elevating, killing, replacing, or duplicating the long-lived fbuild daemon.
User-facing policy:
fbuild deploy --admin: permit one UAC prompt if and only if deployment identifies a supported recoverable USB PnP state.fbuild deploy --no-admin: never prompt; print exact manual recovery instructions.- no flag: diagnose and recommend rerunning with
--admin; do not surprise the user with UAC. - the flags conflict.
- CI/non-interactive sessions never prompt, even when
--adminwas passed; fail with a clear diagnostic.
This issue is optional recovery layered on the correctness fixes in #1146 and #1147. A device must never become selectable merely because recovery was attempted; it must pass a fresh health and openability check afterward.
Locked architecture
The daemon remains normal privilege
Do not:
- terminate and relaunch the daemon elevated;
- run a second privileged daemon on another port;
- add privilege level to daemon cache identity;
- let an elevated process host HTTP/WebSocket endpoints;
- write build/dependency/zccache caches as administrator.
The existing normal daemon continues to own project state, serial leases, caches, and deployment coordination. A normal fbuild invocation during recovery sees the same normal daemon; the helper is too short-lived to arbitrate with it.
Reuse the shipped fbuild executable as the helper
Implement a hidden internal helper subcommand in fbuild-cli, launched by the normal CLI through Windows ShellExecuteExW with verb runas.
Do not add a workspace crate. Prefer no additional binary: reusing fbuild.exe avoids changes to setup.py, ci/publish.py, native-build staging, checksums, attestations, and wheel scripts.
The elevated process must enter helper mode before daemon discovery/startup. It performs exactly one allowlisted PnP operation, writes a small result, and exits. It must not:
- connect to or spawn fbuild-daemon;
- build, flash, monitor, or open project files;
- initialize package/build caches;
- accept arbitrary commands, executable paths, shell fragments, or registry edits;
- invoke
cmd.exeor PowerShell.
Structured normal/elevated handoff
Add a typed recovery payload to the daemon/CLI operation response (exact type location may vary):
struct UsbRecoveryRequest {
operation_id: String,
instance_id: String,
parent_instance_id: Option<String>,
expected_vid: u16,
expected_pid: u16,
expected_serial: Option<String>,
problem_code: Option<u32>,
flash_completed: bool,
}Required control flow:
- The normal daemon discovers the unhealthy endpoint using fix(serial): preserve Windows PnP health through port probing #1146 and returns a structured request; do not scrape human-readable error text.
- The normal CLI checks policy:
--no-adminor no flag: render manual guidance;--admin+ interactive Windows session: launch its own executable in hidden helper mode with UAC;- CI/non-interactive/non-Windows: do not launch.
- The elevated helper re-queries the current PnP tree and validates every identity field before acting.
- The helper performs one bounded recovery ladder and exits.
- The normal CLI/daemon performs a fresh fix(serial): preserve Windows PnP health through port probing #1146 scan and openability probe.
- A post-flash recovery must continue through a recovery-only path; it must not rebuild or flash the firmware again.
- A preflight recovery may retry the original deploy at most once, with an internal ?recovery already attempted? guard.
- If re-probe still reports phantom/problem/unopenable, stop and print BOOTSEL/replug guidance.
A small POST /api/deploy/recover-port-style normal-daemon endpoint is acceptable for step 6. It must acquire the normal device lease, rescan/reopen, and resume an explicitly requested monitor, but it must never build or flash. Keep any normal-daemon recovery context bounded by operation ID and short expiry.
Privileged operation ladder
Put Windows PnP logic in the existing fbuild-serial Windows module so the CLI helper is thin. Use existing Windows bindings/FFI where possible; adding a dependency requires the repository's normal justification.
For the exact validated target:
- Locate the devnode by canonical instance ID, allowing a phantom lookup.
- Re-query VID/PID, serial, class, presence, problem code, and parent. Abort on any mismatch.
- For a phantom child, request
CM_Reenumerate_DevNodeon its nearest verified live parent. Re-enumerating the parent is allowed; disabling/restarting the parent hub is not. - Poll for a bounded interval and re-query.
- If the exact child is now present but still has a problem, restart only that child. Prefer direct SetupAPI/Config Manager calls; an exact-argument
pnputil.exe /restart-device <instance-id>fallback is acceptable on supported Windows versions. Never route through a shell. - Poll again and return structured before/after health.
Allowed operations are only ?re-enumerate verified parent? and ?restart verified target child.? Not allowed:
- install/update/remove drivers;
- remove a devnode;
- disable or restart a hub/root controller;
- scan or restart all USB devices;
- change power policy or registry values;
- operate on an instance ID that no longer matches the expected board identity.
CM_Reenumerate_DevNode requires elevated privilege on current Windows, which is why this code must stay in the one-shot helper.
Result transport
Use a generated nonce plus a create-new result path or a user-scoped named pipe. The response must contain only:
- operation ID/nonce;
- validated instance ID;
- operation attempted;
- before/after health and problem code;
- success/error code.
The helper must revalidate device identity, so tampering with an unprivileged request cannot expand the allowlist. Delete temporary request/result files after the normal CLI consumes them. Do not persist privileged state in fbuild caches.
Implementation map
crates/fbuild-cli/src/cli/args.rs- add conflicting deploy flags
--adminand--no-admin; - add a hidden internal helper subcommand;
- ensure helper dispatch happens before daemon acquisition.
- add conflicting deploy flags
crates/fbuild-cli/src/cli/deploy.rs- apply the recovery policy to structured daemon responses;
- own UAC launch and retry/recover-only orchestration.
crates/fbuild-cli/src/daemon_client/types.rs- carry the typed recovery request/result; no string parsing.
crates/fbuild-daemon/src/models.rs- deserialize recovery policy with a backward-compatible default;
- serialize the structured recovery request.
crates/fbuild-daemon/src/handlers/operations/deploy.rs- emit the request only for an exact unhealthy target;
- expose recovery-only re-probe/monitor continuation without reflashing.
crates/fbuild-serial/src/ports.rsor a sibling Windows module- identity revalidation and allowlisted PnP operations.
crates/fbuild-cli/src/daemon_client.rs- no change that would spawn an elevated daemon.
If implementation chooses a separate helper binary despite the locked preference, stop and obtain maintainer approval first. It would also require local source-install staging in setup.py, release staging in .github/workflows/template_native_build.yml, wheel assembly through ci/publish.py, checksums/attestation, archive contents, and install tests. A helper that works only from a cargo checkout is not acceptable.
Failure behavior
Every failure must preserve the original deployment facts:
- ?flash not attempted? vs ?flash completed?;
- exact unhealthy instance and problem code;
- UAC cancelled vs helper failed vs re-probe failed;
- no stale deploy port;
- manual BOOTSEL/replug/direct-port instructions.
Cancelling UAC is a normal recoverable outcome, not a daemon failure. It must not leave leases, temporary files, or a pending privileged process.
Automated tests
Abstract both UAC launch and PnP calls so CI uses fakes. Required tests:
--adminand--no-adminconflict;- no flag does not prompt;
--no-adminnever launches helper;--adminlaunches once only for a typed recoverable response;- CI/non-interactive
--adminrefuses without launching; - UAC cancellation is reported and cleaned up;
- helper dispatch does not call daemon discovery;
- mismatched VID/PID, serial, class, instance ID, or parent is rejected;
- arbitrary operation strings/commands are impossible or rejected;
- phantom target uses parent re-enumeration but never parent disable/restart;
- present-problem target restart is scoped to the child;
- helper success is followed by fresh health + open probe;
- helper success with unhealthy re-probe still fails closed;
- preflight retry happens at most once;
- post-flash recovery-only continuation never invokes build/deploy;
- concurrent normal fbuild calls keep using the normal daemon;
- request/result files are create-new, nonce-matched, and removed.
Run:
soldr cargo test -p fbuild-serial
soldr cargo test -p fbuild-cli
soldr cargo test -p fbuild-daemon
soldr cargo clippy -p fbuild-serial -p fbuild-cli -p fbuild-daemon --all-targets -- -D warnings
Deferred integration validation (performed under #1147)
With a deliberately known phantom/problem RP2040 record:
- normal
fbuild deploydiagnoses and recommends--adminwithout UAC; fbuild deploy --no-admingives manual recovery and never prompts;fbuild deploy --adminproduces one UAC prompt;- Task Manager/process diagnostics show no elevated
fbuild-daemon.exe; - helper exits after the one operation;
- the normal daemon PID/endpoint/cache identity remains unchanged;
- a simultaneous normal diagnostic command remains functional;
- after helper success, only a freshly healthy/openable CDC endpoint is returned;
- if Windows cannot revive it, the command falls back to BOOTSEL/replug instructions without claiming a port.
Acceptance criteria
- The daemon is never elevated, bounced, or duplicated for recovery.
-
--adminmeans ?permit one-shot USB recovery,? not ?run fbuild as administrator.? - Default and
--no-adminbehavior are non-elevating and deterministic. - The helper is the existing shipped
fbuild.exein hidden allowlisted mode, or an explicitly approved fully packaged alternative. - Identity is revalidated in the elevated process before every operation.
- No arbitrary command, broad USB reset, driver mutation, or hub restart is possible.
- Post-flash recovery does not reflash.
- Normal daemon leases/caches remain owned by the normal user.
- Automated policy/security/concurrency tests pass without UAC in CI.
- Deterministic tests cover UAC accept/cancel outcomes; live UAC evidence is recorded under fix(rp2040): reject phantom CDC ports and give recoverable BOOTSEL guidance #1147 after the RP2040 integration exists.
Non-goals
- This is not a general administrator mode.
- This is not a driver installer or USB repair suite.
- This does not replace physical BOOTSEL/replug recovery.
- This does not make a known-unhealthy port deployable without a successful fresh re-probe.
- macOS/Linux parity is tracked separately in test(usb): validate port scanning parity on macOS and Linux #1091.
Related issues
- meta: Windows USB phantom-device detection and recoverable native-USB deployment #1149 ? parent meta
- fix(serial): preserve Windows PnP health through port probing #1146 ? health/identity input
- fix(rp2040): reject phantom CDC ports and give recoverable BOOTSEL guidance #1147 ? RP2040 consumer and manual fallback
- fbuild: absorb post-deploy serial wedge recovery into SerialMonitor; deprecate client pyserial #605 ? existing post-deploy recovery seam
- daemon: endpoint isolation follow-up to #940 — key the default endpoint by cache-identity/checkout + deterministic version arbitration #1009 ? daemon identity/isolation behavior that must not be disturbed
Implementation handoff: foundation boundary and security checklist
This child supplies the generic, testable Windows recovery capability after #1146 has merged. It does not make RP2040 deployment invoke recovery; #1147 is the sole integration and live-hardware child. Therefore, #1148 can be closed with deterministic policy/identity/PnP fakes and helper-process tests. The final UAC accept/cancel run belongs to #1147's end-to-end hardware evidence once an RP2040 deployment emits a typed request.
Implement in this sequence:
- Define typed request/result and an explicit recovery policy enum (
default,allow-admin,deny-admin); no parsing of diagnostic strings. - Add the hidden helper dispatch before any daemon discovery in helper mode. The normal CLI may contact the existing normal daemon first to obtain a typed request; the elevated helper itself must not.
- Put identity revalidation and the two allowlisted Config Manager operations behind a narrow Windows trait/function boundary with fake implementations for tests.
- Add the normal-CLI UAC launcher behind an injected launcher interface; tests prove no UAC on default/no-admin/CI/cancel paths.
- Return to the normal process for the fresh enumeration/openability gate. The helper result is advisory, never a port result.
Mandatory security mechanics
- The normal process creates a nonce-bound request/result rendezvous under a user-private directory using create-new semantics. Reject pre-existing paths, nonce/operation-ID mismatch, reparse points, and overlong/unexpected fields; remove both artifacts on every terminal path.
- Launch only the current executable with a fixed hidden helper verb/argument shape through
ShellExecuteExWrunas; no shell, command-line fragments, caller-supplied executable, arbitrary instance ID, or arbitrary operation enum. - The helper re-queries the canonical instance and checks VID, PID, serial when supplied, class, parent, and current health before doing anything. A request-file identity match alone is insufficient.
- Permit only re-enumerating the nearest verified live parent and restarting the verified child. Never disable/restart parent hubs/controllers, install/remove drivers, mutate registry/power policy, or enumerate-reset all USB devices.
- Bound helper execution and every poll. Record before/after health and error code only; do not return a COM port or persist elevated cache/daemon state.
Deterministic proof required before merge
Tests must assert helper dispatch bypasses daemon acquisition; default/no-admin/CI never call the launcher; --admin launches once at most; cancellation cleans artifacts; all identity mismatches are rejected before the PnP backend; only the exact two allowlisted operations are representable; and a successful helper result still requires a later non-elevated healthy/openable re-probe. If packaging/argument routing cannot make the existing shipped executable enter helper mode before daemon startup, stop and request maintainer direction rather than adding a second binary.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Jul 22, 2026
Tracked by meta #1149.
feat(windows): orchestrate one-shot RP2040 USB recovery through deplo…
refactor(platform): migrate device mechanics to the neutral facade
refactor(platform): migrate device mechanics to the neutral facade (#…