# mgf-hrb — Family B Deepening (`mgf-hrb-cpu` and the apportionable model)

Resolving open question 9.7 #4: **does `mgf-hrb-cpu` start report-only, or support reservation from day one?** This document also stress-tests the two-family model (Part 2.1) against its first real second case — which is the real value of doing this now: Family A (claimable devices) shaped the whole design; Family B is where we find out if the abstraction *actually* generalizes or just looked like it did.

---

## 1. The honest starting point — Family B was designed by analogy

Parts 2–8 are saturated with Family A thinking. Family B (apportionable: CPU, memory, NUMA, thermal) was *defined* (Part 2.1) but never *exercised*. Defining a thing by analogy to its sibling is exactly where hidden wrongness lives — so before deciding report-vs-reserve, we pressure-test whether the Family A machinery (leases, claim lattice, revocation, safe-reset, the lease engine) even *applies* to Family B, or whether we assumed it does.

The test: walk each Family A mechanism and ask "what is its Family B analog, and does it hold?"

| Family A mechanism | Naive Family B analog | Does it actually hold? |
|---|---|---|
| `claim` (take the thing) | `reserve` (take a fraction) | **Partly** — but "the thing" is indivisible; "a fraction" is divisible and many can hold fractions at once. The exclusivity check becomes a *sum* check. |
| Lease bound to liveness | Reservation bound to liveness | **Yes, cleanly** — a dead holder's fraction returns to the pool, exactly as a dead owner's device frees. This generalizes well. |
| Claim-mode lattice (Observe/Shared/Exclusive) | ??? | **No** — there is no "exclusive" for a continuous quantity; you don't exclusively own "CPU." The lattice doesn't map. |
| Safe-reset on loss | Return fraction to pool on loss | **Yes** — but it's not a "reset," it's a *give-back*. No hardware state to scrub; just accounting. Simpler. |
| Revocation (cooperative preemption) | Reclaim a fraction under pressure | **Yes, and richer** — you can reclaim *part* of a holder's fraction (throttle from 4 cores to 2), which has no Family A analog (you can't half-revoke a device). |
| Topology tree node | Pool node in the same tree | **Yes** — a NUMA node or CPU set is a tree node like any other. Namespace generalizes cleanly. |

**The verdict from the stress test:** the model *mostly* generalizes, with two precise divergences that we must design for rather than paper over:
1. **The exclusivity invariant becomes a capacity invariant.** "At most one exclusive owner" → "sum of reservations ≤ capacity." This was already anticipated (Part 2.1) and holds.
2. **The claim-mode lattice does not apply; a different axis does.** Family B has no Observe/Shared/Exclusive. Instead it has a *reservation-strength* axis (below) — guaranteed vs. best-effort vs. burstable. This was **not** anticipated and is the real discovery of this exercise.

