diff --git a/FABRIC-3.md b/FABRIC-3.md
index e1079b4..a26f129 100644
--- a/FABRIC-3.md
+++ b/FABRIC-3.md
@@ -159,7 +159,13 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
gets written to the device, real and unstubbed. Blocked only on `WRITE(10)` itself, already
modeled in the graph — nothing else stands between this and working.
-- [ ] Implement reading/validating the block-map from a drive on insertion.
+- [x] **CLOSED 2026-08-27 (`FABRIC-3.md` §F.13) — already built, not a separate item.**
+ `cache_load_devblock()` already unpacks and validates every devblock's metadata on load.
+ Tracing "on insertion" surfaced a real, separate, previously-unflagged conflict instead:
+ `homeblocks_sig_t` and the block-subsystem's own generic `'STFR'`/v2 header both wanted
+ devblock 0, which would leave a home-blocks drive permanently write-refused or overwrite its
+ own identity header. Fixed by relocating `homeblocks_sig_t` to devblock 1; `MINT` must also
+ run the ordinary format-confirm path at devblock 0.
- [ ] Design the migration state machine (Section U item 5) — states, transition triggers.
Session direction, 2026-08-25: **ACL manages *when* to relocate** (capacity pressure, or a
@@ -1438,7 +1444,7 @@ graph TD
FIRSTTOUCH["❌ First-touch identity→block-range
allocation (M3) — SCOPED 2026-08-27 (§F.11)"]
BMAPFMT["❌ On-drive block-map format (M3)
SCOPED 2026-08-27 (§F.4) — repurpose blk_meta_t"]
BMAPWRITE["✅ Write block-map to drive (M3)
CLOSED 2026-08-27 (§F.12) — already built, blocked only on W10"]
- BMAPREAD["❌ Read/validate block-map on insert (M3)"]
+ BMAPREAD["✅ Read/validate block-map on insert (M3)
CLOSED 2026-08-27 (§F.13) — already built"]
MIGSM["🟡 Migration state machine (M3)
partially answered: ACL decides *when*"]
UNCLEAN["❌ Unclean-removal handling (M3)
DESIGN CLOSED 2026-08-27 (§F.10), code not yet written"]
@@ -1471,6 +1477,7 @@ graph TD
BMAPFMT --> BMAPWRITE
BMAPFMT --> BMAPREAD
BMAPFMT --> FIRSTTOUCH
+ BMAPREAD -.->|found: sig must relocate off devblock 0, §F.13| MINT
CERTVERIFY --> RUNCAP
RUNCAP --> MINT
@@ -1495,8 +1502,8 @@ 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,HOTPLUG,BMAPWRITE done
- class W10,STALL,FIRSTTOUCH,BMAPREAD,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP,UNCLEAN blocked
+ class M6,PH8,EXPIRE,MSGSHAPE,HOTPLUG,BMAPWRITE,BMAPREAD done
+ class W10,STALL,FIRSTTOUCH,WIREBIND,BINDSTEP,DETACH,MINT,BMAPFMT,CERTVERIFY,RUNCAP,UNCLEAN blocked
class ACLKEY,MSGMIGRATE,SSDSCOPE,ROUNDTRIP,POLYBLOCK open
class MIGSM partial
```
@@ -1559,6 +1566,13 @@ finished). Dashed arrows = softer "gates/informs" relationships.
`blk_meta_t` accessors instead of a new table meant "write the map" and "write a block" were
never actually two operations. The only reason this hasn't been seen working is `WRITE(10)`
itself, already the graph's known hub.
+- **`BMAPREAD` closed the same way, but tracing it surfaced a real, load-bearing bug that had
+ nothing to do with reading at all** (§F.13) — `homeblocks_sig_t` and the block-subsystem's
+ own generic disk header physically collide at devblock 0. Every earlier node that touched
+ `homeblocks_sig_t` (`CERTVERIFY`, `WIREBIND`, `RUNCAP`, `MINT`) inherited this without anyone
+ noticing, because each only traced `homeblocks_sig_check()` itself, never the ordinary attach
+ path running alongside it. Fixed now (relocate to devblock 1) rather than left to surface
+ later as a real, confusing runtime failure.
**Not yet done:** an ordered plan (which node to attack first, given the graph). Per Captain
Bob's own framing, that's the next pass — "start asking and answering questions iteratively
@@ -1870,6 +1884,10 @@ resolved before any design decision was needed:
cert + identity-source regions; everything past that is the user's own general block-storage
pool directly, consistent with `FIRSTTOUCH`'s pool clarification in §F.6). No GPT-parser
node needed in the graph at all — this closes that thread rather than deferring it.
+ **Correction, 2026-08-27 (§F.13): `homeblocks_sig_t` itself moved off devblock 0** to
+ devblock 1 (forth-block 4) once a real collision with the block-subsystem's own generic
+ disk header was found — the "devblocks `0..N`" metadata-region framing above still holds,
+ it just now starts one devblock later than stated here.
2. **Single-device USB driver.** `xhci_dev_t`'s BOT/MSC state (`device_ctx`, `bulk_in_ring`,
`bulk_out_ring`, `bot_data_buf`) are singular fields, not per-slot arrays — only one USB MSC
device is usable at a time today. This looked like it might conflict with the vision's
@@ -2097,6 +2115,44 @@ node may have its own distinct question (e.g. whether a home-blocks drive needs
validate-on-insert step beyond ordinary lazy per-devblock read-through-cache), left for its own
pass rather than assumed away.
+### F.13 — `BMAPREAD` (closed — already built) — and a real header collision found and fixed
+
+Confirmed the handoff note from `F.12`: `cache_load_devblock()` (`block_subsystem.c:308-322`)
+already unpacks and validates every devblock's metadata on load (`meta_from_slice()`,
+magic-checked, resets cleanly on a bad/missing magic) — the read side is exactly as "already
+built, not a separate item" as the write side. Same closure, no new mechanism needed.
+
+**But tracing "validate on insertion" turned up a real, previously-unflagged conflict that has
+nothing to do with `blk_meta_t` at all.** `blk_subsys_attach_device()` — the *general* attach
+path every device goes through, USB drives included — always calls `blk_format_or_load_disk()`
+(`block_subsystem.c:581-613`), which reads devblock 0 looking for the block-subsystem's own
+`'STFR'`/v2 volume header. `homeblocks_sig_t`'s magic is `'LAHB'`, not `'STFR'`, so a minted
+home-blocks drive always reads as "unrecognized" here and lands in `BLK_FMT_PROVISIONAL` —
+and `blk_get_buffer()` (`block_subsystem.c:894`) **refuses all writes** to a `PROVISIONAL`
+slot until `blk_subsys_confirm_format()` is called. That function
+(`blk_commit_format()`, `block_subsystem.c:546-571`) writes a fresh STFR header to the *same*
+devblock 0 — which would physically overwrite `homeblocks_sig_t`. Neither outcome (stay
+`PROVISIONAL` forever, or get STFR-formatted and lose the identity header) works. This was
+invisible to `CERTVERIFY`/`WIREBIND`/`MINT` because each only ever traced
+`homeblocks_sig_check()` itself, never the generic attach path running alongside it.
+
+**Decision made 2026-08-27:** `homeblocks_sig_t` relocates to **devblock 1** (forth-block 4),
+leaving devblock 0 for the generic STFR/v2 header untouched — no changes to the already-tested
+block-subsystem code. `homeblocks_sig_check()` already takes its starting forth-block as a
+plain parameter (`sig_start_fblock`, not hardcoded), so this is a call-site change, not a
+format-logic change. `MINT` (§F.8) must also run the ordinary `blk_subsys_confirm_format()`
+path at mint time (writing a ordinary fresh STFR/v2 header at devblock 0) so the drive is
+immediately writable through the normal block-buffer path, in addition to writing
+`homeblocks_sig_t`/cert/identity-source starting at devblock 1. `F.8`'s own text has been
+corrected in place to point here rather than restate this.
+
+**Not yet scoped (deferred within this node):** whether `MINT`'s STFR-format-then-sig-write
+sequence needs a specific ordering/atomicity guarantee (e.g. what state a drive is left in if
+power is lost between the two writes) — real question, not addressed here; the exact
+`sig_start_fblock` value call sites (`CERTVERIFY`, `WIREBIND`, `RUNCAP`, `MINT`) all need
+updating from their currently-recorded devblock-0-relative assumption to devblock-1, a
+mechanical follow-up once any of this is actually coded, not re-litigated here.
+
### D.5 — Scope expansion (2026-08-27): identity is common to every VM, not just users
Surfaced while scoping `ACLKEY`, stated directly: *"the whole object is to deliver a