diff --git a/FABRIC-3.md b/FABRIC-3.md
index c559263..8b15ff4 100644
--- a/FABRIC-3.md
+++ b/FABRIC-3.md
@@ -107,12 +107,13 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
### From FABRIC-2.md §X, Milestone 2 — USB hardware stack
-- [ ] Decide and implement where the hotplug event surfaces to the rest of the kernel —
- likely a callback registered by whatever owns the home-blocks logic, not xHCI code calling
- into `block_subsystem.c` directly (matching the existing "kernel/Artemis decoupling
- boundary" pattern already documented in `block_subsystem.c`). **Partially addressed by
- Milestone 2h's `blkio_usb.c`/connect-time attach wiring (`FABRIC-2.md`, 2026-08-25) — worth
- re-checking whether that closes this item outright before treating it as still fully open.**
+- [x] **CLOSED 2026-08-27 (FABRIC-3.md §F.3).** Decide and implement where the hotplug event
+ surfaces to the rest of the kernel. Re-checked directly against `sk_repl_idle()` and the
+ `bot_msc_attach_pending`/`bot_msc_detach_pending` doc comments in `xhci_driver.h`: Milestone
+ 2h's flag-set-by-xHCI/flag-consumed-by-`sk_repl_idle()` pattern isn't literally a registered
+ callback, but its own doc comment confirms it was built deliberately to achieve the same
+ goal — *"keeps xhci.c decoupled from block_subsystem.c"* — and that decoupling is real,
+ live on all three arches. Closed as written; no further design work on this node itself.
- [ ] Implement CBW/data/CSW for SCSI WRITE(10) — this is where the earlier "read/write,
unquestionable" requirement actually gets satisfied. Still the single biggest functional
@@ -1386,7 +1387,7 @@ punch list hides that; a graph doesn't.
```mermaid
graph TD
W10["❌ WRITE(10) SCSI support
(Milestone 2 — biggest single blocker)"]
- HOTPLUG["🟡 Hotplug event surfacing
partially addressed, M2"]
+ HOTPLUG["✅ Hotplug event surfacing
CLOSED 2026-08-27, M2 (§F.3)"]
STALL["❌ USB error/stall recovery (M2)"]
M6["✅ Milestone 6 — capsule PKI
DONE 2026-08-26"]
@@ -1450,11 +1451,11 @@ graph TD
classDef unblocked fill:#a83,stroke:#333,color:#fff
classDef open fill:#666,stroke:#333,color:#fff
classDef partial fill:#883,stroke:#333,color:#fff
- class M6,PH8,EXPIRE,MSGSHAPE done
+ class M6,PH8,EXPIRE,MSGSHAPE,HOTPLUG done
class W10,STALL,FIRSTTOUCH,BMAPWRITE,BMAPREAD,UNCLEAN,WIREBIND,BINDSTEP,DETACH,MINT blocked
class CERTVERIFY unblocked
class BMAPFMT,ACLKEY,RUNCAP,MSGMIGRATE,SSDSCOPE,ROUNDTRIP,POLYBLOCK open
- class HOTPLUG,MIGSM partial
+ class MIGSM partial
```
**Legend:** ✅ done · 🔓 unblocked-but-not-built · ❌ not started, no open design question left
@@ -1584,6 +1585,35 @@ from an attached thumbdrive. Doesn't change F.2's design, just its intended reac
remembering when `VMIdentity` actually gets built, so it isn't accidentally scoped narrower
than this.
+### F.3 — `HOTPLUG` (closed)
+
+Re-checked directly against the live code before asking anything, per Captain Bob's own
+standing note not to "grep-sample" — read `sk_repl_idle()` (`starkernel/repl.c:82-190`) and
+the `bot_msc_attach_pending`/`bot_msc_attached`/`bot_msc_detach_pending` doc comments in
+`xhci_driver.h:244-265` in full.
+
+**Finding: closed.** The original Milestone 2 ask was "decide and implement where the hotplug
+event surfaces... likely a callback registered by whatever owns the home-blocks logic, not
+xHCI code calling into `block_subsystem.c` directly." What's built is a flag-set-by-xHCI,
+flag-consumed-by-`sk_repl_idle()` pattern — not literally a registered callback, but the
+`bot_msc_detach_pending` field's own doc comment states the goal directly: *"keeps xhci.c
+decoupled from block_subsystem.c"* — and that decoupling is real and working, confirmed live
+on all three arches (attach: `blkio_usb_open_msc()` → `homeblocks_sig_check()` →
+`blk_subsys_attach_device()`; detach: `blk_subsys_detach_device()`, symmetric shape).
+
+**Confirmed 2026-08-27:** close this node as done. No separate design work remains on
+`HOTPLUG` itself — the answer for `WIREBIND` (the node depending on it) is not "build a new
+hotplug-detection mechanism," it's "reuse this one."
+
+**Handoff note for `WIREBIND`'s own future scoping pass:** the existing attach point already
+calls `homeblocks_sig_check()`, which already distinguishes a recognized home-blocks drive
+(`HOMEBLOCKS_SIG_OK`) from blank/foreign/corrupt media (`HOMEBLOCKS_SIG_BLANK`/
+`BAD_VERSION`/`BAD_CRC`/`READ_ERROR`). That result is the natural branch point for `WIREBIND`:
+a recognized drive continues into cert-verify + `capsule_birth_baby()`; a blank/foreign one
+stays plain read-only block storage, matching the reasoning already documented for the
+`WRITE(10)` refuse-on-blank-media logic. Not built yet — `WIREBIND` still needs its own Q&A
+pass — but it doesn't start from zero.
+
**Two new standing items that came with this, both explicitly deferred (not part of the
current iterative pass, recorded so they aren't lost):**
- **A full codebase audit pass**, using this same iterative Q&A workflow, to scavenge for more