# PUBLIC_API — `mgf.hrb`

> **Master-HEAD banner (REL-03):** reflects CURRENT main/release; verify your pin via `pip show mgf-hrb` + `mgf.hrb.__version__` (mirrors pyproject) + PUBLIC_API.json. Generated from `__all__`, CI-pinned.


> **What this is.** The DOC-08 contract surface. Everything listed here
> is `__all__`-exported from the public module and follows AP-10
> (the JSON companion at [`PUBLIC_API.json`](PUBLIC_API.json) is
> generated from these `__all__`s in CI and pinned).
> **What is NOT listed here is NOT public.** Module names starting with
> a single underscore (`_client.py`, the `cli/_*.py` subcommands) are
> internal-only (AP-14).
>
> **Stability tier (AP-11).** Pre-`0.1.0`. **Everything below is
> `experimental`** until the SDK reaches its first published release.
> Names may change; behavioural contracts may tighten. Pin a specific
> version of `mgf-hrb` in `pyproject.toml` if you depend on this surface.

---

## `mgf.hrb` — the SDK package

### Entry point

| Name | Kind | Notes |
|---|---|---|
| `Client` | class | The synchronous broker client (`_client.py`). Construct with `Client.connect(path)`; context-managed close (DP-13). |
| `Lease` | class | The handle returned by `Client.claim(...)`; doubles as a context manager. Carries `urn`, `lease_id`, and `release()` / `raw()` methods. Typed-exported so consumers can annotate `def hold_one(...) -> Lease` without reaching into `mgf.hrb._client` (FEEDBACK SDK-04). |
| `ShmChannel` | class | The client side of the Part 5.3 shared-memory data plane, returned by `Client.open_shm_channel()`. Wraps the SCM_RIGHTS-passed region fd + its `mmap`; validates the in-region ring header; `push(item: int) -> bool` is the SPSC producer half (the broker drains). Context-managed `close()`. |
| `__version__` | str | Package version. |

#### `Client` — the read + write verbs

The synchronous client exposes one method per wire op. Every method
raises a typed `MgfHrbError` subclass on broker-side failure (EH-02);
`Client` itself works as a context manager (`with Client.connect(...)
as c:`).