> The fact that the stress test *found* something (the lattice doesn't map; a strength axis replaces it) is the justification for doing it. Had we shipped `mgf-hrb-cpu` by blind analogy, it would have inherited a claim-mode lattice that makes no sense for continuous resources — a latent wrongness. This is the "exercise the abstraction against a second case" payoff, concretely.

---

## 2. The reservation-strength axis — Family B's replacement for the claim lattice

Where Family A asks "how exclusively do you hold it?", Family B asks "how *strongly* is your fraction guaranteed?" — because a continuous resource can be promised at different levels of firmness:

| Strength | Meaning | Reclaimable under pressure? | Analogy |
|---|---|---|---|
| `Guaranteed` | this fraction is yours, always available | no (or only by explicit revocation) | a hard CPU reservation / pinned hugepages |
| `BestEffort` | you get it if available, yielded under pressure | yes, first to be reclaimed | a nice-valued workload |
| `Burstable` | guaranteed floor + opportunistic ceiling | the ceiling portion is reclaimable, the floor is not | floor+burst CPU shares |

This axis is the genuine Family-B-shaped concept. It interacts with the capacity invariant precisely:
- **Sum of `Guaranteed` floors ≤ capacity** is the hard invariant (over-committing guarantees is the one thing the engine must refuse).
- `BestEffort` and `Burstable` ceilings may *over-subscribe* capacity (that's their point) — the engine apportions the contended remainder by policy when demand exceeds supply.

> Note this gives Family B a richer reclamation story than Family A: revocation can be *partial and graduated* (reclaim the burst ceiling first, then best-effort, then — only by explicit preemption — guaranteed floors). Family A revocation is all-or-nothing (you have the device or you don't). The lease engine's revocation machinery (Part 3.5) generalizes to "reclaim down to the guaranteed floor," which is strictly more expressive — a pleasant discovery that the mechanism is *more* general than Family A alone revealed.

---

## 3. The decision: report-only first, reservation-capable by design

**Decision: `mgf-hrb-cpu` ships report-only in its first build, but the model and seam are designed reservation-complete from day one.** This is not a fence-straddle; it's a precise split between *what's built* and *what's contracted*, and it follows directly from the priority order and the OCP doctrine.

### 3.1 Why report-only *first* (what's built)

- **Reporting is the part with immediate, broad value and near-zero risk.** A topology/load reporter for CPU/NUMA/memory (how many cores, NUMA distances, current utilization, hugepage availability) serves the same observer-majority that drives Family A (the bus-layout reporter, the GUI mapper). It's `Observe` semantics — read-only, contends with nothing, can't corrupt anything. Shipping it first delivers value while the reservation engine is still being hardened.
- **Reservation is the part with real risk and real dependencies.** Enforcing a CPU/memory reservation means actually *controlling* the resource (cgroups v2, cpusets, hugepage allocation, memory pinning) — which is privileged, kernel-mechanism-heavy, and where the capacity invariant must be enforced without ever over-committing guarantees. That's lease-engine-grade reliability work (priority #1), and rushing it risks the one thing we won't compromise: never lying, never corrupting.
- **The priority order says so.** Reliability (#1) over feature completeness. Report-only is reliably correct on day one; reservation is correct *when the engine that enforces the capacity invariant is hardened*, not before.

### 3.2 Why reservation-complete *by design* (what's contracted)

- **The model carries the full reservation vocabulary from day one** — `State.reserved` (Part 2.7), the strength axis (§2), the capacity invariant. Report-only simply means the *reserve verb is not yet wired to enforcement*; the types, the namespace, the fidelity tags all already accommodate it.
- **The seam (`mgf-hrb-kabi`) declares the reservation operations from day one**, even though `mgf-hrb-linux` initially implements only the read/report side and returns `unsupported` for `reserve`. (This is the same nullable/`unsupported`-default pattern as `enforce_cap` in Part 6.2 — the contract is complete; an implementation may not yet satisfy all of it, and says so honestly rather than lying.)
- **OCP is satisfied:** turning on reservation later is *additive* — implement the `reserve`/`reclaim` operations in the backend, wire the capacity-invariant check into the core's apportionment logic. No consumer above changes; the `Reserver` role-interface (Part 8.2) was already defined. The reporter built in phase one keeps working untouched.

### 3.3 What this is *not*

It is explicitly **not** "build report-only and figure out reservation later." The reservation design is *done now* (this document); only its *enforcement* is deferred. The difference matters: deferring the *design* would risk the model needing to change when reservation lands (a breaking change rippling upward); deferring only the *enforcement* means the model is stable and reservation is a pure addition. We pay the design cost now to make the later build additive — the same discipline as "design all seams in Phase 0" from the very first VM-manager conversation, applied here.

---

## 4. The Family B verbs, settled

Refining the Part 8.2 `Reserver` role-interface against everything above:

```python
class Reserver(Observer, Protocol):                    # Observer base: report-only is just this
    def reserve(self, urn: str, amount: Quantity,
                strength: Strength = Strength.GUARANTEED,
                policy: ReservePolicy = ...) -> Reservation: ...
    def adjust(self, r: Reservation, new_amount: Quantity) -> Reservation: ...   # grow/shrink
    def release_reservation(self, r: Reservation) -> None: ...
```

- **Report-only clients use only the `Observer` base** — `list`/`inspect`/`subscribe` on `hw:/cpu/**`, `hw:/mem/**`. The first build ships exactly this; `reserve`/`adjust` exist in the protocol but the backend returns `unsupported` until enforcement lands.
- **`adjust` is the partial-reclamation counterpart** — grow or shrink a live reservation, the Family-B analog of nothing-in-Family-A (you can't resize a device claim). It's how a holder cooperatively yields part of its fraction under revocation (§2).
- **`strength` defaults to `Guaranteed`** — the safe default (a reservation you can rely on); best-effort/burstable are opt-in for workloads that tolerate reclamation.

---

## 5. Capacity invariant enforcement — the reliability heart of Family B

When reservation enforcement does land, this is its lease-engine-grade core, stated now so the design is complete:

- **The hard invariant, never violated:** `Σ guaranteed_floors(pool) ≤ capacity(pool)`. The engine refuses any `reserve`/`adjust` that would over-commit guaranteed floors — the Family B equivalent of refusing a second `Exclusive` claim. This refusal is non-negotiable (priority #1).
- **The soft apportionment, by policy:** best-effort and burstable demand above the guaranteed floors is contended; when demand > available, `mgf-hrb-policy` (Part 8.4, Python, meaning) decides the apportionment (fair-share, priority-weighted, etc.). The core *enforces* the policy's decision; it does not *choose* it — the same mechanism/meaning split as Family A revocation.
- **Liveness and reclamation:** a holder's reservation is liveness-bound (Part 3.2 generalizes); on holder death the fraction returns to the pool (the "safe-reset" analog, §1). Reclamation under pressure is graduated (burst → best-effort → guaranteed-by-preemption-only), driven by the revocation machinery (Part 3.5) extended to partial reclaim.

> The capacity-invariant check is to Family B what the compatibility check (Part 3.3) is to Family A: the O(1)-ish gate that the whole correctness of the family rests on. Designing it now — even unimplemented — means the reservation build is "wire up a specified gate," not "invent the gate under deadline."

---

## 6. What this exercise changed in the broader design

Doing #4 surfaced concrete corrections/additions to fold back into the main document — recorded honestly rather than silently:

1. **The claim-mode lattice (Part 3.3) is Family-A-only.** The main doc implies it's universal; it is not — Family B uses the reservation-strength axis (§2) instead. *Correction to Part 2.1 / 3.3: name the lattice as Family-A-specific and the strength axis as Family-B-specific.*
2. **Revocation (Part 3.5) is more general than Family A revealed** — it supports *partial, graduated* reclamation, which Family B needs and Family A never exercised. *Enhancement to Part 3.5: the revocation machinery reclaims "down to a floor," of which all-or-nothing (Family A) is the degenerate case.*
3. **The `Reserver` interface gains `adjust`** (§4) — resize a live reservation, absent from the Part 8.2 sketch.
4. **The seam grows reservation operations** declared-but-`unsupported`-initially (§3.2) — the same honest-incompleteness pattern as `enforce_cap`.

None of these is a breaking change to Family A; all are either clarifications (1) or additive generalizations (2–4). **The two-family model survived its first real second case** — bent in two precise, anticipated-in-spirit places, broke in none. That's the outcome we wanted from exercising it: confirmation the abstraction generalizes, plus the two corrections that make it honest.

---

## 7. Decision summary

- **Build first:** `mgf-hrb-cpu` report-only — topology, load, NUMA, memory/hugepage availability — pure `Observer` semantics, broad value, near-zero risk.
- **Design now (this document):** the full reservation model — strength axis, capacity invariant, partial reclamation, the `Reserver` verbs — so that enabling reservation later is additive (OCP), not a model change.
- **Enable reservation when:** the capacity-invariant enforcement engine is hardened to lease-engine reliability standards, and the privileged cgroup/cpuset/hugepage backend operations are contract-conformant (Part 6.4 suite, extended for Family B).
- **Rationale chain:** reliability #1 (don't ship unhardened enforcement) → fidelity #3 (the model is complete and honest now, returning `unsupported` rather than lying) → flexibility #4 (OCP makes the later enable additive) → simplicity #5 (report-only first is the smallest valuable thing).

> This is the priority order producing a clean, non-arbitrary phasing: build the reliably-correct read side now, design the must-be-bulletproof write side completely now, enable the write side only when it meets the reliability bar. The same shape as every other "report/observe is cheap and safe; control/claim is where the danger and the value concentrate" decision in the whole design.
