Skip to content

feat(windows): recover RP2040 phantom/BOOTSEL USB state through scoped admin PnP flow #1152

Description

@zackees

Context\n\nThe Windows recovery design is incomplete in practice. The elevated helper from #1148 is merged, but no deployment path creates a UsbRecoveryRequest, launches the helper, consumes its result, rescans, or retries deployment. Consequently fbuild detects the problem but cannot recover this attached RP2040 without asking the user to manipulate BOOTSEL.\n\nObserved on the attached board:\n\n- runtime identity: 2E8A:000A, serial 5303284720C4641C;\n- build port scan reports historical COM12 as health=phantom selectable=no;\n- the stock ROM is already in BOOTSEL: the mounted RPI-RP2 volume is G:;\n- fbuild's stock UF2 path receives Windows error 1006 (ERROR_FILE_INVALID) while writing G:\NEW.UF2;\n- managed picotool sees an RP2040 BOOTSEL device at bus 1/address 12 but cannot connect because its Windows interface/driver is unavailable;\n- the current candidate correctly rejects explicit COM12 as an unusable phantom before any serial touch. That protects the board but does not unwind Windows.\n\nThe user explicitly does not want a BOOTSEL button sequence as the normal recovery path. The objective is to use least-privilege, explicit UAC-backed host recovery to force a fresh Windows enumeration and resume fbuild deployment when that is possible.\n\n## Existing foundation\n\n#1148 / PR #1151 already provides the secure primitives:\n\n- typed UsbRecoveryRequest / UsbRecoveryResult and policy types in build-core;\n- strict identity revalidation and allowlisted ReenumerateParent / RestartTarget operations in build-serial::usb_recovery;\n- a one-shot UAC helper, private nonce-protected rendezvous files, and --admin / --no-admin policy plumbing in build-cli.\n\nWhat is missing is orchestration. The daemon never returns a typed recovery request, and normal CLI deployment never invokes launch_once_for_typed_request or performs the mandatory fresh post-helper scan/openability gate.\n\n## Proposal\n\nImplement the Windows recovery-only deployment flow for RP2040:\n\n1. When the exact RP2040 identity is known-unhealthy (phantom or present/problem), the daemon constructs a typed request from the currently scanned instance ID, parent, VID/PID, serial, class, health/problem code, and operation ID. Diagnostic visibility remains separate from selection eligibility.\n2. With default policy, fail closed and print the normal non-privileged guidance. With build deploy --admin, return/consume only that typed request and launch the existing one-shot elevated helper. Do not elevate or restart the long-lived daemon.\n3. After helper completion, discard all prior port/volume facts. Perform bounded fresh scans of both runtime CDC and RP2040 BOOTSEL transports, require health eligibility plus a bounded openability probe for CDC, then retry the original deployment at most once without rebuilding.\n4. For the observed BOOTSEL ERROR_FILE_INVALID case, use the same exact-device/verified-parent re-enumeration path before a single fresh stock-transport attempt. Never retry an old G: handle and never use a stale COM12.\n5. If the Windows driver binding still prevents PICOBOOT access after re-enumeration, report the exact interface/driver state and a safe, actionable next step. Do not silently install third-party drivers, change global USB policy, reset all hubs, or claim recovery succeeded.\n6. Preserve the daemon's normal privilege and cache boundaries. A non-admin invocation after helper completion must work normally and must not inherit privileged daemon state.\n\n## Acceptance criteria\n\n- [ ] A phantom COM12 for the attached serial 5303284720C4641C yields a typed exact-device recovery request; default and --no-admin do not elevate or touch it.\n- [ ] build deploy --admin prompts through the existing one-shot helper only; it never elevates/restarts the daemon and never exposes a generic privileged command surface.\n- [ ] The helper revalidates the exact target and parent before acting; unrelated, missing, changed-class, changed-VID/PID, or changed-serial devices are rejected.\n- [ ] After recovery, fbuild discards stale COM/volume facts, rescans, and attempts deployment once only through a fresh eligible/openable endpoint or newly enumerated BOOTSEL transport.\n- [ ] The exact observed state—phantom runtime CDC plus BOOTSEL G: error 1006 and inaccessible PICOBOOT—either recovers without pressing BOOTSEL or produces a precise driver/interface diagnosis. It must not ask for BOOTSEL button use before attempting the permitted PnP recovery.\n- [ ] No stale FBUILD_DEPLOY_PORT, COM name, or mounted-volume handle is returned or retried.\n- [ ] Unit tests fake PnP state transitions, helper result validation, re-enumeration timeout, stale-state discard, one-retry limit, default/no-admin behavior, and the non-admin-after-admin cache/daemon boundary.\n- [ ] Windows hardware proof uses only build port scan and �ash autoresearch rp2040 --rpc-smoke --timeout 120s --skip-lint, with a unique five-digit FBUILD_DAEMON_PORT. Record pre/post health, PnP operation, fresh transport identity, deploy result, and RPC result.\n- [ ] The issue is not closed from static/unit evidence alone while this board remains attached.\n\n## Non-goals\n\n- No raw pyserial, picotool, direct file-copy, Device Manager scripting, Zadig automation, or broad

eset all USB command.\n- No unconditional driver installation or driver rebinding. If a standard Windows driver/binding remains the blocker, diagnose it explicitly and track any driver-install design separately.\n- No physical BOOTSEL-button sequence as the normal successful recovery path.\n- No privileged daemon, privileged cache, or persistent elevation.\n\n## Implementation map\n\n- crates/fbuild-daemon/src/handlers/operations/deploy.rs: produce a typed recovery-needed outcome only from fresh exact scan facts; keep flash/recovery phases separate.\n- crates/fbuild-daemon/src/models.rs and crates/fbuild-cli/src/daemon_client/types.rs: add a backward-compatible typed response field.\n- crates/fbuild-cli/src/cli/deploy.rs and cli/usb_recovery.rs: invoke #1148 exactly once for --admin, render result, then reissue the normal deployment/recovery flow.\n- crates/fbuild-deploy/src/rp2040.rs and
p2040_target.rs: expose only safe recovery facts; retain the health/openability gates from #1147.\n- crates/fbuild-serial/src/usb_recovery.rs: extend only if the BOOTSEL composite-interface identity requires a new narrow, revalidated operation; do not broaden the allowlist casually.\n\n## Related issues\n\n- #1149 — parent Windows USB recovery meta\n- #1148 — merged elevated helper foundation\n- #1147 — phantom CDC fail-closed state machine\n- #1081 — RP2040 BOOTSEL synthetic-volume transport failures\n

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions