# mgf-hrb roadmap — single source of truth

> **What this is.** The live forward-looking work queue for
> mgf-hrb. When you ask "what's left to do?" — this is the file.
> All other tracker docs are scoped (per-rule conformance, per-
> sub-phase status, historical closure ledgers) or immutable
> (FEEDBACK papers as filed). This file is the consolidated
> what's-next.
>
> **Last refreshed:** 2026-05-31 — verified current against `main` (477 Python · 373 Zig; all gates green; count re-measured, was a +1 tally drift at 370).
>
> **Discipline.** Update on: new work surfacing (open a new `R<N>`);
> work shipping (move out of this file, into the commit's
> CHANGELOG); a FEEDBACK paper flipping ACCEPTED → SHIPPED
> (close its `R<N>`). Keep entries SHORT — detail lives in the
> cited source-of-truth files. The doc is read every session;
> don't bloat it.

---

## §1 Status snapshot (2026-05-31)

| Axis | State |
|---|---|
| Broker version | Pre-`0.1.0`. SDK + Zig + hrb-app current; synced to **mgf-common 0.42.0** (pin `>=0.41,<0.43`; OB-10 cross-language `LogRecord` Zig conformance compiled in this repo's CI). |
| Tier-0 survivability | **✅ 5/5 — mechanisms shipped** (trust gate, unforgeable caps, deadlines, revoke-below-fd; #5 asymmetric-trust §3.5 + per-tenant region isolation §5.5; the live shm data plane R-TIER0-5-DATAPLANE fully wired, slices 0–4 — pull + push mode). |
| Tier-1 survivability | **🟡 5/7 shipped; #10 + #7 cores landed** (#6 + #8 + #9 + #11 + #12 shipped — #8 live-wired via `reconcile`; **#10 FIFO anti-starvation (C2/C4)** + **#7 no-IOMMU DMA-claim refusal (D9/LINUX-01)** shipped 2026-05-29, **#10 priority-ordered admission (C3 §5.4)** + #7 full VFIO passthrough LIFECYCLE (`vfio.zig` + wiring: bind + atomic unwind, host-driver detach + atomic reattach, `fd_for_token` device-fd delivery, and `revoke` below-the-fd teardown — the whole acquire→deliver→revoke path, all atomic, all seam-tested)** 2026-05-30/31; the gated remainders are #10's active holder-preemption + weighted-fair/tuning and #7's irreducible device-bind core. **Slice 4 (2026-05-31) shrank the #7 hardware gate**: `realOps`' container ioctls now execute against this host's live `/dev/vfio/vfio` (API=0, TYPE1=1), every UAPI constant + `GroupStatus` is cross-checked vs the installed `linux/vfio.h`, and the detach sysfs paths are verified on the live `/sys` — so the only VFIO surface still hardware-unverified is `GROUP_GET_DEVICE_FD` + the destructive sysfs writes (root + a passthrough device); see §4/§5). |
| Tier-2 survivability | **✅ 3/4 — the defense-in-depth trio shipped 2026-05-29** (U7 fd-validation-on-receipt · I8 deadlock-freedom invariant · D7 interrupt-rate containment — hook + the real `/proc/interrupts` reader, per-scan cached + binary-searched). #16/I4 table-invariant checks were already delivered by R-T1-8's `auditInvariants`. |
| FEEDBACK papers | **0 OPEN** · 4 ACCEPTED (path documented, awaiting execution time) · 14 SHIPPED (LOG-01 adopted by mgf-common `37e2b01`, 2026-05-30 — the outbound Zig 0.16 `LogRecord` port; upstream + the vendored copy now reconverge) · 0 PARTIALLY SHIPPED. |
| Presentation-layer standard | **✅ binding for this pair**; federation DRAFT (pending vm-pair second adopter → R7). |
| Conformance level | L2 — Standard, declared honestly per `MGF_STANDARDS_CONFORMANCE.md` (synced to standards v0.42.0; OB-* section added). |
| Test count (default suite) | 477 Python + 373 Zig (broker; ~44 more in carved leaf repos) + nightly stress/perf/conformance gated. |
| All four gates | ✅ green (ruff + mypy --strict + pytest + lint-imports + audit_transitive_deps). |

**No blockers to a `0.1.0-RC1` candidate.** Remaining items
are post-RC hardening, hardware-gated, or cross-project
coordination.

---

## §2 In-flight (active queue)

*None right now.* Last shipped (2026-05-31): **R5 slice 4 — the #7 hardware gate,
shrunk** (`vfio.zig` host-integration pins: `realOps` drives the REAL container
ioctls against this host's live `/dev/vfio/vfio` — `VFIO_GET_API_VERSION` returns
exactly `VFIO_API_VERSION`, `CHECK_EXTENSION(TYPE1)` is recognised; every UAPI
constant + the `GroupStatus` layout is cross-checked against the installed
`linux/vfio.h` with a bridge to our computed `_IO(';',100+n)` numbers; and the
detach sysfs paths we write to are verified to EXIST on the live `/sys`. House
style — exercise the real path where present, assert honest-absence where not. The
first half of `bind()`'s real path is no longer fake-only; the only remaining
hardware-unverified VFIO surface is `GROUP_GET_DEVICE_FD` + the destructive sysfs
writes. 4 Zig pins). Prior (2026-05-30): **the wave-2 cluster-G regression
pins — ALL cleared** (R9 `priv_ipc` fd-leak burst pin over a socketpair; R10
exhaustive `@enumFromInt` UB sweep; R11 claims-table concurrent-race pin — each
"infrastructure-gated" gate dissolved once the real leak/race surface was
isolated; all verified non-vacuous via negative controls), the
**`SIGTERM`/`SIGINT` clean-stop handler** (`restart_marker.installCleanupHandler`
— an operator stop now clears the marker so the next start is cold, not a fake
crash-restart; the `defer clear` never fired in production, so this was a latent
bug, not a nicety), and **LOG-01 adopted upstream** (mgf-common took the supplied
Zig 0.16 `LogRecord` port verbatim in `37e2b01`; upstream + the vendored copy
reconverged). Prior 2026-05-29: **R-T1-8 (a) self-restart detection**
(`restart_marker` + auto-reconcile on a detected crash, strict bound source —
the broker now knows it restarted and re-derives hardware truth, unattended).
Prior 2026-05-29: **R-T1-8 (b) bound-at-hardware
reconcile precision** (`linux_bound` — reconcile quarantines only genuine
`vfio-pci` orphans, sparing idle / host-used PCI devices). Prior 2026-05-29:
**Tier-1 #7 no-IOMMU DMA-claim refusal** (`linux_iommu` + `linux_backend.vAcquire`
— refuse a DMA-capable PCI claim on a device with no IOMMU group; D9/LINUX-01).
Prior 2026-05-29:
**helper-mid-op in-flight quarantine** (R-T1-8 residual I3 —
`core.quarantineOnHelperFault`: a helper killed mid-`raw`/`control` quarantines
the device, like the acquire path). Prior 2026-05-29: **Tier-1 #10 anti-starvation core**
(`fairness.zig` — FIFO wait queue + reservation-on-release; a spammer can't
starve a waiter, C2/C4). Prior 2026-05-29: **the Tier-2 defense-in-depth
trio** — U7 fd-validation-on-receipt, I8 deadlock-freedom invariant, and D7
interrupt-rate containment (hook → real `/proc/interrupts` reader → per-scan
cache → binary-searched snapshot). Prior 2026-05-29: **R-T1-8-RESTART-WIRING**
(broker-wide `reconcile` op — live wiring of the #8 recovery contract),
**R-J-A4-TIMING** (sleeping-backend lock-drop pin), **R-TIER0-5-DATAPLANE
slice 4** (shm background drain thread — push-mode `start_shm_drain`), and the
**`/sync` to mgf-common 0.42.0** (OB-10 Zig `LogRecord` conformance). Earlier
shipped work lives in `CHANGELOG.md` + §3/§8.

**Genuinely remaining (all gated, none effort-ready-without-a-blocker):**
Tier-1 #7's gate is now down to its irreducible core — only `GROUP_GET_DEVICE_FD`
+ the destructive sysfs writes (unbind / `driver_override` / `drivers_probe`) stay
hardware-unverified, needing root + a dedicated passthrough device freed from its
host driver. As of slice 4 the rest of `realOps`/`realDriverOps` is pinned against
the live kernel: the container ioctls execute here, the UAPI constants + struct
are cross-checked vs `linux/vfio.h`, and the sysfs write-paths are verified to
exist on `/sys`. The no-IOMMU refusal, the VFIO bind + atomic unwind, the
host-driver detach + atomic reattach, AND `fd_for_token` delivery + `revoke`
below-the-fd teardown (`vfio.zig` + the backend wiring — the whole
acquire→deliver→revoke path) are all built + tested; #10's
richer policy (weighted-fair, **active
holder-preemption** = forced revocation §5.4, hold-time + grace-window tuning —
needs real multi-tenant traffic + the holder-signal/VFIO-teardown mechanisms;
the anti-starvation core AND priority-ordered admission are done); the R-T1-8 residual
tail is now closed for PCI (self-restart detection, helper-mid-op quarantine,
bound-at-hardware classification, AND the `SIGTERM`/`SIGINT`-clears-marker
clean-stop handler all in) — only **USB bound-detection** (a detached interface
is ambiguous) and the **real vfio-orphan end-to-end** (a passthrough device =
the #7 gate) remain; and the Tier-2 #16/I4 row (already covered by
`auditInvariants`).

When you start a new piece of work, move its `R<N>` from §3/§4/§5
up here.

---

## §3 Pending — scheduled (effort-gated)

These are the next-up items. Each is bounded; no external
blockers.

### R-T1-8 — Recovery contract formalised (Tier-1 #8, S4, survivability §4.3/§4.4) ✅ CLOSED 2026-05-28

Two tested, callable lease-engine mechanisms: `verifyLeaseInvariants` +
`Core.auditInvariants` (in-memory integrity → controlled-fault quarantine
on violation, never a silent bad grant; wired into `holderDied` as a
safety net) and `Core.reconcileAfterRestart(bound_urns)` (re-derive truth
from hardware + live clients; bound-but-unclaimed → quarantine
`unclaimed_after_restart`; composes with #12's recovery ladder;
idempotent). 5 Zig pins. See CHANGELOG 2026-05-28.

### R-TIER0-5-DATAPLANE — Live shm data-plane wiring ✅ CLOSED 2026-05-29

- **Source.** Spawned by Tier-0 #5 mechanism closure (2026-05-28). Both
  #5 halves were mechanism-complete but connected to nothing.
- **Status.** ✅ slices 0–4 shipped — the live data plane works end-to-end
  across two real processes, in both pull-mode and push-mode.
- **Slice 4 (2026-05-29) — background per-channel drain thread.** Opt-in
  push-mode: a new `start_shm_drain` wire op spawns a broker-side thread
  (`shm.Drainer`) that drains the channel's ring continuously via
  `popUntrustedProducer`, so a streaming producer needs no per-batch
  `drain_shm_channel` RPC; `drain_shm_channel` then returns the thread's
  accumulated stats. Lifecycle is co-located in `RegionPool`/`Region`:
  `unmapRegion` stop+joins the drain thread BEFORE unmapping, so every free
  path (release / `releaseAllForUid` on disconnect / `deinit`) joins the
  thread first by construction — no use-after-free, no leak (pinned by
  `testing.allocator`). arm64 weak-ordering was a non-issue: the ring already
  carries the producer/consumer acquire-release ordering, so the thread is a
  plain loop over it. Pins: 2 Zig (accumulation round-trip + disconnect-join
  no-leak) + 3 Python contract (push-mode round-trip, owner-check/idempotent,
  close-no-hang) + SDK `Client.start_shm_drain`. Pull-mode (slices 0–3)
  unchanged + still green. See CHANGELOG 2026-05-29.
- **What landed.** `mgf-hrb-rt` gained `HostedRing(T)` — the shared-memory
  overlay of the SPSC ring (head/tail/slots as pointers into a region; the
  `ShmRingHeader` ABI). `shm.zig` regions are now memfd-backed (mmap
  MAP_SHARED + owner-checked `fdFor`). New wire ops `open_shm_channel`
  (acquire region → layout ring → SCM_RIGHTS-pass the memfd) +
  `drain_shm_channel` (owner-checked, drains via `popUntrustedProducer`,
  capacity-bounded against a hostile `head`). SDK `ShmChannel` (mmap +
  header validation + `push`) + `Client.open_shm_channel`/`drain_shm_channel`.
  Disconnect runs `releaseAllForUid`. Pins: 5 HostedRing (rt) + 2 shm
  memfd + 6 contract (open/round-trip/overflow/hostile-producer). See
  CHANGELOG 2026-05-28.
- **Known limitation.** Channels key on uid (per §5.5); a same-uid second
  connection disconnecting frees the first's regions. Re-key by connection
  (cfd) if multi-connection-per-uid shm becomes a real use case.

### R-T1-8-RESTART-WIRING — Live restart plumbing ✅ MOSTLY CLOSED 2026-05-29

- **Source.** Spawned by R-T1-8 closure (2026-05-28). The recovery
  contract was formalised as callable mechanisms; this wires it live.
- **What landed (2026-05-29) — broker-wide reconcile + operator trigger.**
  `Facade.reconcileAfterRestart(scope, deadline)` enumerates each intersecting
  worker's present-in-hardware set (bounded + scope-filtered) and feeds each
  core's `reconcileAfterRestart`, quarantining every present-but-unclaimed
  orphan (`unclaimed_after_restart`). Exposed as a steal-gated `reconcile` wire
  op + `Client.reconcile_after_restart(scope=...)`. This is the CORRECT trigger
  given no persistence: the operator/supervisor (who knows a restart happened)
  fires it — auto-reconcile without restart-detection would wrongly quarantine
  idle devices on a healthy broker, so the steal-gate keeps that power scoped.
  Pins: 2 Zig facade (broker-wide + scoped) + 3 Python contract (orphan
  quarantine + recover, idempotent, authority-gate). See CHANGELOG 2026-05-29.
- **Residual.**
  (1) **Automatic self-restart detection** — ✅ CLOSED 2026-05-29. `restart_marker`
  is a one-byte marker beside the socket (atomic `O_CREAT|O_EXCL`): a marker a
  prior instance left → RESTART (a host reboot clears tmpfs → cold start, no
  orphans; a crash/kill leaves it → restart, vfio bindings persist → real
  orphans). On a detected restart `server.zig` auto-reconciles with the STRICT
  bound source (only confident `vfio-pci` orphans — never disrupts idle / USB).
  Pinned via a Zig unit + a Python integration test (spawn → kill -9 → respawn
  detects RESTART). Residual closed 2026-05-30: a clean `SIGTERM`/`SIGINT` stop
  now runs a signal handler (`installCleanupHandler`) that `unlink`s the marker
  before `exit_group`, so an operator stop reads as a COLD start next boot — only
  an uncatchable SIGKILL / crash leaves the marker → a restart. This mattered
  more than a nicety: the accept loop never returns, so the `defer clear` never
  fired in production and EVERY restart looked like a crash-restart. Pinned via a
  Zig plumbing unit + a Python contract test (spawn → SIGTERM → respawn is cold),
  verified non-vacuous by a negative control.
  (2) **Real-Linux "bound-at-hardware" enumeration** — ✅ CLOSED 2026-05-29 (PCI).
  `linux_bound` classifies a URN's bound state from sysfs (`/sys/.../driver`):
  `vfio-pci` (broker passthrough binding) → orphan candidate; host-driver-bound
  or unbound PCI → not an orphan; non-PCI (USB) → conservatively included.
  `reconcileAfterRestart(scope, ?BoundSource, …)` filters present → bound, so the
  wire op now spares idle / host-used PCI devices instead of nuking them. Pinned
  via the classifier (host integration) + a facade end-to-end (injected source).
  Remaining: USB bound-detection (a detached interface is merely driverless —
  ambiguous) and exercising a REAL `vfio-pci` orphan end-to-end (needs a
  passthrough device = the #7 VFIO gate).
  (3) **Helper-mid-op in-flight quarantine** — ✅ CLOSED 2026-05-29.
  `core.quarantineOnHelperFault` extends the acquire-timeout → quarantine safety
  net to the other client-facing helper-touching ops: `core.raw` quarantines on
  a `rawChannel` timeout, the new `core.control` (which `facade.control` now
  routes through) quarantines on a `control` timeout; a clean error (state known)
  is left alone. `reset` was already covered by `recoverByReset`. Pinned with a
  test wrapper (Timeout → quarantine; HardwareFault → not). Residual sliver:
  `release()` is `void` (no error surfaced) + the U7 bad-fd `HardwareFault` path.

### R-T1-12 — Reset-escalation ladder (Tier-1 #12, S2/S4, survivability §2.5) ✅ CLOSED 2026-05-28

`Core.recoverByReset(urn, deadline)` runs `FLR → (verify) → bus reset →
(verify) → quarantine`; each rung deadline-bounded + backend-verified;
ends in `Free` (recovered) or `Quarantined` (escalated to
`reset_unverified`), never a hang. Wired to an operator-triggerable
`recover` wire op (steal-gated) + `Client.recover`. Closes the dead-end:
before this, `backend.reset` had no caller and `clearQuarantine` was dead
code, so a quarantined resource (#6) was never recovered. Pins: 4 Zig
behavioural (per-level FakeBackend knob `reset_fails_below`) + 3 Python
contract + 2 SDK unit. See CHANGELOG 2026-05-28.

### R-T1-9 — Bound everything (Tier-1 #9, S5, survivability §3.7) ✅ CLOSED 2026-05-27

Sliced like #11. Two §3.7 bounds already existed before this work
(max request size: `MAX_LINE_BYTES`/`MAX_PENDING_BYTES`; aggregate
connection cap: `MAX_ACTIVE_CLIENTS`). The remaining bounds, per slice:

- **R-T1-9A — per-tenant + aggregate live-lease ceiling** ✅ CLOSED 2026-05-27.
  `core.zig` `MAX_LEASES = 128` (aggregate per Core) + `MAX_LEASES_PER_UID = 64`
  (per-tenant, keyed on the unforgeable SO_PEERCRED uid recorded on each
  `Lease.peer_uid`), enforced in `claimImpl` Phase 1, shedding with
  `Err.Overloaded`. fds bounded transitively (a raw channel rides a lease).
  3 Zig + 2 Python-contract pins (the latter prove the uid flows end-to-end
  through dispatch). See CHANGELOG 2026-05-27.
- **R-T1-9B — per-tenant (per-uid) connection cap** ✅ CLOSED 2026-05-27.
  `server.zig` `MAX_CONN_PER_UID = 32`, a SpinMutex-guarded per-uid
  live-connection registry checked in `handleClient` after the peer-cred read;
  over-cap → `TooBusy` line + close. Mapped `TooBusy` → `OverloadedError` in
  the SDK (was unmapped). 2 Python-contract pins. See CHANGELOG 2026-05-27.
- **R-T1-9C — per-connection progress deadline (slowloris kill)** ✅ CLOSED 2026-05-27.
  `server.zig` recv loop sets `SO_RCVTIMEO` and drops a connection whose partial
  request line doesn't complete within the deadline of its first byte (typed
  `Timeout`); idle connections are NOT penalised. Deadline 10 s default,
  tunable via `MGF_HRB_SLOWLORIS_MS`. 2 Python-contract pins. See CHANGELOG.
- **R-T1-9D — per-tenant rate-limit token bucket** ✅ CLOSED 2026-05-27.
  `server.zig` per-uid milli-token bucket charged in `handle` (after parse,
  before dispatch); over-rate → `Err.Overloaded`. Per-uid state survives
  reconnects (256-slot LRU table). Generous defaults (burst 2048, refill
  1000/s) so real workloads never trip it; tunable via `MGF_HRB_RL_BURST` /
  `MGF_HRB_RL_REFILL_PER_SEC`. 3 Python-contract pins. Lives in the Zig
  dispatch path (no Python policy plane exists in the request path today,
  despite the design's "policy (Python)" framing — that plane is U-future).
- **R-T1-9E — enumerate result bounds** ✅ CLOSED 2026-05-27. `ListCollector`
  and `LeaseJsonWriter` now reserve a tail margin, stop emitting before it, and
  emit an honest `"truncated"` flag — fixing both the silent record-drop and a
  latent suffix-write buffer overflow (Debug-build panic). 2 Python-contract
  pins. See CHANGELOG.

**All five slices closed 2026-05-27 — Tier-1 #9 is COMPLETE.** Every
client-driven resource is now bounded per-tenant and in aggregate
(table entries, fds-via-leases, connections, op rate, response size);
back-pressure (`Overloaded` / `TooBusy` / `Timeout`) instead of
unbounded growth.

### R1 — USB-02 Slice B: claim + raw channel ✅ CLOSED 2026-05-27

- **Source.** FEEDBACK [USB-02](../../FEEDBACK.md) — ACCEPTED 2026-05-27, "Slice A first".
- **Status.** ✅ closure pins shipped this commit. The Zig backing (`hrb-code/src/linux_usb.zig` lines 1055-1535) implements Phases U3a end-to-end: `vAcquire` USBDEVFS_CLAIMINTERFACE; `vAcquireDetach` atomic DISCONNECT_CLAIM; `vRelease` USBDEVFS_RELEASEINTERFACE + close; `vRawChannel` fd-via-SCM_RIGHTS; `vFdForToken` plumbing. Same shape as G1: the Zig was already shipped; the missing piece was the Python regression pins proving the wire round-trip holds.
- **Pins shipped this commit:**
  - **5 contract tests** in `tests/contract/test_r1_usb_claim_paths.py`: nonexistent USB URN → `NotFoundError`; real USB URN without root → `NotPermittedError` (EACCES path through `linux_usb.zig:489`); wire-level pin that `cap_id` is the canonical post-G2 field on USB claim; negative pin that legacy `cap` field is no longer accepted; idempotent release-against-unknown-lease-on-routed-USB-URN.
  - **2 root-gated e2e tests** in `tests/e2e/test_r1_usb_claim_happy_path.py` (skip cleanly on non-root): driver-free USB interface → claim → `lease.raw()` returns SCM_RIGHTS fd → release tears down; driver-bound USB interface → `ConflictError` (no surprise driver theft without `detach=True`).
- **Unlocked.** figrecovery's mediation half — replaces direct libusb-on-Linux opens with broker-mediated claims. USB-03 enforcement code (refuse-on-conflict) is queued at R-USB03 below; the underlying claim path is now exercised end-to-end through the wire.

### R2 — Cluster J A4: `Core.claim` placeholder protocol ✅ CLOSED 2026-05-27

- **Source.** Work-roadmap memory item #7 (consolidated here).
- **Status.** ✅ shipped this commit. `Core.claim` now runs a 3-phase protocol: Phase 1 reserves a placeholder lease (`token.id == 0` sentinel) under `Core.lock`; Phase 2 calls `backend.acquire` with the lock DROPPED; Phase 3 reacquires the lock and installs the real token. `holderDied` skips placeholder leases (no spurious `backend.release` on `token.id == 0`). The lock is no longer held across the (potentially blocking) backend acquire — `list`/`status`/`raw`/`release` against unrelated URNs complete without waiting on a slow claim.
- **Pins shipped this commit (2 Zig behavioural tests at the bottom of `hrb-code/src/core_test.zig`):**
  - `A4: rollback on acquire failure leaves no residue` — `fb.fail_acquire = true` → claim returns `HardwareFault` → `liveLeaseCount() == 0` (placeholder removed by Phase 2's catch block); subsequent claim succeeds (no stale compat-check residue).
  - `A4: rollback on quarantine-triggering Timeout leaves no lease residue` — `slow_acquire_ms = 5000` + 10 ms deadline → `Err.Timeout` → placeholder cleaned + URN moved to quarantine → next claim returns `Err.Unavailable`. Pins the quarantine-on-timeout path through the new rollback shape.
  - Cross-pin: the existing "N threads claiming SAME URN — exactly one wins" test (top of `core_test.zig`) continues to hold under the new lock-drop shape, proving no race produces a double-grant.
- **Deferred deliberately.** Empirical lock-drop verification (a thread observing `list_leases` complete during a slow concurrent claim) needs a backend that REALLY sleeps in `vAcquire` — `FakeBackend.slow_acquire_ms` only controls the deadline-budget check (declared-vs-deadline), not real-time slowness. A dedicated sleeping-backend test fixture is a separate piece of work (queued as R-J-A4-TIMING below). Until then the lock-drop is verifiable by code inspection (`core.zig` `claimImpl` — Phase 1 `self.lock.unlock()`, Phase 3 `self.lock.lock()`) + the behavioural pins above.

### R2-A3 — Cluster J A3: `subscription.pushFanout` snapshot-and-eval ✅ CLOSED 2026-05-28

- **Source.** Work-roadmap memory item #6 (split out from R2 when A4 shipped 2026-05-27).
- **Status.** ✅ shipped this commit. **Re-scoped on inspection:** the old code was NOT a latent UAF (it held `registry.lock` across the whole fan-out, serializing vs `reapPeer`); the win is getting the slow sysfs `lookup` OUT from under the lock + dropping the fragile "lookup must not re-enter" contract. And the imagined fix (per-slot generation counter) was **insufficient** — it detects slot ABA only AFTER eval already dereferenced freed memory.
- **What shipped.** `pushFanout` is now snapshot (locked, `refs++`) → evaluate (unlocked) → commit (locked, deliver + `refs--` + deferred-free). `reapPeer`/`unregister` route through `retireSubLocked`: a sub with `refs > 0` is unlinked + marked `dead`, freed by the eval's commit phase when the last ref drops — the correct **refcount + deferred-free (RCU-lite)** pattern. The `lookup`-must-not-re-enter contract is removed (eval no longer holds the lock).
- **Pin.** A threaded stress test (`subscription_test.zig`) races ~4000 `pushFanout` vs ~4000 `register`/`reapPeer` cycles on one peer; the GPA testing allocator's UAF/double-free/leak accounting is the verdict (stable across runs). All allocator calls stay under `registry.lock`, so no thread-safe-allocator wrapper is needed.

### R-J-A4-TIMING — Sleeping-backend test fixture for lock-drop verification ✅ CLOSED 2026-05-29

- **Source.** Spawned by R2-A4 closure (2026-05-27).
- **What shipped.** `SleepingBackend` in `core_test.zig` — a `KernelBackend`
  wrapper that delegates every op to a wrapped backend but whose `acquire`
  really sleeps `sleep_ms` first (vs `FakeBackend.slow_acquire_ms`, which only
  fails the deadline-BUDGET check and never blocks). The R2-A4 lock-drop is now
  pinned **empirically**: a thread runs a 500 ms claim (parked in
  `backend.acquire` with `Core.lock` dropped, phase 2); the main thread then
  times a lock-requiring op (`liveLeaseCount`) and asserts it returns in
  microseconds (< 150 ms), not blocked the remaining ~420 ms. Pre-A4 (lock held
  across the backend acquire) the op would have blocked — so the test would fail
  without the lock-drop. Stable across repeated runs (generous threshold; the op
  is microseconds, the slow claim is half a second). Lives in `core_test.zig`
  for its sole consumer; promote to a shared module if a second test wants
  real-time backend slowness. See CHANGELOG 2026-05-29.

### R3 — TOOLS-01: substrate-report rows in `doctor` ✅ CLOSED 2026-05-27

- **Source.** FEEDBACK [TOOLS-01](../../FEEDBACK.md) — was PARTIALLY SHIPPED; flipped to ✅ SHIPPED this commit.
- **Status.** ✅ shipped this commit. The USB worker's claim path closed in R1 (commit `a07c1cf`) unlocked the substrate-specific checks the doctor was missing; R3 wires them in.
- **Pins shipped this commit:**
  - Two new diagnostics in `src/mgf/hrb/diagnostics.py`: `check_usbfs_devnode` (`USBFS_DEVNODE_PRESENT`) probes `/dev/bus/usb` presence + bus-dir population; `check_usbfs_openable` (`USBFS_DEVNODE_OPENABLE`) does an `open(O_RDWR)` against the first device node so operators see the EACCES → "install udev rule" remediation at bring-up time rather than at first claim. Both SUBSTRATE category.
  - Doctor finding count went from 7 → 9 (additive, stable codes). Call-site test pins updated in lockstep: `tests/use_cases/test_gui_doctor_pane.py`, `tests/e2e/test_e2e.py`, `tests/unit/mgf/hrb/test_client.py`.
  - 3 new unit pins in `tests/unit/mgf/hrb/test_cli_doctor.py`: `USBFS_DEVNODE_PRESENT` returns a valid severity; `USBFS_DEVNODE_OPENABLE` returns a valid severity; the EACCES branch points at the udev-rule remediation.
  - `PUBLIC_API.json` regenerated for the two new `__all__` exports (`check_usbfs_devnode`, `check_usbfs_openable`).
- **Closed deliberate gap.** The `UDEV_RULE_INSTALLED` check itself remains `DEFERRED` — its udev-rule template still hasn't shipped from the worker packaging side. That's tracked as **R-USB-PKG** below (packaging-gated; not a code change but a release artefact).

### R-USB-PKG — Ship the mgf-hrb USB udev rule template (closes UDEV_RULE_INSTALLED check) ✅ CLOSED 2026-05-27

- **Source.** Spawned by R3 closure (2026-05-27).
- **Status.** ✅ shipped this commit. Last `DEFERRED` finding in the substrate suite is now active.
- **Pins shipped this commit:**
  - **`packaging/udev/70-mgf-hrb.rules`** — the rule template from `hrb-design/mgf-hrb-usb-worker.md` §7 (`SUBSYSTEM=="usb", MODE="0660", GROUP="mgf-hrb"` plus the matching `DRIVERS=="hub"` row for uhubctl-style port controls). Header comment includes the install + group-add commands so operators can copy-paste.
  - **`check_udev_rule`** in `src/mgf/hrb/diagnostics.py` now actually checks for the rule file at the three canonical udev paths (`/etc/udev/rules.d/`, `/usr/lib/udev/rules.d/`, `/run/udev/rules.d/`) instead of returning `DEFERRED`. Returns `OK` when found, `WARN` when absent (with the install one-liner as remediation). The doctor's substrate suite is now fully active — zero DEFERRED rows on a typical operator host.
  - **G2 honesty pin reshaped.** `test_G2_doctor_deferred_means_we_dont_know_yet` was pinning `UDEV_RULE_INSTALLED` as the canonical DEFERRED example. It now pins `BACKEND_CAPABILITIES` in the no-client branch (the engine still has a DEFERRED expression — just expressed via a different check). The G2 principle is preserved; the test is honest about what it's pinning.
  - **`test_udev_rule_check_returns_status`** in `tests/unit/mgf/hrb/test_cli_doctor.py` replaces the old `test_udev_rule_check_is_deferred` — pins both OK/WARN as valid outcomes and verifies the WARN branch carries the install remediation.
- **Deliberate scope cap.** The check verifies file presence ONLY — it doesn't validate the rule contents, check that the `mgf-hrb` group exists, check that the operator is in that group, or that udev has reloaded the rule. Those secondary checks are the design's "robust signals" (`mgf-hrb-usb-worker.md` §7) — useful but not blocking; they can be added as additional finding codes when an operator reports the gap. Today's outcome already moves the bring-up failure from "discovered at first claim" to "discovered at `mgf-hrb doctor` time" which is the bulk of the win.

### R-T1-11A — Cap-filtered `list` op (S3 isolation, first slice of Tier-1 #11) ✅ CLOSED 2026-05-27

- **Source.** Survivability review §3.6 (U11 cross-tenant enumerate, S3 isolation gap). Highest-priority Tier-1 item per the priority order in §4.
- **Status.** ✅ shipped this commit. The MECHANISM is wired for the `list` op: when a caller presents a non-zero `cap_id`, the broker intersects the requested scope with the cap's scope before emitting URNs. The S3 isolation property ("a holder of cap X cannot observe the existence of resources outside X's scope") now holds for `list`.
- **Pins shipped this commit:**
  - **Zig side:** `request.ListReq.cap_id` field added (default 0 ⇒ legacy unfiltered); parser extracts `cap_id` via `getOptionalInt`; `CapTable.permitsNaming(cap_id, urn)` added (scope-only check, lighter than `permitsClaim`); `Facade.capPermitsNaming` + `Facade.usesCapFilter` plumb through; `ListCollector` in `server.zig` filters URNs through `capPermitsNaming` BEFORE emission when `usesCapFilter` is true; `fmtList` decides whether to wire the filter based on `usesCapFilter(cap_id)`.
  - **6 new Zig tests** in `hrb-code/src/facade_test.zig`: `usesCapFilter` is false without verifier; `usesCapFilter` requires both verifier AND non-zero cap_id; `capPermitsNaming` filters by cap scope (in/out); refuses unknown cap_id (forgery probe); returns true with no verifier (legacy compat); lower-level `CapTable.permitsNaming` shape pin.
  - **3 new Python contract tests** in `tests/contract/test_wire_list_cap_filter.py`: omitted vs explicit `cap_id=0` produce identical results (back-compat); root cap doesn't narrow (intersection is the request); unknown `cap_id` returns empty (no URN leak to forgery probes, no BadRequest signal either).
- **Backward compatibility.** `cap_id=0` (the wire sentinel) bypasses the filter entirely. Today's SDK doesn't yet pass `cap_id` on `Client.list()` — that's R-T1-11B. The broker change is wire-additive: legacy clients see no behavior change.
- **Deliberate scope cap.** `list_leases`, `dump_all`, `inspect_full`, and `inspect` are NOT yet cap-filtered. They have the same U11 leak shape; closure is the same pattern (extract cap_id → wrap collector with `capPermitsNaming` filter). Tracked as R-T1-11B + R-T1-11C (the latter for SDK plumbing). This commit ships the mechanism + the first consumer; the rest is mechanical replication.

### R-T1-11B — Cap-filtered `list_leases` / `dump_all` / `inspect_full` ✅ CLOSED 2026-05-27

- **Source.** Spawned by R-T1-11A closure (2026-05-27). The mechanism (`Facade.capPermitsNaming` + collector-wrap pattern) was shipped by R-T1-11A; this slice replicates it across the remaining enumeration ops.
- **Status.** ✅ shipped this commit. `cap_id: u64 = 0` added to `ListLeasesReq` / `DumpAllReq` / `InspectFullReq`; parser extracts it via `getOptionalInt`; dispatch threads it to the formatters. The two scope-shaped ops filter URNs in their collectors; the single-URN op checks the root.
  - **`dump_all`** — `DumpAllCollector.push` drops out-of-scope URNs during pass-1 enumeration (before they're inspected or buffered), gated by `usesCapFilter`.
  - **`list_leases`** — `LeaseJsonWriter.push` drops lease records whose URN is outside the cap's scope before emission.
  - **`inspect_full`** — single root URN: when the filter applies and the root is outside the cap's scope, returns `NotFound` (not `NotPermitted`) so an out-of-scope URN is indistinguishable from a nonexistent one. The substrate subtree below the root is sysfs structure (not broker URNs), so no per-entry filtering applies once the root passes.
- **Pins shipped this commit (8 Python contract tests in `tests/contract/test_wire_enum_cap_filter.py`):**
  - `dump_all`: omitted vs `cap_id=0` equivalence (back-compat); root cap doesn't narrow; **unknown `cap_id` ⇒ zero items even though the host exposes real resources** (the strong S3 pin — directly wire-observable).
  - `inspect_full`: omitted vs `cap_id=0` equivalence; root cap permits a real URN; **unknown `cap_id` on a real URN ⇒ `NotFound`** (the distinctive single-URN S3 shape).
  - `list_leases`: field-acceptance + back-compat + no-error on unknown `cap_id`.
- **Why no new Zig tests.** The scope-narrowing filter (`capPermitsNaming` in/out + `usesCapFilter` gating) is the *same* mechanism R-T1-11A already pinned in `facade_test.zig`; this slice adds no new facade surface. `server.zig` is built as an executable (not a Zig test target per `build.zig`), so the collector wiring is pinned at the wire level via the Python contract suite — the round-trips exercise the real parser + dispatch + collector path end-to-end.
- **Deliberate scope cap.** `inspect` (single-URN resource+attrs) and `dump_chain` (parent-chain walk) are still unfiltered — same single-URN `NotFound` pattern as `inspect_full`. Tracked as **R-T1-11D** below. After R-T1-11D, #11's mechanism gap is fully closed; SDK plumbing for all enum ops is R-T1-11C.
- **`list_leases` honest limitation.** Filter-narrowing isn't wire-observable at broker startup (zero live leases — nothing claimed), so its contract pins cover field-acceptance + back-compat only; the narrowing rides on the shared `capPermitsNaming` Zig pin + the `LeaseJsonWriter.push` filter (code-inspectable).

### R-T1-11D — Cap-filter `inspect` + `dump_chain` ✅ CLOSED 2026-05-27

- **Source.** Spawned by R-T1-11B closure (2026-05-27). Both are single-URN ops; the `inspect_full` `NotFound`-on-out-of-scope pattern applies verbatim.
- **Status.** ✅ shipped this commit. `cap_id: u64 = 0` added to `InspectReq` + `DumpChainReq`; parser extracts via `getOptionalInt`; dispatch threads `r.cap_id`. `fmtInspect` / `fmtDumpChain` return `NotFound` when `usesCapFilter(cap_id)` and the root/leaf URN is out-of-scope — identical guard to `fmtInspectFull`. For `dump_chain` the S3 check is on the named leaf only (the parent chain above it is substrate topology, not separately-claimable URNs).
- **Pins shipped this commit (6 Python contract tests in `tests/contract/test_wire_inspect_cap_filter.py`):** for each of `inspect` / `dump_chain` — omitted vs `cap_id=0` equivalence (back-compat); root cap permits a real URN; **unknown `cap_id` on a real URN ⇒ `NotFound`** (the S3 pin, wire-observable against a real resource).
- **Why no new Zig tests.** Same rationale as R-T1-11B: the filter decision is the shared `capPermitsNaming`/`usesCapFilter` surface already pinned in `facade_test.zig`; `server.zig` is an executable (not a Zig test target), so the formatter guard is pinned at the wire level via the contract suite.
- **Outcome.** **Tier-1 #11's mechanism gap is now fully closed** — every enumeration op (`list`, `list_leases`, `dump_all`, `inspect`, `inspect_full`, `dump_chain`) honors the cap filter on the wire. The only remaining #11 work is SDK plumbing (R-T1-11C) so clients actually pass `cap_id` by default.

### R-T1-11C — SDK passes cap_id on enumeration ops ✅ CLOSED 2026-05-27

- **Source.** Spawned by R-T1-11A closure (2026-05-27). The wire accepts `cap_id` on ALL six enum ops (R-T1-11A/B/D); the SDK didn't pass it on the read-shaped ones.
- **Status.** ✅ shipped this commit. **Last remaining Tier-1 #11 work — #11 is now fully closed.** `Client.list()` / `list_leases()` / `dump_all()` / `inspect()` (+`inspect_raw`) / `inspect_full()` (+`inspect_full_raw`) / `dump_chain()` (+`dump_chain_raw`) now forward the connection-bound `self._cap` (the cap the broker minted in its hello). Forwarding is uniform with the claim/raw/revoke/audit ops that already passed it.
- **Pins shipped this commit (7 unit tests in `tests/unit/mgf/hrb/test_client.py`):** `test_{list,list_leases,dump_all,inspect,inspect_full,dump_chain}_forwards_connection_cap_id` assert `cap_id == self._cap` on the wire (the typed + `_raw` variants share the op, so the inspect/inspect_full/dump_chain pins cover both); `test_cap_id_zero_is_forwarded_when_connection_has_no_cap` pins that a cap_id=0 client forwards 0 (no special-casing — the SDK forwards whatever the hello gave). Two existing exact-match request assertions (`list`, `inspect`) updated to include the field.
- **Back-compat is total.** Every client holds the ROOT cap (scope `hw:/**`), so the scope intersection is the request itself and nothing narrows. The win is forward-looking: when per-client attenuated caps land, attenuated reads are filtered automatically with no further SDK change.
- **Honest gap → spawned R-CAP-ATTEN-WIRE.** The end-to-end narrowing through a REAL attenuated grant the original entry imagined can't be exercised from Python yet — there is no wire op to mint an attenuated sub-cap. The narrowing itself stays pinned at the broker layer (`facade_test.zig` `capPermitsNaming` + the `test_wire_*_cap_filter.py` contract suites).

### R-CAP-ATTEN-WIRE — Wire op to mint an attenuated sub-cap ✅ CLOSED 2026-05-27

- **Source.** Spawned by R-T1-11C closure (2026-05-27). Per-client cap attenuation was "U-future"; `request.zig::Op` had no `attenuate`, so a Python client only ever held the root cap.
- **Status.** ✅ shipped this commit. New `attenuate` wire op + SDK `Client.attenuate(...)`. This closes the honest gap R-T1-11C flagged: #11's enumeration filtering and #9's per-tenant caps are now exercised — and pinned — through a REAL attenuated grant, not just the root cap.
- **Mechanism.** `CapTable.attenuateFromPeer(parent_id, peer, restriction)` (cap.zig) — peer-binding + `grant`-right gated; narrowing core shared with the plain `attenuate` via `attenuateLocked`; child inherits the parent's `bound_to`. `AttenuateReq` (request.zig) carries the parent `cap_id` + optional scope/rights/mode_ceiling. `fmtAttenuate` (server.zig) returns `{"ok":true,"cap_id":<sub>}`. **The root cap now carries the `grant` right** so connections can delegate (safe — attenuation only shrinks). SDK `Client.attenuate` returns a child `Client` sharing the connection (non-owning `owns_transport=False`) presenting the sub-cap.
- **Pins.** 4 Zig cap_test (grant required / binding enforced / child inherits binding + can't widen / missing-parent NotPermitted) + 5 Python contract (`test_cap_attenuate.py` — narrowed `list` returns exactly the in-scope URNs; widen-refused; grant-gated; rights-narrowing bites `claim`; unknown-parent refused) + 4 SDK unit. See CHANGELOG 2026-05-27.
- **Follow-on (optional).** Cross-process delegation — handing a sub-cap to a *different* uid/connection — would need an explicit "unbound delegation" path (today a sub-cap inherits the parent's peer binding, so it's usable only on the minting connection). Not needed for the single-connection narrowing use case; open a new R-item if a consumer wants it.

### R4 — Helper privilege-drop layer hardening ✅ CLOSED 2026-05-27

- **Source.** Work-roadmap memory item #8.
- **Status.** ✅ shipped this commit. The three missing privilege-drop steps from `mgf-hrb-privileged-core-hardening.md` §3.1 are now wired in `hrb-code/src/usb_priv_main.zig`'s `dropPrivileges`: capset narrowing, bounding-set drop, uid demotion.
- **Pins shipped this commit:**
  - **`capsetMinimum`** — `capget` current permitted, intersect with the keep-set `{CAP_DAC_OVERRIDE}`, `capset` to that intersection on effective + permitted, zero inheritable. Intersection-with-current is what makes this safe for non-setuid invocations (asking for a cap you don't have returns EPERM; intersection just yields the empty set there and capset succeeds).
  - **`dropBoundingSet`** — `PR_CAPBSET_DROP` every cap outside the keep-set. Best-effort: silently no-ops when we lack `CAP_SETPCAP` (the common non-setuid case). When the helper is deployed setuid root and we DO have CAP_SETPCAP, the drops fire and the bounding set actually narrows.
  - **`dropToRealUid`** — `setresgid(rgid)` + `setresuid(ruid)` with `PR_SET_KEEPCAPS` first so the cap set survives the euid 0→non-0 transition. No-op when euid == ruid (today's non-setuid case).
  - Sequence wired in order: `PR_SET_NO_NEW_PRIVS` → `closeInheritedFds` → `PR_SET_KEEPCAPS` → `capsetMinimum` → `dropBoundingSet` → `dropToRealUid`. The `KEEPCAPS` + capset-before-uid-drop ordering is what makes the cap set survive the uid demotion when the helper is setuid; reversed, the kernel would zero the caps at uid transition.
  - **3 fork-isolated Zig tests** in `hrb-code/src/usb_priv_main.zig`: capset narrows effective ⊆ {CAP_DAC_OVERRIDE} (always passes — capset can only reduce; the keep-set is the upper bound); bounding set narrows when CAP_SETPCAP is held (skips honestly when absent via exit 77 sentinel); `dropToRealUid` no-ops cleanly when euid == ruid. Tests fork because cap drops are process-wide irreversible.
  - **Bonus fix (test infra):** `tests/_broker.py::_is_build_input` extended to exclude sibling-binary entrypoints (`usb_priv_main.zig`, `usb_scan.zig`, `usb_watch.zig`) from the SERVER staleness check. Editing the helper's source was incorrectly marking the server binary as stale; the helper is a separate executable and its source isn't a server build input.
- **Real-deployment posture.** Today the helper isn't setuid — drops are no-ops. When a production deployment marks `mgf-hrb-usb-priv` setuid root (or installs file caps), the drops fire automatically; no further code change needed. The structural "LEAST of {worker, parent, env}" property is now complete: caps narrowed to `{CAP_DAC_OVERRIDE}`, uid drops to ruid, bounding set is sticky-narrowed across any future fork.

---

## §4 Pending — Tier-1 survivability work

Tier-0 is complete (5/5). Tier-1 is the next survivability layer
per [`hrb-design/mgf-hrb-survivability-review.md`](../../hrb-design/mgf-hrb-survivability-review.md) §4. Not blockers to
`0.1.0`; this is the post-RC hardening queue.

| Item | Status | Where it bites |
|---|---|---|
| **#6** Quarantined state + verified-reset recovery | ✅ shipped | `model.zig:102` defines variant; `core.zig:126` maintains list |
| **#7** IOMMU enforced + no-IOMMU declared degraded (LINUX-01) | 🟡 no-IOMMU refusal (2026-05-29) + VFIO bind orchestration (2026-05-30) shipped | `linux_iommu` resolves the IOMMU group from sysfs + a DMA-safety posture; `linux_backend.vAcquire` **refuses** a DMA-capable PCI claim on a device with no IOMMU group (`NotPermitted`, D9), `observe` exempt. **2026-05-30:** `vfio.zig` + the wiring give the full PCI VFIO lifecycle — bind (container/group/device dance + atomic unwind, slice 1), host-driver detach + atomic reattach (slice 2), and `fd_for_token` device-fd delivery + `revoke` below-the-fd teardown (slice 3, filling two prior-null vtable slots). All behind injected seams; the whole acquire→deliver→revoke path is pinned with fakes (13 Zig pins; unwind, reattach, and revoke each negative-control verified). **2026-05-31 (slice 4):** the #7 hardware gate shrunk — `realOps`' container ioctls now run against this host's live `/dev/vfio/vfio`, the UAPI constants + `GroupStatus` are cross-checked vs `linux/vfio.h`, and the detach sysfs write-paths are verified on `/sys` (4 host-integration/pure Zig pins). **Gated remainder (R5):** now only `GROUP_GET_DEVICE_FD` + the destructive sysfs writes — running them needs root + a passthrough device. FEEDBACK [LINUX-01](../../FEEDBACK.md). |
| **#8** Worker/helper restart recovery contract | ✅ contract formalised (2026-05-28) | `verifyLeaseInvariants` + `Core.auditInvariants` (integrity → controlled-fault quarantine, §4.4) + `Core.reconcileAfterRestart` (re-derive: bound-but-unclaimed → quarantine, §4.3). Live restart plumbing + helper-in-flight quarantine = follow-on R-T1-8-RESTART-WIRING (§3). See §3 R-T1-8 |
| **#9** Bound everything (per-tenant rate/lease/fd ceilings) | ✅ shipped (2026-05-27) | All five slices: lease ceilings (9A) + per-uid connection cap (9B) + slowloris deadline (9C) + per-uid rate limit (9D) + enumeration result bounds (9E), on the pre-existing request-size + aggregate-connection bounds. See §3 R-T1-9 |
| **#10** Fairness / anti-starvation policy | 🟡 anti-starvation core (2026-05-29) + priority-ordered admission (2026-05-30) shipped | `fairness.zig` — per-resource wait queue + reservation-on-release keyed on the SO_PEERCRED uid, wired into `claimImpl`/`release`; a spammer can't starve a waiter (C2/C4). **2026-05-30:** a `PrioritySource` seam + priority-ordered reservation — a freed resource goes to the highest-priority waiter (then longest-waiting), closing the priority-inversion-among-waiters half of C3 (§5.4); no source set → exact FIFO back-compat. Gated remainder: weighted-fair, **active preemption of a live holder** (= forced revocation, §5.4 — needs the holder-signal channel + #7 VFIO teardown), hold-time limits + tuning — need real multi-tenant traffic. `Fairness` is the swap point for that future `ContentionPolicy`. |
| **#11** Capability-filtered enumeration (S3 isolation gap) | ✅ shipped (2026-05-27) | All 6 enum ops cap-filtered on the wire — `list` (R-T1-11A) + `list_leases`/`dump_all`/`inspect_full` (R-T1-11B) + `inspect`/`dump_chain` (R-T1-11D); SDK forwards `cap_id` on all six (R-T1-11C). Follow-on R-CAP-ATTEN-WIRE adds an attenuate op so a narrow grant can be exercised end-to-end |
| **#12** Reset-escalation ladder (FLR → bus → quarantine, each verified) | ✅ shipped (2026-05-28) | `Core.recoverByReset` runs FLR→bus→quarantine (per-rung verified, deadline-bounded); `recover` wire op (steal-gated) + `Client.recover`. Quarantine (#6) now has a recovery path. See §3 R-T1-12 |

**Priority order if you want to pick one:** the buildable cores of both #10 and
#7 shipped 2026-05-29 — #10's FIFO anti-starvation (`fairness.zig`) and #7's
no-IOMMU DMA-claim refusal (`linux_iommu`) — and #10's priority-ordered
admission followed 2026-05-30. What's left is genuinely gated: #10's richer
policy (weighted-fair, **active preemption** of a live holder = forced
revocation, hold-time, tuning) needs real multi-tenant traffic and the
holder-signal + VFIO-teardown mechanisms; #7's VFIO bind/teardown needs root + a
dedicated passthrough device. No effort-ready-without-a-blocker Tier-1 work remains.
(#8 + #9 + #11 + #12 + R-CAP-ATTEN-WIRE shipped 2026-05-27/28;
#8 live-restart wiring is R-T1-8-RESTART-WIRING.)

---

## §5 Pending — hardware-gated

Cannot be scheduled by effort; opens when hardware is available.

### R5 — VFIO acquire on real host — 🟡 slices 1 + 2 + 3 + 4 shipped (2026-05-30/31)

- **Slice 1.** `vfio.zig` — the container/group/device-fd bind orchestration
  behind an injectable `VfioOps` seam: open container → API + TYPE1 checks → open
  group → VIABLE gate → SET_CONTAINER → SET_IOMMU → GROUP_GET_DEVICE_FD, with
  **atomic errdefer-unwind** (no container/group/device fd leak on a partial bind,
  Part 3.4) + `unbind` teardown + an honest errno→`Err` map.
- **Slice 2.** the host-driver detach — `detachHostDriver` / `reattachHostDriver`
  behind a `DriverOps` seam: unbind the host driver → `driver_override=vfio-pci`
  → `drivers_probe`, with **atomic reattach** if any step fails (a failed detach
  never orphans the device). `vAcquire` now does detach → bind as one atomic
  operation (bind failure reattaches the host driver); `vRelease` does unbind →
  reattach. Pinned with injected fakes (12 Zig tests total; the atomic unwind AND
  the atomic reattach each verified by a negative control).
- **Slice 3.** the lifecycle's other two ends, filling the two previously-null
  PCI vtable slots: `.fd_for_token` (hand the client the bound VFIO device fd for
  SCM_RIGHTS — the "delegate usage" G4 dividend) + `.revoke` (**revoke below the
  fd**, §3.4: forced reclaim by URN → unbind the VFIO fds + reattach the host
  driver → the client's dup'd fd references a dead kernel object; idempotent when
  nothing is bound). The `vfio`/`driver` ops are now injected on the backend
  (DIP), so the full acquire → fd_for_token → revoke lifecycle is pinned
  host-independently with fakes + a synthetic bound device (revoke teardown +
  removal + idempotency negative-control verified).
- **Slice 4 (2026-05-31) — shrink the hardware gate.** Where the real ops CAN run
  without a passthrough device, they now do, pinned against this host's live
  kernel (host-integration tests, house style — real path where present, honest-
  absence where not): (A) `realOps` drives the REAL container ioctls against
  `/dev/vfio/vfio` — `VFIO_GET_API_VERSION` returns exactly `VFIO_API_VERSION`,
  `CHECK_EXTENSION(TYPE1)` is recognised (here API=0, TYPE1=1), so the first half
  of `bind()`'s real path executes, no longer fake-only; (B) every UAPI constant +
  the `GroupStatus` layout is cross-checked against the installed `linux/vfio.h`
  (whitespace-insensitive `#define` match) with a bridge asserting our computed
  numbers equal `_IO(';',100+n)` — a drift detector tied to on-disk ground truth;
  (C) the detach sysfs paths we write to (`driver_override` / `driver/unbind` /
  `drivers_probe`) are verified to EXIST on the live `/sys` via a real-device
  enumeration, atop an exact-string construction pin. 4 Zig pins.
- **Hardware-gated remainder (now narrowed).** Only `GROUP_GET_DEVICE_FD` + the
  destructive sysfs WRITES in `realOps`/`realDriverOps` stay HARDWARE-UNVERIFIED —
  the irreducible "take a real device off its host driver and hand out its fd"
  core. They run only on a real host; here the privileged open/write fails and the
  caller returns an honest `Err` (never a faked success). What remains is purely
  **running** that core on a real passthrough device (root + a device freed from
  its host driver) and confirming the final UAPI details against live hardware.
- **Closes.** Tier-1 #7 (LINUX-01 IOMMU enforcement) once slice 2 + a real
  passthrough device land.

### R6 — USB worker reset-escalation ladder verification

- **Source.** [`hrb-design/mgf-hrb-usb-worker.md`](../../hrb-design/mgf-hrb-usb-worker.md) §9 — Phase U4 follow-ons.
- **Status.** Partially shipped (commit `4160bc5`); interrupt-rate watchdog + `Quarantined` state transitions against a stub-firmware fake still open.
- **Outstanding.** Either a real device with reset-stuck failure mode OR a stub-firmware test substrate.

---

## §6 Pending — multi-week / cross-project

These items genuinely require cross-project coordination or
multi-session investment.

### R7 — G3: vm-pair second-adopter binding (federation PR-* promotion)

- **Source.** [`POST_PRESENTATION_TRACKER.md`](POST_PRESENTATION_TRACKER.md) G3 (🟡 PARTIAL); [`mgf-standard/docs/standards/PRESENTATION_LAYER.md`](../../../mgf-standard/docs/standards/PRESENTATION_LAYER.md) §8.2 "Federation adoption status".
- **What shipped this round** (2026-05-27): the standard accommodates the vm-pair layout variant (`presentation/{cli,gui}/` per PR-03 §5.5); gap analysis in §8.2 enumerates what the vm pair needs.
- **Outstanding** (work happens in `mgf-vm-sdk` + `vm-vmanager-app`, not here):
  1. PR-01 import-linter contract on `mgf-vm-sdk` (forbid UI frameworks).
  2. PR-06 fmt module decision (ship `mgf.vm.sdk.fmt` OR mark N/A in tracker).
  3. PR-07 §PR sections in both vm pair conformance trackers.
  4. 5 hardened-pair items per PRESENTATION_LAYER.md §8.1 (per-rule conformance tests, adversarial-input pins, import-linter alive meta-test, cross-version feature-detection, conformance-tracker drift detector).
- **Promotion gate.** When the vm pair ships all 4, PR-* drops the DRAFT marker from PRESENTATION_LAYER.md §8 and becomes federation-wide L1-binding.
- **Requires.** Coordination with vm-pair maintainers (different repos, different release cadence). Either the user drives a dedicated session against the vm pair OR vm-pair maintainers adopt on their own timeline.

### R8 — Phase 6 of Part 13: module helper sibling

- **Source.** [`hrb-design/mgf-hrb-self-aware-introspection.md`](../../hrb-design/mgf-hrb-self-aware-introspection.md) §9.
- **Status.** 🚫 explicitly out of scope this quarter (per design doc + INDEX.md).
- **Outstanding.** Needs new `mgf-hrb-modules-priv` repo + its own design conversation. 5 sub-features remain: sibling repo + `module_deps` + `module_load`/`module_unload` + helper doctor check + `device_ready` event kind.

---

## §7 Wave-2 deferred test pins — ✅ ALL CLEARED (2026-05-30)

**All 6 wave-2 cluster-G regression pins are now closed** (R9 + R11 on
2026-05-30, R10 on 2026-05-29; G2/G7/G8 earlier). Each "blocked on
infrastructure" gate dissolved once the *actual* race surface was isolated:
R9/R10 became deterministic sweeps stronger than the fuzz harnesses they waited
on, and R11's data race lives entirely in the claims table + lock — a synthetic
seed exercises it with no hardware. The residual hardware-locked work (real
USBDEVFS_CLAIMINTERFACE / SCM_RIGHTS / VFIO-bind end-to-end) is tracked in §3,
not here. Documented in [`POST_PRESENTATION_TRACKER.md`](POST_PRESENTATION_TRACKER.md)
§4 G6 closure ledger.

### R9 — wave-2 G3: priv_ipc fd-leak burst pin ✅ CLOSED 2026-05-30

- **Fix.** Shipped commit `52b3a91` (errdefer-guarded extraction + the M6
  multi-fd close); leak window structurally closed.
- **Pin.** `recvResponseWithFd` is now driven over a socketpair (a faithful
  stand-in for the privileged helper) with a sustained burst of the three
  hostile fd-bearing frame shapes — G3 malformed-short, M6 two-fd, and the
  M6/`MSG_CTRUNC` three-fd overflow — and a kernel-lowest-free-fd probe proves
  the descriptor table is exactly where it started after ~24 hostile frames
  (zero leak). The deterministic sweep of every leak path is stronger than the
  helper-subprocess fuzz harness the pin was waiting on, and needs no
  subprocess or `/proc` parsing.

### R10 — wave-2 G5: hostile-worker UB on enumFromInt ✅ CLOSED 2026-05-29

- **Fix.** Shipped commit `83c1ff4` (`std.meta.fields(...)`-derived bound).
- **Pin.** The three inline bounds-checks in `usb_priv_main.zig` are now one
  `safeEnumFromInt(comptime E, ordinal) ?E` helper, exhaustively verified: all
  256 `u8` ordinals × `{ClaimMode, ControlVerb, ResetLevel}` — in-range decodes
  correctly, every out-of-range ordinal is rejected (no `@enumFromInt` UB). For a
  `u8` domain the exhaustive sweep is strictly stronger than the fuzz harness the
  pin was waiting on.

### R11 — wave-2 G6: concurrent raw+release race ✅ CLOSED 2026-05-30

- **Fix.** Shipped commit `c04fb59` (dedicated `claims_lock` over every
  `claims`/`next_token` access + value-copy returns from `findClaim` /
  `findClaimByUrn`); race window closed by the lock.
- **Pin.** The gate ("needs real USB or a deeper FakeBackend") was too
  pessimistic — the race surface is only the `claims` table + lock, which a
  synthetic seed populates with no hardware. A `linux_usb.zig` contention pin
  races the REAL `findClaim`/`findClaimByUrn` (in 6 reader threads) against a
  writer reproducing `vAcquire`'s `append` and `vRelease`'s `swapRemove`
  critical sections verbatim, with a frequently-freed backing buffer. A
  value-copy invariant (every observed claim must be canonical for its id —
  no torn `id`↔`urn`↔`if_num`↔`fd` blend) plus the testing allocator are the
  detectors. **Verified non-vacuous by a negative control**: stripping
  `claims_lock` from `findClaim` makes the pin fail ~90%/run (an
  index-out-of-bounds as a reader iterates a freed/reallocated buffer), while
  the locked code is deterministically green (12/12).
- **Honestly scoped.** This pins the claims-table data race (exactly what G6
  fixed). It is a contention/sampling test, not the exhaustive proof R9/R10
  could be — a data race has unbounded interleavings and this toolchain ships
  no ThreadSanitizer. The real-hardware raw+release end-to-end (live
  `USBDEVFS_CLAIMINTERFACE`, the SCM_RIGHTS channel, device-fd teardown under
  concurrent ops) needs USB + root and stays in §3.

---

## §8 Closed items — see git log

Closure history lives in the standalone audit ledgers:

- **2026-05-27 round (post-presentation 6 groups):** [`POST_PRESENTATION_TRACKER.md`](POST_PRESENTATION_TRACKER.md) §4 — G1 ✅, G2 ✅, G4 ✅, G5 ✅, G6 ✅, G3 🟡.
- **2026-05-26 round (presentation-layer hardening + standard lock-in):** `CHANGELOG.md` `[Unreleased]` entry.
- **2026-05-26 review waves 1/2/3:** [Work-roadmap memory](../../../.claude/projects/-home-bassam-PycharmProjects-mgf-hrb/memory/work_roadmap.md) — 38 commits.
- **Part 13 (self-aware introspection, 21/21):** `hrb-design/mgf-hrb-self-aware-introspection.md` §9.
- **Per-paper retrospectives:** `FEEDBACK.md` — each shipped paper carries its retrospective inline.

This roadmap doc is intentionally forward-looking; closure
history stays in those files. They're the audit trail.

---

## §9 Doc relationships (no duplication)

| Doc | Owns | Updated when |
|---|---|---|
| **THIS file** (`ROADMAP.md`) | Forward-looking queue | Work surfaces / ships / scope changes |
| [`FEEDBACK.md`](../../FEEDBACK.md) | Immutable consumer-filed papers + their status field + retrospective | Paper filed / triaged / shipped |
| [`POST_PRESENTATION_TRACKER.md`](POST_PRESENTATION_TRACKER.md) | Historical 6-group closure ledger for the 2026-05-27 round | Never (frozen — historical) |
| [`MGF_STANDARDS_CONFORMANCE.md`](MGF_STANDARDS_CONFORMANCE.md) | Per-rule conformance state | Standard rule status changes |
| `CHANGELOG.md` | Release / [Unreleased] ledger | Every shipped commit |
| `hrb-design/*` | Architecture corpus | Design decisions land |
| Memory `work_roadmap.md` | (DEPRECATED — pointer-only; this file replaced it) | Never (updated to a pointer 2026-05-27) |

---

## §10 If you're starting fresh

1. Read §1 for the snapshot.
2. If you're picking next work: look at §2 (in-flight) → §3 (scheduled effort) in priority.
3. If you're investigating a specific concern: search `FEEDBACK.md` by area prefix (USB / SDK / DOC / TOOLS / etc.).
4. If you need design rationale: cite `hrb-design/` per the §3/§4 entries' source links.
5. Never use the `work_roadmap.md` memory as a source of truth — it's stale by design (memory is session-context, not the canonical log). Memory now points here.
