# mgf-hrb — Self-Aware Hardware Introspection (design paper)

> **What this is.** A forward-design paper extending the broker's
> typed-op + event + capabilities model to deliver the six
> scenarios captured in
> [`self-aware-introspection-current-state.md`](self-aware-introspection-current-state.md):
> multi-client write coordination, driver-name + interface
> introspection, module lifecycle, condition-filtered callbacks,
> bulk topology dump, and the meta requirement of near-real-time
> broker self-awareness.
>
> **Why now.** The current-state analysis confirms the gaps are
> mostly *additive, not architectural* — the typed-op model
> accommodates the new surface without rewriting the lease engine
> or the capability layer. Designing the extensions before
> implementing protects against ad-hoc op proliferation; the
> wire vocabulary jumps from **9 ops to 20** if the full set
> lands, and the additions need to compose with each other.
>
> **Reads alongside.**
> [`mgf-hrb-complete-design.md`](mgf-hrb-complete-design.md)
> Part 1.2 (SOLID applied where it earns its place), Part 2.6
> (typed ops + raw channel), Part 2.9 (event model), Part 3
> (lease engine), Part 4 (capabilities), Part 6 (kabi seam),
> Part 12 (survivability).
> [`mgf-hrb-usb-worker.md`](mgf-hrb-usb-worker.md) §U3b
> (privileged-helper subprocess shape — the module-management
> sibling [§5.3](#53-driver--module-lifecycle--scenario-4)
> mirrors this).
> [`self-aware-introspection-current-state.md`](self-aware-introspection-current-state.md)
> (the gap inventory this paper closes).
>
> **Discipline.** Every new op + every new event kind + every
> vtable extension is justified by **the priority order**
> (correctness → completeness → operability → simplicity →
> performance — Part 1.1) and **SOLID applied where it earns its
> place** (Part 1.2). Decisions that decline a principle are
> recorded inline, not hidden. The aim is a design a future
> reader can audit, not a design that looks elegant.

---

## §1. The design's responsibility, drawn sharply

This paper adds:

- **6 new wire ops** in the "near-term" tier (mostly read-only,
  always available): `inspect_full` · `dump_chain` · `dump_all` ·
  `driver_info` · `module_deps` · `subscribe` (+ `unsubscribe`).
- **2 new wire ops** in the "self-aware" tier (broker-side
  introspection): `audit_tail` · `state_snapshot`.
- **2 new wire ops** in the "privileged" tier (require the
  module-management helper sibling): `module_load` · `module_unload`.
- **3 new event kinds**: `driver_bound` · `driver_unbound` ·
  `device_ready`.
- **1 new claim-related concept**: per-URN control-verb
  serialisation with optimistic-concurrency disclosure.
- **1 new sibling repo** (proposal): `mgf-hrb-modules-priv` —
  the privileged helper for `CAP_SYS_MODULE` operations.

It refuses:

- **Broker-side write multiplexing.** Two `SHARED_RW` clients
  writing to the same fd — the broker does not interpose. That
  would put it on the data-path, which Part 5 rules out.
- **Pushing ioctl table parsing into the broker.** ioctl numbers
  are not discoverable at the kernel ABI level; the
  registry-curation work
  ([§5.2.3](#523-the-ioctl-interface-registry))
  is *data*, not *code* — shipped as YAML, not as kernel-probing.
- **A general remote-procedure framework.** New ops follow the
  existing typed-op shape. No "rpc(method, args)" escape hatch.
- **Bundling everything into one PR.** The phasing
  ([§9](#9-phasing--dependency-graph)) ships the cheap additive
  set first; the helper sibling is its own conversation.

---

## §2. The six scenarios — design summary

For the gap details and code-verified status, see
[`self-aware-introspection-current-state.md`](self-aware-introspection-current-state.md)
§3–§4. One-paragraph design summary per scenario; the §5
subsections are the detailed mechanism.

1. **Multi-client concurrent use** ([§5.1](#51-multi-client-write-coordination--scenario-1--2)) —
   `ClaimMode` lattice already handles this for raw I/O. No
   change needed; design honest about the data-path stance.
2. **Multi-client writes / control verbs** ([§5.1](#51-multi-client-write-coordination--scenario-1--2)) —
   `control` ops gain per-URN serialisation + an
   optimistic-concurrency response shape so consumers can detect
   when their write was superseded.
3. **Driver introspection** ([§5.2](#52-driver-introspection--scenario-3)) —
   `inspect` grows `driver_name` + `driver_module` attrs (small);
   new `driver_info(name)` op returns ioctl + sysfs interfaces
   from a hand-curated registry.
4. **Driver dependency + module load/unload** ([§5.3](#53-driver--module-lifecycle--scenario-4)) —
   new `module_deps(name)` op (no privilege) parses
   `/lib/modules/$(uname -r)/modules.dep`; new
   `module_load` / `module_unload` ops route through a NEW
   privileged helper sibling (mirrors USB §U3b).
5. **Condition callbacks** ([§5.4](#54-condition-filtered-subscriptions--scenario-5)) —
   event taxonomy grows `driver_bound` / `driver_unbound` /
   `device_ready`; new `subscribe` op with server-side
   predicate filter.
6. **Bulk dump** ([§5.5](#55-bulk-topology-dump--scenario-6)) —
   three additive read-only ops: `inspect_full(urn)` (one device,
   every sysfs attr), `dump_chain(urn)` (parent walk), and
   `dump_all(scope)` (the whole tree under a scope).

**Meta — broker self-awareness** ([§5.6](#56-broker-self-awareness--meta)) —
new `audit_tail(n)` op exposes the last N operations the broker
processed; new `state_snapshot()` op reports internal-state
counters. Correlation IDs added to every response so client-side
traces can be assembled. Audit log lives in a ring buffer in
broker memory.

---

## §3. Design principles applied

This section grounds every later decision in the master design's
named principles. The aim is that a future reader can rebut a
decision by citing the principle, not by re-arguing taste.

### §3.1 The priority order (Part 1.1)

When two design choices conflict, **correctness > completeness >
operability > simplicity > performance**. Concrete applications
in this paper:

- **Correctness > completeness.** Module unload that races a
  concurrent module-in-use refuses with `Conflict` (correct) even
  though it's less complete than "force-unload"
  ([§5.3.3](#533-the-privileged-helpers-typed-verb-surface)).
- **Operability > simplicity.** The control-verb serialisation
  surface returns a richer response (`applied_value` +
  `generation_counter`) even though it's two more fields than
  `{"ok": true}` ([§5.1.2](#512-the-optimistic-concurrency-response-shape)).
- **Operability > performance.** The audit log is recorded for
  *every* op even though most are uneventful; the per-op cost is
  ~200 ns and the operability dividend is enormous
  ([§5.6.1](#561-the-audit-log)).

### §3.2 SOLID, applied per Part 1.2

Master design Part 1.2 commits to "we apply each principle where
it buys reliability, flexibility, or testability worth its
cost — and we consciously decline it where it would tax the hot
path or add ceremony without payoff. Every decline is recorded,
not hidden." This paper's principle map:

| Principle | Application in this paper | Where declined |
|---|---|---|
| **SRP** — one reason to change | Module-management lives in a SEPARATE sibling `mgf-hrb-modules-priv` because its reason-to-change (kernel module ABI / privilege escalation policy) is different from the broker's reason-to-change (lease engine + worker shape). [§5.3](#53) | The control-verb serialiser lives inside the existing per-worker code, not a new module — it changes when control verbs change, which is the same change as the verbs themselves. [§5.1.3](#513) |
| **OCP** — additive extension | THE spine of this paper. Every new op is additive (the existing 9 keep working); every new event kind is additive; the predicate-filter language is opt-in (no predicate = current scope-glob behaviour). | None — every extension is additive. |
| **LSP** — substitutable contracts | New vtable methods (`inspect_full`, `driver_info_table`) carry behavioural contracts; backends that don't implement them return `Unsupported` honestly via `capabilities()`. A backend reporting `inspect_full = true` MUST return a complete subtree, no silent truncation. [§6.2](#62-backend-capability-extensions) | None. |
| **ISP** — no client depends on unused | New `subscribe` op is its own role-interface; consumers using polling-style `monitor` are unaffected. The `module_load` / `module_unload` ops are gated by a new capability right (`load_module`) — clients without it cannot even encode the op (server rejects at the verifier). [§8](#8-capability-model) | None — every new op is rights-gated independently. |
| **DIP** — depend on abstractions | The audit log's storage is behind an abstract `AuditSink` interface so a future implementation can swap in (e.g.) a memory-mapped ring or a syslog forwarder. [§5.6.1](#561) The privileged-helper IPC is a typed channel, not a generic RPC. [§5.3.2](#532) | None. |

**One explicit decline:** the audit-log's per-op records do NOT
flow through `mgf.common.observability` (the federation logging
sink). Rationale: an OTLP/syslog hop in the hot path violates the
priority order's *performance* gate at the bottom of the stack
(every cap-checked op pays the cost). The ring lives in broker
memory; consumers query it via `audit_tail`. A future log-forwarder
can subscribe and ship; the broker itself stays local-only.
Recorded here so the decline is auditable.

### §3.3 The dependency direction law (Part 1.3)

Master design Part 1.3 names the law: dependencies point toward
`mgf-hrb-model` (pure vocabulary, depends on nothing) and toward
abstractions. Applied to this paper:

- The new wire ops + event kinds live in `mgf-hrb-model`
  (pure vocabulary).
- The new vtable methods live in `mgf-hrb-kabi` (the abstract
  backend interface).
- The implementations live in `mgf-hrb-linux` /
  `mgf-hrb-linux-usb` (the concrete backends) AND in the new
  `mgf-hrb-modules-priv` sibling.
- No backend depends on another backend. No worker depends on
  another worker. The audit-log lives in `mgf-hrb-core` (not in
  a backend) so it observes every cross-backend op.

### §3.4 The fidelity guarantee (Part 1.4)

Every new read-only op MUST tag its data with the existing
`FieldFidelity` enum (`live` / `cached` / `inferred` / `unknown`).
A `dump_all(scope)` that walks 200 USB devices in 100ms cannot
guarantee per-device liveness; the response carries per-URN
fidelity so the consumer knows. The ioctl-registry data is
tagged `inferred` (curated, not live-probed). The audit log's
counters are tagged `live`.

---

## §4. The new wire-op vocabulary

The full table — 11 new ops + the today's 9 — with rights,
fidelity, and the §5 sub-section that designs each.

| Op | Today | New | Right required | Fidelity | Privileged? | Designed in |
|---|:-:|:-:|---|---|:-:|---|
| `list` | ✓ |  | `observe` | live | | (existing) |
| `claim` | ✓ |  | `claim_exclusive` / `_shared_rw` / `_observe` | live | | (existing) |
| `release` | ✓ |  | (lease-owner) | live | | (existing) |
| `raw` | ✓ |  | `raw` | live | | (existing) |
| `inspect` | ✓ |  | `observe` | live | | (existing) — gains `driver_name` + `driver_module` attrs |
| `capabilities` | ✓ |  | (none) | live | | (existing) |
| `drain_events` | ✓ |  | `observe` | live | | (existing) |
| `control` | ✓ |  | `control` | live | | (existing) — gains serialisation + optimistic-concurrency response |
| `list_leases` | ✓ |  | `observe` | live | | (existing) |
| `inspect_full` |  | ✓ | `observe` | live | | [§5.5.1](#551-inspect_full-walks-one-devices-sysfs-subtree) |
| `dump_chain` |  | ✓ | `observe` | live | | [§5.5.2](#552-dump_chain-walks-the-parent-chain-upward) |
| `dump_all` |  | ✓ | `observe` | mixed (per-URN tagged) | | [§5.5.3](#553-dump_all-walks-every-urn-matching-a-scope) |
| `driver_info` |  | ✓ | `observe` | inferred | | [§5.2.2](#522-driver_info-reports-the-known-interface-of-a-driver) |
| `module_deps` |  | ✓ | `observe` | live (parsed from `modules.dep`) | | [§5.3.1](#531-module_deps-the-non-privileged-half) |
| `module_load` |  | ✓ | `load_module` (NEW right) | live | YES | [§5.3.3](#533-the-privileged-helpers-typed-verb-surface) |
| `module_unload` |  | ✓ | `unload_module` (NEW right) | live | YES | [§5.3.3](#533-the-privileged-helpers-typed-verb-surface) |
| `subscribe` |  | ✓ | `observe` | live | | [§5.4.2](#542-the-subscribe-op-with-server-side-predicate) |
| `unsubscribe` |  | ✓ | (subscription-owner) | live | | [§5.4.2](#542-the-subscribe-op-with-server-side-predicate) |
| `audit_tail` |  | ✓ | `audit_read` (NEW right) | live | | [§5.6.1](#561-the-audit-log) |
| `state_snapshot` |  | ✓ | `audit_read` (NEW right) | live | | [§5.6.2](#562-the-state-snapshot-op) |

Three new capability rights: `load_module`, `unload_module`,
`audit_read`. The latter is a low-risk grant suitable for
diagnostics consumers; the first two carry the full
`CAP_SYS_MODULE` threat surface and need explicit operator
acceptance.

---

## §5. Per-scenario design

### §5.1 Multi-client write coordination — scenarios 1 + 2

**Scenario 1 needs no change.** [Current-state §4.1](self-aware-introspection-current-state.md#41-scenario-1--multi-client-concurrent-use-)
confirmed the lease lattice is end-to-end correct.

**Scenario 2 needs serialisation + disclosure on `control` verbs.**

#### §5.1.1 Per-URN control-verb serialiser

Inside `mgf-hrb-core`, each URN that has ever received a
`control` op carries a small per-URN state object:

```zig
// New in hrb-code/src/core.zig (or core_control.zig if it grows)
pub const ControlState = struct {
    generation: u64,            // monotonic; ++ on every applied write
    last_writer_cap_id: u64,    // who wrote last
    last_value: ?[]const u8,    // the value as actually applied
    last_ts_ms: u64,             // when
    queue_depth: u8,             // how many writes pending behind us
    serialiser: SpinMutex,       // FIFO ownership
};
```

The serialiser is **per-URN-per-verb**. A `set_authorized` and a
`set_autosuspend` against the same URN are independent (different
sysfs files); two `set_authorized` calls against the same URN
serialise. Hash key: `(urn, verb)`.

Throughput cost: a `SpinMutex` acquire-release per control call
(~50 ns uncontended; ~1 µs under contention). Control verbs are
not hot-path; this is comfortably within the operability budget.

#### §5.1.2 The optimistic-concurrency response shape

Today's `control` response is `{"ok": true}`. Proposed extension:

```jsonc
{
  "ok": true,
  "generation": 17,           // post-write generation counter
  "applied_value": "1",       // the value actually on disk after our write
  "superseded_by": null,      // OR { generation: 18, cap_id: ..., at_ms: ... }
                              // when another writer raced ahead before we returned
  "queue_depth": 0            // how many writes were behind ours in the FIFO
}
```

The `superseded_by` field is non-null ONLY when the broker
detected another writer landed *between* our write completing and
our response returning. A consumer reading `superseded_by != null`
knows: "my write happened; it may or may not be the current state;
here's who overwrote me."

Schema-versioned via the existing wire `version` field (the
hello-handshake's `version: "0.1.0"` rolls forward).

#### §5.1.3 SRP decline recorded

This lives inside the per-worker control path (`linux_usb.zig:vControl`),
not a new module. Reason-to-change is the same as the verbs
themselves — when a new verb lands, the serialiser shape
extends with it. A separate module would create a circular
dependency without buying isolation.

### §5.2 Driver introspection — scenario 3

Three sub-asks; three sub-designs.

#### §5.2.1 `inspect` gains `driver_name` + `driver_module` attrs

Trivial extension in `linux_usb.zig:vInspect` (mirrors in
`linux_backend.zig:vInspect` for PCI). After the existing
attr-emit sequence:

```zig
// Read /sys/bus/usb/devices/<iface>/driver symlink → driver dir → basename.
var drv_path_buf: [256]u8 = undefined;
const drv_link = std.fmt.bufPrintZ(&drv_path_buf,
    "{s}/{s}/driver", .{ USB_DEVICES_DIR, iface_name }) catch return;
if (readSysfsSymlink(drv_link)) |drv_dir| {
    const drv_name = std.fs.path.basename(drv_dir);
    attrs.push(attrs.ctx, .{ .key = "driver", .value = dupeShort(drv_name) });
    // .../driver/module is the loaded kernel module (often == driver name,
    // but e.g. snd_usb_audio's module is snd-usb-audio).
    const mod_link = ...; // similar for driver/module
    if (readSysfsSymlink(mod_link)) |mod_dir| {
        attrs.push(attrs.ctx, .{ .key = "module", .value = dupeShort(std.fs.path.basename(mod_dir)) });
    }
}
```

~20 LoC per backend. Read-only; no privilege; no wire-format
change (just two more `(key, value)` entries in the existing
`ResourceDetail.attrs` tuple).

Tests: extend `tests/conformance/test_g2_honest_emptiness.py`
with an assertion that `inspect("hw:/usb/.../if0").attr("driver")`
returns the kernel driver name when one is bound, OR returns
`None` honestly when the device is unbound.

#### §5.2.2 `driver_info` reports the known interface of a driver

```jsonc
// Request
{ "op": "driver_info", "name": "usbhid", "cap_id": ... }

// Response
{
  "ok": true,
  "name": "usbhid",
  "module": "usbhid",
  "module_loaded": true,
  "bound_count": 3,          // how many devices currently bound
  "sysfs_attrs": [           // attrs the driver exposes on bound devices
    { "name": "country", "type": "string", "writable": false },
    { "name": "report_descriptor", "type": "blob", "writable": false }
  ],
  "ioctls": [                // from the curated registry; never live-probed
    { "name": "HIDIOCGRDESCSIZE", "number": "0x4004480001",
      "header": "<linux/hidraw.h>", "arg": "int *", "fidelity": "inferred" }
  ],
  "char_devices": [          // /dev nodes this driver creates
    { "pattern": "/dev/hidraw*", "class": "hidraw" }
  ],
  "uevent_actions": ["bind", "unbind"],
  "registry_version": "2026-05-26",
  "fidelity": "inferred"     // top-level fidelity for the whole record
}
```

**Three data sources:**

- **`module_loaded` + `bound_count`** — live, from
  `/sys/module/<name>/holders/` and `/sys/bus/.../drivers/<name>/`.
- **`sysfs_attrs`** — live, walked from the first currently-bound
  device's sysfs node.
- **`ioctls` + `char_devices`** — curated YAML registry shipped
  with `mgf-hrb-model` (see [§5.2.3](#523-the-ioctl-interface-registry)).

The whole response is tagged `inferred` because the ioctls half
is inferred from the registry, not probed. A consumer using the
ioctl table to write a UAPI client knows it's working off a
curated map.

#### §5.2.3 The ioctl-interface registry

Lives at `hrb-code/src/data/driver_interfaces/<driver_name>.json`,
one file per driver class. **Note (2026-05-26):** the deployed
shape is JSON-on-the-Zig-side (loaded via `@embedFile` at
broker startup), not YAML-on-the-Python-side — the latter was
the original aspiration but didn't survive contact with Zig
0.16's std lib (no built-in YAML parser; the data needs to be
readable from `linux_backend.zig` for `driver_info`, which is
the Zig broker, not the Python SDK). The two-shape evolution
is documented at `hrb-code/src/data/driver_interfaces/README.md`.
The semantics below stay valid; treat the YAML below as
schematic. Reload is `state_snapshot`-triggered (the embed is
compile-time; the broker reloads on next start).

```yaml
# Schematic shape — actual file is hrb-code/src/data/driver_interfaces/usbdevfs.json
driver: usbdevfs
module: usbcore         # usbdevfs is built into usbcore, not a separate module
header: <linux/usbdevice_fs.h>
maintainer: mgf-hrb     # us; we curate this
last_verified: 2026-05-26
ioctls:
  - name: USBDEVFS_CONTROL
    number: 0xc0185500
    arg: struct usbdevfs_ctrltransfer
    direction: rw       # _IOWR
  - name: USBDEVFS_BULK
    number: 0xc0185502
    arg: struct usbdevfs_bulktransfer
    direction: rw
  # ... 25 USBDEVFS ioctls total; see <linux/usbdevice_fs.h>
char_devices:
  - pattern: /dev/bus/usb/*/*
    permissions: 0664   # via udev rule
```

**Curation policy:**

- The federation maintains the registry for drivers we use
  directly: USBDEVFS, VFIO, DRM-render, V4L2, HIDRAW.
- A `driver_info` call for a driver not in the registry returns
  `{"name": "X", "module_loaded": ..., "ioctls": null,
  "registry_version": null, "fidelity": "unknown"}` — honest
  emptiness (Part 1.4).
- A registry entry's `last_verified` field MUST be updated when
  the corresponding kernel UAPI header changes. CI gate:
  `tests/conformance/test_ioctl_registry_currency.py` (new) reads
  the live `/usr/include/linux/<header>.h`, parses the `#define
  X _IO*(...)` lines, and asserts every number in the registry
  matches. A mismatch fails the test with a diff.

**Refusal:** the broker does NOT live-probe ioctls (calling
`ioctl(fd, X, NULL)` and reading errno). That's UB-adjacent for
drivers that interpret the arg unconditionally; we are
explicitly NOT making the broker the thing that crashes on a
driver bug.

### §5.3 Driver + module lifecycle — scenario 4

Split into two halves that ship independently:
**non-privileged dependency-graph reads** ([§5.3.1](#531-module_deps-the-non-privileged-half))
and **privileged module load/unload** ([§5.3.2](#532-why-a-new-sibling)).

#### §5.3.1 `module_deps` — the non-privileged half

`/lib/modules/$(uname -r)/modules.dep` is a plain-text file
mapping each kernel module to its transitive dependencies. The
broker parses it; no privilege needed:

```jsonc
// Request
{ "op": "module_deps", "name": "usbhid" }

// Response
{
  "ok": true,
  "name": "usbhid",
  "path": "kernel/drivers/hid/usbhid/usbhid.ko.zst",
  "loaded": true,
  "depends": ["hid", "usbcore"],
  "depends_transitive": ["hid", "usbcore"],   // flattened
  "depended_on_by": ["hid_generic"],          // reverse — modules holding this one
  "fidelity": "live"
}
```

Implementation: read + cache `modules.dep` at broker startup;
rebuild cache on `SIGHUP` and on `module_load` / `module_unload`
response. Reverse-dep (`depended_on_by`) is computed from
`/sys/module/<name>/holders/` (live).

#### §5.3.2 Why a new sibling

Loading or unloading a kernel module requires `CAP_SYS_MODULE`.
The mgf-hrb broker runs unprivileged by design (Part 12 §2 —
"the broker holds no kernel authority it doesn't need"). Two
paths considered:

- **Grant `CAP_SYS_MODULE` to the broker.** Rejected: violates
  Part 12's least-privilege principle; one bug in the broker
  becomes one bug with module-loading authority.
- **A separate privileged helper subprocess.** Accepted: mirrors
  the USB-worker §U3b plan. Helper holds the capability; broker
  forwards typed verbs over a typed socketpair; helper accepts
  ONLY the typed verbs (no client bytes); response shape matches
  the broker's other ops.

The helper lives in a NEW sibling repo: **`mgf-hrb-modules-priv`**.
Same federation discipline as `mgf-hrb-usb-priv` when it lands.
Console script: `mgf-hrb-modules-priv` (single binary).
Communication: SOCK_SEQPACKET (matches USB helper).

#### §5.3.3 The privileged helper's typed-verb surface

```jsonc
// helper-only protocol, accessible to the broker only
// (via socketpair handed off at broker startup)

{ "op": "module_load",
  "name": "usbhid",
  "params": [ "rid=1" ],            // module parameters; passed to modprobe
  "deadline_ms": 5000
}
// → { "ok": true, "loaded": true, "version": "6.8.0", "size_bytes": 159744 }
// → { "ok": false, "err": "ModuleNotFound", "detail": "modprobe: FATAL: ..." }
// → { "ok": false, "err": "Conflict", "detail": "module already loaded" }

{ "op": "module_unload",
  "name": "usbhid",
  "force": false,                   // never true; helper rejects force-unload
  "deadline_ms": 5000
}
// → { "ok": true, "unloaded": true }
// → { "ok": false, "err": "Conflict", "detail": "module is in use",
//      "holders": [ "hid_generic", "/sys/.../1-1.4:1.0" ] }
```

**Honest refusal of force-unload.** The kernel's `rmmod -f`
(MODULE_FORCE_LOAD) is a known crash-introducer; the helper
NEVER passes it. If a module is in use, the response names the
holders so the caller can free them first. This is the
priority order's *correctness > completeness* applied
([§3.1](#31-the-priority-order-part-11)).

**Deadline plumbing.** Both ops take `deadline_ms`. `modprobe`
takes nontrivial wall-clock on cold start (loading dependencies
from disk); the helper enforces the deadline by sending SIGTERM
to its `modprobe` child if it overruns. Same pattern as the
broker's worker deadlines (Part 12 §3).

#### §5.3.4 LSP contract for the helper

The helper is a backend in the kabi-seam sense: it implements
a subset of the `KernelBackend` vtable plus the new
`module_load` / `module_unload` ops. Its conformance test suite
(`tests/conformance/test_module_helper_*.py`) mirrors what the
USB helper will get: every op tested for happy path + every
documented error variant + a timeout test + an in-use rejection
test.

The broker probes the helper's `capabilities()` at startup;
absence (helper not running, or built without `CAP_SYS_MODULE`)
means `module_load` / `module_unload` return `Unsupported`
honestly. **The doctor surface gains a row:**
`MODULE_HELPER_AVAILABLE`.

### §5.4 Condition-filtered subscriptions — scenario 5

#### §5.4.1 New event kinds

`model.zig:Event` extends additively:

```zig
pub const Event = union(enum) {
    added: struct { urn: Urn },
    removed: struct { urn: Urn },
    state_changed: struct { urn: Urn, old: State, new: State, previous_urn: ?Urn = null },
    gap: struct { source: GapSource, since_ms: u64 },

    // NEW (§5.4) — driver lifecycle from netlink uevents.
    driver_bound: struct { urn: Urn, driver: []const u8 },
    driver_unbound: struct { urn: Urn, driver: []const u8 },

    // NEW (§5.4.3) — synthetic; emitted by per-driver-class adapters.
    device_ready: struct { urn: Urn, signal: ReadySignal },
};

pub const ReadySignal = enum {
    block_node_appeared,        // USB Mass Storage, NVMe — /dev/sdX / /dev/nvme*
    input_node_appeared,        // USB HID — /dev/input/event*
    drm_node_appeared,          // GPUs — /dev/dri/card*
    sysfs_ready_attr,           // generic — a sysfs attr the adapter watches
    custom: []const u8,         // adapter-defined; carries a name
};
```

Existing consumers using only `added` / `removed` / `state_changed`
keep working (union-variant additions are forward-compatible at
the JSON wire). New consumers can match on the new kinds.

#### §5.4.2 The `subscribe` op with server-side predicate

Today's `drain_events` (polling) and `monitor` (iter) stay
unchanged — they keep working without predicates. The new
`subscribe` op is a parallel surface that pushes events to a
per-client queue with a server-side filter:

```jsonc
// Request
{
  "op": "subscribe",
  "id": "vmgr-hid-watch-1",    // caller-chosen; unique-per-client
  "scope": "hw:/usb/**",
  "predicate": {                // a minimal expression tree
    "all_of": [
      { "kind_in": ["driver_bound", "added"] },
      { "attr_eq": { "key": "vid", "value": "0x1234" } }
    ]
  },
  "cap_id": ...
}

// Response
{ "ok": true, "subscription_id": "vmgr-hid-watch-1", "queue_max": 1024 }

// Events arrive via drain_events / monitor / monitor_fd just as today,
// but ONLY events matching the predicate land in this client's queue.
// Other subscribers' predicates are independent.
```

The predicate expression tree is intentionally minimal:

```jsonc
// Operators
{ "all_of": [...] }              // AND
{ "any_of": [...] }              // OR
{ "not": {...} }                 // NOT

// Terms
{ "kind_in": ["added", "driver_bound"] }
{ "urn_glob": "hw:/usb/001/**" }
{ "attr_eq": { "key": "...", "value": "..." } }
{ "attr_in": { "key": "...", "values": ["x", "y"] } }
```

No arithmetic, no regex, no Turing-completeness. If the operator
grammar needs to grow, it grows additively — that's the OCP
discipline. The minimal grammar handles the common cases (filter
by kind + URN glob + a single attr) without becoming a query
language the broker has to defend against.

**Unsubscribe is mandatory.** A client that disconnects cleanly
releases all its subscriptions (server tracks subscriptions per
cap_id; cap_id is bound to peer per Tier-0 #2). A client that
crashes — the broker reaps via the existing peer-disconnect path.

**Backpressure.** Each subscription has a bounded queue
(`queue_max` returned at subscribe time; default 1024). When the
queue fills, the OLDEST event is dropped AND a `gap` event is
emitted into the queue with `source = subscription_overflow`.
Gap-honest is the project's contract (Part 5.3.3); the
subscription path inherits it.

#### §5.4.3 The "device ready" adapters

`device_ready` is synthesised by per-driver-class adapters, not
read from a kernel-emitted event. Each adapter lives in the
relevant backend and is small (~50 LoC each):

| Adapter | Trigger | Signal carried |
|---|---|---|
| `usb_block_ready` | block-subsystem `ACTION=add` event with parent matching a USB device URN | `block_node_appeared` |
| `usb_input_ready` | input-subsystem `ACTION=add` event with parent matching a USB device URN | `input_node_appeared` |
| `drm_render_ready` | drm-subsystem `ACTION=add` event for `/dev/dri/renderD*` | `drm_node_appeared` |
| `nvme_namespace_ready` | block-subsystem `ACTION=add` event for `/dev/nvme*n*` + namespace-controller probe via sysfs | `block_node_appeared` |

Adapters opt in via the backend's capability declaration:
`capabilities().emits_ready_events = true` for backends that have
at least one adapter. A consumer subscribing to
`device_ready` events against a backend that doesn't emit them
gets honest empty.

### §5.5 Bulk topology dump — scenario 6

Three additive read-only ops, all under the `observe` right.

#### §5.5.1 `inspect_full` — walks one device's sysfs subtree

```jsonc
// Request
{ "op": "inspect_full", "urn": "hw:/usb/.../if0", "depth": "all" }
//   depth = "device" | "interface" | "all" (default "all")

// Response
{
  "ok": true,
  "urn": "hw:/usb/.../if0",
  "tree": {
    // mirrors the sysfs directory layout exactly:
    "bInterfaceClass": "3",
    "bInterfaceSubClass": "1",
    "driver": { "_symlink": "/sys/bus/usb/drivers/usbhid" },
    "endpoint": {
      "ep_81": { "wMaxPacketSize": "8", ... },
      "ep_02": { "wMaxPacketSize": "8", ... }
    },
    // ... every attribute file in the device's sysfs subtree
  },
  "fidelity": "live",
  "captured_at_ms": 17324893
}
```

Symlinks rendered as `{"_symlink": "<target>"}`. Binary attrs
(like `report_descriptor`) base64-encoded.

#### §5.5.2 `dump_chain` — walks the parent chain upward

```jsonc
// Request
{ "op": "dump_chain", "urn": "hw:/usb/001/004.if0" }

// Response
{
  "ok": true,
  "chain": [
    { "urn": "hw:/usb/001/004.if0",   "level": "interface", "summary": "..." },
    { "urn": "hw:/usb/001/004",       "level": "device",    "summary": "..." },
    { "urn": "hw:/usb/001",           "level": "hub",       "summary": "..." },
    { "urn": "hw:/usb/bus/1",         "level": "root_hub",  "summary": "..." },
    { "urn": "hw:/pci/0000:00:14.0",  "level": "controller","summary": "..." }
  ],
  "fidelity": "live"
}
```

Each chain entry carries a one-line summary; the consumer calls
`inspect_full` per URN for full detail. The point of the chain
op is **topology**, not bulk data.

#### §5.5.3 `dump_all` — walks every URN matching a scope

```jsonc
// Request
{ "op": "dump_all", "scope": "hw:/usb/**", "depth": "interface" }

// Response
{
  "ok": true,
  "scope": "hw:/usb/**",
  "count": 47,
  "items": [
    { "urn": "hw:/usb/...", "detail": { ... } },
    ...
  ],
  "fidelity_summary": { "live": 47, "cached": 0, "unknown": 0 },
  "captured_at_ms": 17324893
}
```

**Wire-size discipline.** `dump_all` against `hw:/**` on a host
with dozens of PCI functions + a hub of USB devices can be tens
of KB. The broker's response buffer (raised 1 KiB → 16 KiB in
commit `271bdd4`) is sized for `list` + `inspect` + `capabilities`;
`dump_all` likely needs a 256 KiB buffer OR a chunked-response
shape. Open decision in [§11](#11-open-questions).

### §5.6 Broker self-awareness — meta

#### §5.6.1 The audit log

Every wire op the broker handles is recorded in a fixed-size
ring buffer in broker memory:

```zig
pub const AuditRecord = struct {
    seq: u64,               // monotonic; the broker's per-op counter
    ts_ms: u64,             // broker-monotonic ms
    cap_id: u64,            // who called
    peer_pid: u32,          // from SO_PEERCRED at accept-time
    peer_uid: u32,
    op: []const u8,         // "list", "claim", etc
    urn: ?[]const u8,       // when applicable
    outcome: Outcome,       // ok | err | timeout | conflict
    err_code: ?[]const u8,  // when outcome != ok
    duration_us: u32,       // wall-clock from request-parse to response-emit
    correlation_id: ?u64,   // when the caller passed one in
};

pub const Outcome = enum { ok, err, timeout, conflict };
```

Ring size: hardcoded at 4096 records (`MAX_RING` in
`hrb-code/src/audit.zig`). The env-var configuration
(`MGF_HRB_AUDIT_RING_SIZE`) is reserved in the design but not
yet wired in code — change it at the constant for now; an
env-var lookup is a future addition once a real consumer asks
for it. Each record is ~400 bytes (bounded inline buffers for
op/urn/err/correlation_id, no allocator); the full ring is
~1.6 MB of broker memory. At a sustained 1000 ops/sec it covers
the last ~4 seconds; at 10 ops/sec (typical interactive use) it
covers the last ~6 minutes. Operators detect ring overflow via
the `audit_dropped_total` field on `state_snapshot` (cluster B3 /
2026-05-26): a non-zero delta between two snapshots means
records are being silently overwritten.

**Storage abstraction.** The ring is behind an `AuditSink`
interface (DIP applied):

```zig
pub const AuditSink = struct {
    push: *const fn (ctx: *anyopaque, record: AuditRecord) void,
    snapshot: *const fn (ctx: *anyopaque, n: u32, out: *[]AuditRecord) usize,
};
```

The default impl is the in-memory ring. A future impl could
forward to syslog OR write to a file OR ship to OTLP. The
default stays in-process to honour the priority-order *performance*
gate (no I/O on the hot path).

**No mgf.common.observability hop.** [§3.2](#32-solid-applied-per-part-12)
records this explicit decline.

#### §5.6.2 The `state_snapshot` op

```jsonc
// Request
{ "op": "state_snapshot" }

// Response
{
  "ok": true,
  "ts_ms": 17324893,
  "uptime_ms": 432189,
  "broker_version": "0.1.0",
  "ops_total": 17234,
  "ops_per_second_1m": 12.4,
  "active_connections": 7,
  "active_leases": 3,
  "active_subscriptions": 5,
  "cap_table_size": 7,
  "audit_ring_used": 4096,
  "audit_ring_capacity": 4096,
  "workers": {
    "hw:/pci/":  { "backend": "mgf-hrb-linux",     "mode": "read_only",  "url_count": 36 },
    "hw:/usb/":  { "backend": "mgf-hrb-usb",       "mode": "full",       "url_count": 15 },
    "hw:/test/": { "backend": "mgf-hrb-fake",      "mode": "full",       "url_count":  2 }
  },
  "memory": {                          // best-effort; OS-dependent
    "rss_bytes": 12582912,
    "allocator_high_water_bytes": 8388608
  },
  "fidelity": "live"
}
```

Cheap to gather (all counters live in broker memory already).
Rights-gated by `audit_read`. Consumers: `doctor`, dashboards,
the GUI's "broker health" pane.

#### §5.6.3 Correlation IDs

Every op response gains an optional `correlation_id` echo: if
the caller passes `"correlation_id": "vmgr-claim-001"` on a
`claim` request, the broker echoes it in the response AND records
it in the audit-ring entry. Lets the caller build a causal trace
across multiple ops without server-side reconstruction.

The broker does NOT generate correlation IDs itself; that's a
client responsibility (uuid, request-counter, whatever fits).
Keeps the broker stateless re: correlation; SRP applied.

#### §5.6.4 `audit_tail(n)` for retrospective queries

```jsonc
// Request
{ "op": "audit_tail", "n": 100 }

// Response
{
  "ok": true,
  "records": [ ... up to N most-recent AuditRecords ... ],
  "ring_capacity": 4096,
  "earliest_ts_ms": ...,
  "fidelity": "live"
}
```

Rights-gated by `audit_read`. Useful for `doctor` ("show me what
happened in the last 30 seconds while I'm trying to figure out
why my claim failed").

---

## §6. Kabi seam extensions

The kabi seam (`hrb-code/src/kabi.zig`) gains four optional
vtable methods. All are nullable (LSP): backends that don't
implement them return `Unsupported` honestly via `capabilities()`.

### §6.1 New vtable methods

```zig
pub const VTable = struct {
    // ... existing methods ...

    /// §5.5.1 — walk the device's sysfs subtree, push every attr
    /// + every symlink. Same AttrSink shape as `inspect`. Optional;
    /// backends without subtree-walk semantics (PCI today, Fake)
    /// leave this null.
    inspect_full: ?*const fn (ctx: *anyopaque, urn: *const Urn,
        attrs: *const AttrSink, depth: InspectDepth, deadline: Deadline) Err!Resource,

    /// §5.5.2 — walk the parent chain upward; push each level
    /// as a (urn, level, summary) triple via the chain sink.
    dump_chain: ?*const fn (ctx: *anyopaque, urn: *const Urn,
        sink: *const ChainSink, deadline: Deadline) Err!void,

    /// §5.4.3 — does this backend have per-driver-class ready-event
    /// adapters? Capability hint; consumed by `capabilities()`.
    emits_ready_events: bool,    // not a function — a const field

    /// §5.2.2 — return the curated driver-info record for a driver
    /// name. Implemented by the worker that owns the driver class
    /// (USB worker for USB drivers, etc); falls back to the in-mem
    /// registry if no worker claims it.
    driver_info: ?*const fn (ctx: *anyopaque, name: []const u8,
        sink: *const FactSink, deadline: Deadline) Err!void,
};
```

`dump_all` does NOT need a new vtable method — it's a core-level
fan-out over `enumerate` + `inspect_full`. Same pattern as
`list_leases` calling `enumerate` per worker.

### §6.2 Backend capability extensions

`BackendCapabilities` (in `kabi.zig`) gains four new bool fields:

```zig
pub const BackendCapabilities = struct {
    // ... existing fields ...
    pub const Ops = struct {
        // ... existing per-op flags ...
        inspect_full: bool,
        dump_chain: bool,
        driver_info: bool,
        emits_ready_events: bool,
    };
};
```

Backends declare what they support; `doctor` and `capabilities()`
both surface the matrix. The PCI backend probably ships
`inspect_full = true, dump_chain = true, driver_info = false,
emits_ready_events = false` initially; USB ships all four true.

---

## §7. Wire format additions

Beyond the new ops in [§4](#4-the-new-wire-op-vocabulary), three
wire-level additions:

### §7.1 The `correlation_id` echo

Optional per-request. Every response echoes it when present. No
schema bump — the field is a new top-level key, backward-compat.

### §7.2 The `fidelity` field on read-only responses

Every new read-only op response carries a top-level `fidelity`
field (`"live"` | `"cached"` | `"inferred"` | `"unknown"`).
Existing ops (`list`, `inspect`, `capabilities`, `list_leases`,
`drain_events`) gain it too — backward-compat additive.

### §7.3 The `predicate` grammar version

`subscribe`'s `predicate` field carries an implicit version: the
broker's `capabilities()` response gains a `predicate_grammar_version`
field (default `"1.0.0"`). Consumers query this before sending
predicates with features the broker may not understand.

---

## §8. Capability model

Three new rights. Cap-set additions go in `mgf-hrb-cap`
(`hrb-code/src/cap.zig:RightSet`).

| Right | Gates ops | Threat model |
|---|---|---|
| `audit_read` | `audit_tail` · `state_snapshot` | Read-only; reveals operation history (caller pids, urns, timing). Low-risk for trusted operators; significant for a multi-tenant deploy where one tenant could infer another's activity. Grant to dashboards + `doctor` consumers. |
| `load_module` | `module_load` | Authorises loading a kernel module by name. Threat: a malicious or compromised consumer could load a kernel module that grants further privilege escalation. Grant ONLY to operator-controlled tools. |
| `unload_module` | `module_unload` | Authorises unloading a kernel module. Threat: can crash the kernel (unloading a critical driver). Grant ONLY to operator-controlled tools. |

The two module rights are intentionally separable; a recovery
script may need `unload_module` (to free a stuck driver) without
`load_module` (which would let it replace it with a different
one).

**Cap-id binding.** All three new rights are bound to the
SO_PEERCRED peer at handshake time (Tier-0 #2 layer-2 — already
in place at commit `47e2cf3`). A client cannot present a cap_id
issued to a different peer.

---

## §9. Phasing + dependency graph

Six phases. Each phase ships independently. Phases name their
predecessors so a maintainer can ship out of order when
appropriate.

**This block is THE tracker** — keep it current as work lands.
Each sub-feature carries a status emoji + the commit SHA on
shipping. A `🟢 shipped` row should NEVER be reverted; a `🟡
in-progress` row points at the active work; `⬜ pending` is the
queue.

| # | Sub-feature | §  | Status | Commit |
|:-:|---|:-:|:-:|---|
| **1** | **Cheap additives** — independent; the "shippable in 1 PR each" set | | | |
| 1A | `inspect.driver_name` + `inspect.driver_module` | §5.2.1 | 🟢 shipped | `5f9c4c6` (2026-05-26) |
| 1B/1 | `dump_all(scope)` op | §5.5.3 | 🟢 shipped | `f0671b9` (2026-05-26) |
| 1B/2 | `inspect_full(urn, depth)` op | §5.5.1 | 🟢 shipped | `b0640be` (2026-05-26) |
| 1B/3 | `dump_chain(urn)` op | §5.5.2 | 🟢 shipped | `9c9b347` (2026-05-26) |
| 1C | `driver_bound` + `driver_unbound` event kinds | §5.4.1 | 🟢 shipped | `00a9120` (2026-05-26) |
| 1D/1 | `correlation_id` echo (every op) | §7.1 / §5.6.3 | 🟢 shipped | `0a589a9` (2026-05-26) |
| 1D/2 | Top-level `fidelity` field on read-only responses | §7.2 | 🟢 shipped | `633116a` (2026-05-26) |
| **2** | **Predicate filter** — depends on Phase 1's event kinds | | | |
| 2A | `subscribe` + `unsubscribe` ops + predicate grammar | §5.4.2 / §7.3 | 🟢 shipped | `418a248` (2026-05-26) |
| 2B | `predicate_grammar_version` field on `capabilities()` | §7.3 | 🟢 shipped | `db06b09` (2026-05-26) |
| **3** | **Driver-info registry** — depends on Phase 1A | | | |
| 3A | `driver_info` op (live half; registry fields null) | §5.2.2 | 🟢 shipped | `2348f00` (2026-05-26) |
| 3B | ioctl-interface registry + CI currency gate | §5.2.3 | 🟢 shipped | `187a6a8` (2026-05-26) |
| **4** | **Control serialisation** — independent | | | |
| 4A | Per-URN-per-verb serialiser | §5.1.1 | 🟢 shipped | `83436fe` (2026-05-26) |
| 4B | Optimistic-concurrency response shape | §5.1.2 | 🟢 shipped | `83436fe` (2026-05-26) |
| **5** | **Broker self-awareness** — independent | | | |
| 5A | `AuditSink` + in-memory ring buffer | §5.6.1 | 🟢 shipped | `d906bce` (2026-05-26) |
| 5B | `audit_tail` op | §5.6.4 | 🟢 shipped | `d906bce` (2026-05-26) |
| 5C | `state_snapshot` op | §5.6.2 | 🟢 shipped | `d906bce` (2026-05-26) |
| **6** | **Module helper sibling** — depends on a NEW sibling repo | | | |
| 6A | `mgf-hrb-modules-priv` repo + binary | §5.3.2 | ⬜ pending (own design conv) | — |
| 6B | `module_deps` op (broker-side, no privilege) | §5.3.1 | ⬜ pending | — |
| 6C | `module_load` + `module_unload` ops | §5.3.3 | ⬜ pending | — |
| 6D | Helper-availability doctor check | §5.3.4 | ⬜ pending | — |
| 6E | `device_ready` event kind + per-class adapters | §5.4.3 | ⬜ pending | — |

**Aggregate progress.** Phase 1: 7 of 7 sub-features shipped
(all Phase-1 ops surfaces complete: `inspect.driver`/`module` +
`dump_all` + `inspect_full` + `dump_chain` + `driver_bound`/`_unbound`
+ `correlation_id` echo + top-level `fidelity`). Phase 2: 2 of 2
shipped (`subscribe`/`unsubscribe` + predicate grammar +
`predicate_grammar_version`). Phase 3: 2 of 2 shipped (`driver_info`
op + ioctl-interface registry with CI currency gate). Phase 4:
2 of 2 shipped (per-(urn, verb) serialiser + disclosure response
shape). Phase 5: 3 of 3 shipped (AuditSink + audit_tail +
state_snapshot). Phase 6: 0 of 5 sub-features shipped —
explicitly out of scope this quarter (needs the
`mgf-hrb-modules-priv` sibling repo + its own design conversation).

**Pull this into git log.** `git log --oneline --grep='Part 13'`
returns every commit citing this design paper. Every commit
message that ships a sub-feature MUST carry the sub-feature ID
(e.g., `sub-phase 1A` / `sub-phase 1B/1`) so the table above
and `git log` stay in sync.

**Recommended sequencing.** Phase 1 + Phase 4 + Phase 5 in
parallel (they don't touch each other). Phase 2 + Phase 3 land
together (the GUI use-case wants both). Phase 6 is its own
design conversation + a new repo; not on the next quarter's
roadmap unless a consumer asks for it.

**Test cost per phase.** Each phase ships with conformance tests
covering happy path + every documented error variant + a
multi-client interaction test (where the surface admits one).
The `mgf-test-supervisor` tiers map naturally: tier-1 for the
op-shape contract tests, tier-2 for the use-case scenarios,
tier-3 for the stress tests on the new surfaces.

---

## §10. SOLID report

Master Part 2.10 commits to a per-part SOLID report. This is
ours.

### §10.1 Where each principle earned its place

- **SRP** — Module-management split into its own sibling
  (`mgf-hrb-modules-priv`) because its reason-to-change (kernel
  module ABI + `CAP_SYS_MODULE` policy) is orthogonal to the
  broker's. The control-verb serialiser stays inside the per-worker
  code because its reason-to-change is the verb set itself.
- **OCP** — The new wire ops are additive; the new event kinds
  are additive; the predicate grammar is opt-in. Existing
  consumers using the today's 9 ops + 4 event kinds keep working.
  The `BackendCapabilities` struct gains fields; backends that
  don't implement the new vtable methods declare so honestly.
- **LSP** — Every new vtable method carries a behavioural
  contract (`inspect_full` walks the FULL subtree; partial
  results are forbidden; an Err is the alternative).
  `driver_info` on a driver not in the registry returns
  `fidelity = "unknown"` — substitutable with the future
  registry-backed implementation that returns `inferred`.
- **ISP** — Three new rights (`audit_read`, `load_module`,
  `unload_module`) are independently grantable. Consumers using
  `observe` only never need to know `audit_read` exists.
- **DIP** — `AuditSink` interface lets the audit-log storage
  be swapped; module-helper IPC is a typed channel, not generic
  RPC; backends depend on the abstract kabi vtable, never on
  each other.

### §10.2 Where principles were consciously declined

- **SRP declined** for the control-verb serialiser
  ([§5.1.3](#513-srp-decline-recorded)). Living inside the
  per-worker code keeps the change locality co-located; a separate
  module would buy abstraction without isolation benefit.
- **DIP declined** for the audit log's transport
  ([§3.2's "one explicit decline"](#32-solid-applied-per-part-12)).
  The federation logging sink (`mgf.common.observability`) is the
  obvious abstraction but the per-op cost in the hot path
  violates the priority order. Recorded as a deliberate decline.
- **OCP declined** for the predicate grammar's operator set.
  Adding `regex_match` or `arithmetic_compare` to the grammar is
  refused at v1; if a consumer needs them, file a paper. Aggressive
  closed-ness here is a security posture — a richer query language
  is a richer attack surface.

### §10.3 Where the priority order overrode SOLID

- **Correctness > completeness** for `module_unload`'s
  force-unload refusal ([§5.3.3](#533-the-privileged-helpers-typed-verb-surface)).
  An "elegant" completeness would expose force-unload as a flag;
  the priority order says no.
- **Operability > performance** for the audit log
  ([§5.6.1](#561-the-audit-log)). Every op pays a ~200 ns ring-push
  cost. Acceptable; the operability dividend is enormous and the
  cost is comfortably below the ~10 µs per-op envelope.

---

## §11. Open questions

These are deferred to maintainer triage. Each names the option
space rather than picking — the design is structurally OK with
either resolution but the picks have downstream consequences.

1. **`dump_all` response shape.** Chunked response (streaming
   JSON; multi-line) vs single-blob response with a much larger
   buffer (256 KB+)? Chunked is closer to the existing wire
   shape; single-blob is simpler for consumers. Recommendation:
   single-blob with a 256 KB buffer for v1, chunked-streaming as
   a follow-up if real consumers hit the cap.
2. **Audit log retention vs the `holders` list.** When `module_unload`
   refuses because the module is in use, should the response list
   the holder URNs (revealing the broker's lease state) or only
   the holder count? Recommendation: full URNs — the caller is
   already cap-gated by `unload_module` (operator-controlled), so
   the disclosure is fine.
3. **The privileged helper's wire format.** Same JSON-line shape
   as the broker's main wire OR a tighter binary format (CBOR /
   capnproto)? Recommendation: same JSON-line shape, for
   debuggability + symmetry with the broker. Cost is marginal
   (the helper's traffic is low-volume).
4. **`device_ready` for drivers we don't have an adapter for.**
   Silent-no-emit (today's design) vs a generic "the driver bound
   N ms ago" timer-based heuristic? Recommendation: silent. A
   timer-based heuristic teaches consumers a wrong contract.
5. **`audit_read` rights distribution.** Should the default
   capability for new connections include `audit_read`? Recommendation:
   no — explicit grant required. Reveals operation history of
   other clients, which is a tenancy-boundary concern even on
   single-operator deploys (a script running with a different
   cap may not be intended to see another script's calls).
6. **Module-helper subprocess lifecycle.** Spawned at broker
   startup (always-on) vs spawned-on-demand at first
   `module_load` request? Recommendation: always-on. The startup
   cost is acceptable (~50 ms); the first-call latency would
   surprise an operator otherwise.

---

## §12. Verification + acceptance

How a future implementation verifies the design landed correctly:

| Phase | Verification |
|---|---|
| 1 | `inspect` returns `driver` + `module` attrs on a USB device with a bound driver (e.g. `usbhid`). `dump_chain("hw:/usb/001/004.if0")` returns 5 entries (interface, device, hub, root_hub, controller). `dump_all("hw:/test/**")` returns 2 items on the test backend. `driver_bound` events fire in `monitor()` when a USB device is plugged into a host with `usbhid` available. |
| 2 | `subscribe` with a predicate filtering on `vid == "0x05ac"` (Apple) delivers ONLY Apple-vendor events to that subscription; other subscribers' streams unaffected. Subscription overflow emits a `gap` of source `subscription_overflow`. Predicate grammar v1.0 documented in `predicate_grammar_version`. |
| 3 | `driver_info("usbhid")` returns the canonical USBHID ioctl table + `module_loaded: true`. CI gate (`test_ioctl_registry_currency.py`) reads `<linux/hidraw.h>` and asserts every number matches. Unknown drivers return `fidelity: "unknown"`. |
| 4 | Two clients calling `control(urn, SET_AUTHORIZED, true)` and `control(urn, SET_AUTHORIZED, false)` simultaneously: both succeed; one returns `superseded_by != null`; the on-disk value matches whichever returned `superseded_by: null`. |
| 5 | `audit_tail(100)` shows the last 100 ops with monotonic seq + ts. `state_snapshot()` returns active connection + lease + subscription counters. Correlation IDs echo through both successful and failed responses. |
| 6 | `module_deps("usbhid")` returns the dep chain (`["hid", "usbcore"]`). `module_load("nonexistent_module")` returns `Err.ModuleNotFound` with a helpful detail. `module_unload` on an in-use module returns `Err.Conflict` with the holders list. Doctor row `MODULE_HELPER_AVAILABLE` flips OK ↔ FAIL when the helper subprocess is killed/restarted. |

Each phase is a separate "verify-against-the-spec" pass; the
combined test count after all six phases ships is ~30 new tests
on top of today's 182.

---

## §13. Cross-references

- [`self-aware-introspection-current-state.md`](self-aware-introspection-current-state.md)
  — the gap inventory this paper closes. Every "🟡 partial" /
  "🔴 missing" row in that doc is addressed by a §5 sub-section
  here.
- [`mgf-hrb-complete-design.md`](mgf-hrb-complete-design.md)
  — master. Part 1 (priorities + SOLID + dependency law),
  Part 2 (model + event shape), Part 3 (lease engine), Part 4
  (capabilities), Part 6 (kabi seam), Part 12 (survivability).
- [`mgf-hrb-usb-worker.md`](mgf-hrb-usb-worker.md) §U3b —
  the privileged-helper pattern the module sibling mirrors.
- [`mgf-hrb-privileged-core-hardening.md`](mgf-hrb-privileged-core-hardening.md)
  — Part 11; relevant for the audit-log + state-snapshot threat
  model.
- [`mgf-hrb-survivability-review.md`](mgf-hrb-survivability-review.md)
  — Part 12; the audit log feeds the "why did this fail?"
  diagnostic surface.
- Federation: when the privileged-module sibling lands, file a
  TS-26 (freshness check) + WF-* CI rules per
  `mgf-standard/docs/standards/`.