| Method | Purpose | Notes |
|---|---|---|
| `connect(path, *, client_id=1, hello_deadline_ms=2000)` | classmethod | Open a Unix-socket connection to the broker at `path` and complete the hello handshake. The broker writes a hello line on accept; this method blocks reading it and then returns. `hello_deadline_ms` (SDK-06) bounds that recv — a peer that accepts but never writes the hello raises `MgfHrbError` instead of hanging. See **Handshake** below for the wire format. |
| `close()` | method | Close the socket. The `with`-form is preferred (DP-13). |
| **Read verbs** | | |
| `list(urn_glob) -> list[str]` | enumerate | URNs matching the trailing-`**` glob (e.g. `"hw:/usb/**"`). Filtered server-side. |
| `inspect(urn) -> ResourceDetail` | per-URN peek (typed) | Returns a frozen `ResourceDetail` dataclass with `urn`, `family`, `kind`, `fidelity`, and a backend-specific `attrs` tuple (USB: `idVendor`/`idProduct`/`serial`/`bInterfaceClass`; PCI: `vendor`/`device`/`bdf`). Use `detail.attr("idVendor")` for single-key lookup. Read-only — does NOT claim. FEEDBACK SDK-02 typed half. |
| `inspect_raw(urn) -> dict` | escape hatch | The un-projected wire dict — preserved for the CLI info renderer + forward-compat readers. |
| `capabilities() -> list[BackendCapability]` | broker introspection | Per-worker record carrying `scope_prefix`, `backend_name`, `resource_count`, and the forward-compatible `backend_version` / `mode` / `mode_reason` / `ops` fields (FEEDBACK SDK-05). |
| `capabilities_raw() -> list[dict]` | escape hatch | The un-projected wire dicts — preserved for the CLI doctor's per-worker ops grid render which reads fields the typed shape doesn't promise. |
| `list_leases(scope="hw:/**", *, deadline_ms=0) -> list[LeaseRecord]` | who-holds-what view | Returns one `LeaseRecord` per live lease whose URN matches `scope` — INCLUDING leases held by other tenants (FEEDBACK SDK-03; multi-operator UX). Pure read. |
| `drain_events(scope="hw:/**") -> list[dict]` | non-blocking event drain | Returns whatever's queued in any worker's buffered event source (USB netlink today). One-shot; pair with `monitor()` for streaming. |
| `monitor(scope="hw:/**", *, poll_interval_s=0.5) -> Iterator[dict]` | streaming events (sync) | Layer 1 of the three-layer event API (FEEDBACK SDK-01). Polls `drain_events` on a timer; yields each event as a dict. `kind` is `"added"` / `"removed"` / `"state_changed"` / `"gap"`. |
| `amonitor(scope="hw:/**", *, poll_interval_s=0.5) -> AsyncIterator[dict]` | streaming events (async) | Layer 2 — for asyncio / qasync consumers. Wraps `drain_events` with `asyncio.to_thread` + `asyncio.sleep`; same event shape as `monitor()`. (FEEDBACK SDK-01.) |
| `monitor_fd(scope="hw:/**", *, poll_interval_s=0.5) -> int` | streaming events (fd) | Layer 3 — returns the read end of a self-pipe. A daemon thread polls `drain_events` and writes each event as a newline-terminated JSON line. For Qt's `QSocketNotifier`, Gtk's `Glib.IOAdd_watch`, raw `select.poll()`. `Client.close()` tears down the thread + pipes. Only one active per Client. (FEEDBACK SDK-01.) |
| **Write verbs** | | |
| `claim(urn, mode=ClaimMode.EXCLUSIVE, *, deadline_ms=0, detach=False) -> Lease` | grant a lease | Returns a context-managed `Lease`. `detach=True` opts into the atomic disconnect-then-claim path (USBDEVFS_DISCONNECT_CLAIM); cap-gated by `detach_driver`. |
| `release(urn, lease, *, deadline_ms=0)` | release a held lease | Total + idempotent (Part 6.3) — releasing an unknown lease is a no-op. |
| `raw(urn, lease, *, deadline_ms=0) -> dict` | direct hardware channel | When the backend has an fd-shaped raw channel (USBDEVFS), the returned dict carries `fd` as a usable file descriptor (SCM_RIGHTS-passed). The fd outlives the broker's copy (Part 7.4 — survivability dividend, G4). |
| `control(urn, verb, value)` | typed device-state mutation | Operator-visible verbs (`set_authorized`, `set_autosuspend`, `set_port_power`, `bind_driver`, `unbind_driver` — USB-worker §5.10). Backends without writable device state raise `UnsupportedError` (G2). |
| `open_shm_channel() -> ShmChannel` | open shm data plane | Allocates a private memfd-backed region, hosts an SPSC ring in it, and SCM_RIGHTS-passes the fd; returns a `ShmChannel` to push into (Part 5.3). Reclaimed broker-side on disconnect. `OverloadedError` at the S5 region ceiling. |
| `drain_shm_channel(channel) -> dict` | drain shm data plane | Asks the broker to drain the channel's ring (consumer side, asymmetric-trust pop); returns `{drained, checksum, last_seq, dropped}`. In push-mode (after `start_shm_drain`) returns the background drainer's accumulated stats instead. |
| `start_shm_drain(channel) -> dict` | push-mode shm data plane | Switches a channel to push-mode (Part 5.3 slice 4): the broker spawns a background thread that drains the ring continuously, so a streaming producer needs no per-batch drain RPC. `drain_shm_channel` then reports that thread's stats. Thread torn down on channel/connection close. Returns `{ok, draining, channel}`. |
| `reconcile_after_restart(*, scope="hw:/**", deadline_ms=0) -> dict` | restart recovery (Tier-1 #8) | Triggers the broker's restart reconcile: re-derives truth from hardware (present devices) + live leases, quarantining every present-but-unclaimed orphan (`unclaimed_after_restart`). Returns `{ok, quarantined, scanned, truncated}`. Steal-gated on `scope`; **trigger only after an actual restart** (operator/supervisor-driven — no self-restart-detection yet). Recover quarantined devices with `recover`. |
| `irq_scan(*, deadline_ms=0) -> dict` | interrupt-rate containment (Tier-2, D7) | Sweeps every granted device's interrupt counter and revoke+quarantines any flooder (a runaway-IRQ use-path DoS the broker contains by withdrawing the grant). Returns `{ok, scanned, contained}`. Steal-gated on `hw:/**`; operator/supervisor-triggered. Counts are read live from `/proc/interrupts` per PCI device (`/sys/.../msi_irqs`); a USB interface shares the host-controller IRQ so it isn't individually attributable. Recover a contained device with `recover`. |
| `doctor(*, socket_path=None) -> list[DoctorFinding]` | broker-precondition diagnostics | Runs the same check suite as `mgf-hrb doctor` (FEEDBACK TOOLS-02). Each finding carries a stable `code` (e.g. `SOCKET_REACHABLE`, `IOMMU_PRESENT`), four-way `severity`, and operator-facing prose. `socket_path` defaults to `Client.socket_path`. |
| `socket_path` (property) | str \| None | The path the Client connected to (set by `Client.connect`); used as the default for `doctor()`. `None` for Clients built directly via `__init__` (test fakes). |

### Handshake (Part 12 §3.3 — Tier-0 #2; SDK-06)

The broker writes exactly one line on `accept`, before any client byte:

```
{"ok": true, "hello": "mgf-hrb", "cap_id": <int>}\n
```

`cap_id` is a CSPRNG-drawn root capability id (~2^63 entropy — closes
the U3 forgery vector). `Client.connect` reads this line, binds the
id to the connection, and presents it on every subsequent cap-checked
op. A connection that doesn't speak the handshake cannot guess a
working `cap_id`.

**Consumer-side test fakes MUST volunteer this line on `accept`.**
The SDK's `Client.connect` blocks reading it before returning; a
fake that waits for a client request before responding will deadlock
the test. `hello_deadline_ms` (default `2000`) bounds the wait —
the SDK raises `MgfHrbError("broker hello not received within Xms")`
instead of hanging. Reference fake shape:

```python
def serve_one_client(conn: socket.socket, cap_id: int = 42) -> None:
    # Volunteer the hello immediately on accept.
    conn.sendall(json.dumps(
        {"ok": True, "hello": "mgf-hrb", "cap_id": cap_id}
    ).encode() + b"\n")
    # Then enter the normal request-response loop.
    ...
```

### Exception hierarchy (EH-01)

Root: `MgfHrbError`. Every wire-level `Err` code translates to a typed
subclass at the SDK's transport seam (EH-02).

| Name | Wire code | Meaning |
|---|---|---|
| `MgfHrbError` | (root) | Anything went wrong inside the broker. |
| `NotFoundError` | `NotFound` | The named resource doesn't exist. |
| `NotPermittedError` | `NotPermitted` | Capability or holder check refused. |
| `ConflictError` | `Conflict` | Claim-mode incompatibility. |
| `UnsupportedError` | `Unsupported` | Backend honestly cannot (G2). |
| `HardwareFaultError` | `HardwareFault` | Device-level failure. |
| `InvalidTokenError` | `InvalidToken` | Token/lease/cap not recognised. |
| `BadRequestError` | `BadRequest` | Trust gate refused the request. |
| `TimeoutError` | `Timeout` | Op exceeded its deadline (S2). |
| `OverloadedError` | `Overloaded` | Per-tenant / aggregate bound hit (S5). |
| `RevokedError` | `Revoked` | Lease revoked; further use will fault. |

### Diagnostics surface (FEEDBACK TOOLS-02)

| Name | Kind | Notes |
|---|---|---|
| `DoctorFinding` | frozen dataclass | `code` (stable id, e.g. `"SOCKET_REACHABLE"`), `severity`, `category`, `summary`, `detail`, `why`, `remediation`. Codes are stable across SDK MINORs; renderers may match on them. |
| `DoctorSeverity` | enum | `OK` / `WARN` / `FAIL` / `DEFERRED`. `DEFERRED` names a check the engine knows about but cannot yet run (e.g. udev-rule check before the rule template ships). |
| `DoctorCategory` | enum | `SUBSTRATE` / `HARDWARE` / `BROKER` — grouping for the operator-facing report. |
| `mgf.hrb.diagnostics` | submodule | The shared engine: `run_all(socket_path, *, client=None)` plus the individual `check_socket / check_pci_sysfs / check_usb_sysfs / check_vfio_device / check_iommu_groups / check_udev_rule / check_backend_capabilities` helpers. Same engine the `mgf-hrb doctor` CLI uses — no duplicate-source-of-truth. |

### Domain types

Frozen dataclasses across boundaries (DP-03). Mirror `hrb-code/src/model.zig`.

| Name | Kind | Notes |
|---|---|---|
| `ClaimMode` | enum | `OBSERVE` / `SHARED_RW` / `EXCLUSIVE` (Part 3.3 — Family A only; see "Claim-mode lattice" below). |
| `ControlVerb` | enum | `SET_AUTHORIZED` / `SET_AUTOSUSPEND` / `SET_PORT_POWER` / `BIND_DRIVER` / `UNBIND_DRIVER` — typed verbs for `Client.control()` (USB-worker §5.10). |
| `LeaseRecord` | frozen dataclass | One row in `Client.list_leases()`: `urn`, `lease_id`, `mode`, `holder`, `granted_at_ms` (FEEDBACK SDK-03). |
| `BackendCapability` | frozen dataclass | One row in `Client.capabilities()`: `scope_prefix`, `backend_name`, `resource_count`, and the forward-compatible `backend_version` / `mode` / `mode_reason` / `ops` (FEEDBACK SDK-05). Carries class constant `KNOWN_OPS: tuple[str, ...]` (the canonical kabi-seam op-name list) + `ops_dict()` method (flattens storage to `dict[str, bool]`). Recommended idiom for a stable display grid: `[cap.ops_dict().get(op, False) for op in cap.KNOWN_OPS]`. (A3 — presentation-layer plan.) |
| `AuditOutcomeSummary` | frozen dataclass | Typed return shape of `AuditTailResult.outcome_summary()`: `(ok, err, other, newest_error)` tally. Frozen / hashable so consumers can cache as dict keys or Qt model rows. (A2 — presentation-layer plan.) |
| `ResourceDetail` | frozen dataclass | Return of `Client.inspect()`: `urn`, `family`, `kind`, `fidelity`, `attrs` (sorted `(key, value)` tuple), plus forward-compat `parent` / `state`. Includes an `attr(key)` accessor for single-key lookup. (FEEDBACK SDK-02 typed half.) |
| `FieldFidelity` | enum | `live` / `cached` / `inferred` / `unknown` (G2). |
| `IdentityClass` | enum | URN stability ladder (D-010). |
| `Resource` | dataclass | Minimum descriptor the broker reports. |

#### Claim-mode lattice (Family A — FEEDBACK DOC-01)

`ClaimMode` is the Family-A compatibility lattice (Part 3.3 of the
design). The three modes name *what kind of access the caller wants*
and the lattice settles *what coexists with what* on a single resource:

| Mode | Wire value | Coexists with | Typical use |
|---|---|---|---|
| `ClaimMode.OBSERVE` | `"observe"` | Anything (observers never conflict) | Read-only watching: a monitor, a doctor, a diagnostic dashboard. |
| `ClaimMode.SHARED_RW` | `"shared_rw"` | Other `SHARED_RW` holders + `OBSERVE` | Cooperative shared access (rare on Family-A hardware; mostly Family-B). |
| `ClaimMode.EXCLUSIVE` | `"exclusive"` | Only `OBSERVE` | The canonical "I own this device" — VFIO passthrough, USB-interface claim, GPU compute reservation. |

The `Client.claim()` default is `EXCLUSIVE` — the safest assumption.
A common consumer error is guessing `SHARED` (wrong) when the intent
is `SHARED_RW` (right); these are `StrEnum` values, so importing
`ClaimMode.SHARED_RW` is the type-safe path.

Family B (apportionable — CPU shares, memory) replaces the lattice
with a **reservation-strength axis** (`Guaranteed` / `BestEffort` /
`Burstable`) that lives outside `ClaimMode`. The two families share
the URN namespace but not the mode vocabulary (Part 2.1 / Part 10).

### Typed configuration (CF-01)

| Name | Kind | Notes |
|---|---|---|
| `HrbSettings` | class | Subclass of `mgf.common.config.BaseAppSettings`. Carries `socket_path` (default `/tmp/mgf-hrb.sock`) and `default_deadline_ms` (default `5000`). Env-var prefix `MGF_HRB_`; precedence CLI > env > YAML > defaults (CF-02). Also importable as `mgf.hrb.config.HrbSettings`. |

### Identity-continuity comparator (FEEDBACK `MODEL-01`)

Python mirror of `hrb-code/src/identity.zig`; same algorithm + weights.

| Name | Kind | Notes |
|---|---|---|
| `Snapshot` | dataclass | Captured identity at a point in time. |
| `Confidence` | enum (Int) | `DIFFERENT` < `AMBIGUOUS` < `LIKELY_SAME` < `SAME`. |
| `ComparatorOpts` | dataclass | Per-call tunables; defaults match the iFig validation. |
| `Contribution` | dataclass | One scored signal. |
| `ScoreResult` | dataclass | The comparator's full reasoning. |
| `same_likely` | function | Lightweight: → `Confidence`. |
| `same_likely_explained` | function | Full reasoning: → `ScoreResult`. |
| `snapshot_from_sysfs` | function | I/O helper (`mgf.hrb.sysfs`): reads sysfs USB-device attrs and returns a `Snapshot` ready for the comparator. Closes the SDK-07 "every consumer rewrites 30 lines of sysfs parsing" gap. Stdlib-only; pure I/O isolated from `identity.py`'s pure-logic invariant. |

### Computed properties on existing dataclasses (A2 — presentation-layer plan)

Convenience accessors that delegate to ``mgf.hrb.fmt``. The
medium-agnostic formatting decisions (uptime as ``"Hh Mm Ss"``,
RSS as binary prefix, audit-record duration with cascading units)
live in one place; CLI / Qt / future TUI / web consumers read the
same properties and never drift.

| Property | On | Returns | Notes |
|---|---|---|---|
| `uptime_human` | `StateSnapshot` | str | E.g. ``"1h 2m 5s"`` — delegates to ``fmt.format_uptime(self.uptime_ms)``. |
| `memory_rss_human` | `StateSnapshot` | str | E.g. ``"42.3 MiB"`` — binary prefix (KiB/MiB/GiB) to match Linux memory conventions. |
| `duration_human` | `AuditRecord` | str | E.g. ``"4.5ms"``, ``"1.2s"``, ``"5m12s"`` — cascading units; one decimal in the sub-minute bands. |

| Method | On | Returns | Notes |
|---|---|---|---|
| `outcome_summary()` | `AuditTailResult` | `AuditOutcomeSummary` | Tallies records by outcome bucket and picks the newest err. Exploits the broker's newest-first ordering — no full-list sort needed. |
| `ops_dict()` | `BackendCapability` | `dict[str, bool]` | Flattens the storage tuple-of-pairs to the flat dict shape consumers actually want. Returns a fresh dict per call (mutation OK). |

### Presentation-layer formatting helpers (`mgf.hrb.fmt` — A1, PR-06 reference impl)

Medium-agnostic primitives every UI consumer needs. Pure
functions, primitive-in / str-out, no I/O, no state, no logging.
Stdlib-only — CI-fenced by `pyproject.toml`'s `PR-06` import-linter
contract. The reference implementation of the upcoming Magogi
Foundation rule PR-06 (presentation-layer separation; see
`mgf-standard/docs/standards/PRESENTATION_LAYER.md`).

| Name | Kind | Signature | Notes |
|---|---|---|---|
| `format_uptime` | function | `(uptime_ms: int) -> str` | ``"Hh Mm Ss"`` / ``"Mm Ss"`` / ``"Ss"``. Truncates to whole seconds. Defensive on negative / non-int (returns ``"0s"``). |
| `format_duration` | function | `(duration_us: int) -> str` | Cascading units ``"Nµs"`` / ``"X.Yms"`` / ``"X.Ys"`` / ``"MmSs"``. Picks largest unit ≤4 integer digits. |
| `format_rss` | function | `(num_bytes: int) -> str` | Binary prefix ``"N B"`` / ``"X.Y KiB"`` / ``"X.Y MiB"`` / ``"X.Y GiB"`` / ``"X.Y TiB"``. Deliberately binary not decimal (matches Linux memory reporting). |
| `format_severity_glyph` | function | `(severity: Severity \| str) -> str` | Canonical 5-character glyph ``"  OK "`` / ``" WARN"`` / ``" FAIL"`` / ``" ... "``. Unknown values render ``"  ?  "``. Accepts enum OR string. |

---

## `mgf.hrb.cli` — the embedded CLI

Reachable as the `mgf-hrb` console script (declared in `pyproject.toml`),
or via `python -m mgf.hrb.cli`. Public surface is intentionally minimal —
the CLI is *behaviour*, not an importable library.

| Name | Kind | Notes |
|---|---|---|
| `main` | function | Entry point used by the console script. |
| `build_parser` | function | Argparse parser — exposed for testing the CLI's shape without invoking subcommands. |

The subcommand implementations (`_scan`, `_info`, `_doctor`) are
internal-only; consumers invoke them through the CLI, not by import.

---

## Stability commitment

Until `mgf-hrb` ships `0.1.0`:

- **Every name above is `experimental`** — may move, rename, or be removed
  without a deprecation cycle.
- **The wire protocol** (the line-JSON broker dialect) is **also**
  pre-stability. The SDK + the Zig server are versioned together; pin
  both.
- **Lease + raw channel semantics** (Part 3.2 / 3.8 — leases-bound-to-
  liveness, raw-channel-only-while-owner) are settled in the design and
  will not regress. These are the design's load-bearing claims.

After `0.1.0`, AP-04 deprecation discipline applies: announce → still
works for ≥1 MINOR → remove. Deprecated names emit `DeprecationWarning`.
