# Federation update strategy

> **Audience:** the maintainer (human or AI agent) driving an update across
> more than one `mgf-*` project — a `mgf-common` bump, a standards-corpus
> change, an identity/packaging/pin sweep, or a "make the whole world current"
> pass.
> **What this is:** the **umbrella** procedure that picks the *scope*, fixes the
> *walk order*, sequences the two existing engines
> ([cohort-release-wave](cohort-release-wave.md) for version propagation,
> [federation-sync-prompt](federation-sync-prompt.md) for per-repo conformance),
> and defines the *close-out proof* that everything actually synced.
> **What this is NOT:** the mechanics of shipping one package (that's
> [`cohort-release-wave.md`](cohort-release-wave.md)) or bringing one repo into
> conformance (that's [`federation-sync-prompt.md`](federation-sync-prompt.md)).
> This recipe is the layer *above* both — when, in what order, and how to know
> you're done.

The guarantee in one line: **walk roots → leaves, conform-then-ship each repo
behind green gates, never cross a wave barrier until the lower wave is REST-200
live, and close on REL-13 GREEN (libraries) + a complete tracker (apps).**

---

## 1. First decide the scope — three concentric rings

"Update the federation" means one of three things. Pick deliberately; the ring
sets the roster and the stop-line.

| Ring | Roster | Run it when |
|---|---|---|
| **R1 — one lib + downstream** | one `mgf-*` lib + only the repos whose pins / `used_by` touch it | a fix or feature in a single library |
| **R2 — all `mgf-*` libraries** | the registry roster ([`MGF_LIBRARIES.json`](../../MGF_LIBRARIES.json)) | a `mgf-common` minor, a standards-corpus change, a federation-wide pin / identity / packaging sweep |
| **R3 — everything** | R2 **+** the consumer apps (`hrb-app`, `inthewords`, `vm-vmanager-app`, `vm-vmanager-agent`, `iosRecovery`, `TelePlasma`) | a breaking cornerstone change apps consume, or a "make the entire world current" pass |

**The roster authority is the registry for R2.** `MGF_LIBRARIES.json` rosters
`mgf-*` libraries only. The **apps in R3 are not in the roster** — discover them
from each library's [`DEPS.md`](deps-md-template.md) §2 and their own
[`MGF_ADOPTION.md`](mgf-adoption-md-template.md). This asymmetry matters: R2 has
a self-checking gate (REL-13, §4); R3's app tail does not, which is why the apps
always trail and must be tracked by hand (§4, the tracker file).

---

## 2. The invariant walk order — dependency waves

Always walk **roots → leaves** of the *runtime* dependency graph. The order is
**generated**, never hand-maintained — `python scripts/fed_waves.py` derives it
from the live registry, and the same waves are embedded in
[`MGF_LIBRARIES.md`](../../MGF_LIBRARIES.md) (§ *Release order (waves)*), in
`MGF_LIBRARIES.json` (`release_waves`), and resolvable as wheel data via
`mgf.standard.release_waves()`. A representative shape:

```
WAVE 0  roots — nothing mgf depends on them at runtime
  mgf-common (cornerstone) · mgf-standard (corpus/registry — see §6)
  mgf-common-zig · mgf-hrb-model · mgf-website-template

WAVE 1  depend only on a wave-0 root
  mgf-apiprobe · mgf-auth · mgf-django · mgf-hrb · mgf-http · mgf-livepush
  mgf-logship · mgf-sqlalchemy · mgf-tele-core · mgf-test-supervisor
  mgf-vm-core · mgf-hrb-kabi · mgf-hrb-rt

WAVE 2
  mgf-alembic · mgf-fastapi · mgf-tele-teltonika · mgf-common-identity
  mgf-hrb-cap · mgf-vm-firecracker · mgf-vm-forensics · mgf-vm-interchange
  mgf-vm-libvirt

WAVE 3
  mgf-vm-sdk

WAVE 4  (R3 only) the consumer apps — leaves of everything
  hrb-app · inthewords · vm-vmanager-app · vm-vmanager-agent
  iosRecovery · TelePlasma
```

**The single ordering law:** a repo is eligible to ship only when **every mgf
dep below it is already published + REST-200 live** (§4). Within a wave there
are no inter-deps, so a wave is fully **parallelizable**; between waves there is
a **hard barrier**. This is what makes the result reproducible regardless of who
or what runs it.

**Two edges are not build-order constraints** (the wave computer drops them):

- `mgf-test-supervisor` — a *dev/test* dependency. Everyone tests against it,
  yet it runtime-depends on `mgf-common`; treating that as a build edge would
  close a false cycle (TS-23 / CFM-04 carve-out).
- `mgf-standard` — the standards/registry corpus, consumed by `mgf-common`
  as the optional `[standards]` extra, not a runtime dep (ADR-026).

Both still *ship* in the waves; they just don't constrain the order. (If a
genuine runtime cycle ever appears among libraries — a real layering violation —
`fed_waves.py` fails loudly, naming the members, rather than emitting a wrong
order.)

---

## 3. The two engines — and the fact that they are different jobs

You already have both. The strategy is knowing which one an update needs;
conflating them is the usual source of "I thought it was synced."

| | Version-propagation engine | Conformance engine |
|---|---|---|
| Recipe | [`cohort-release-wave.md`](cohort-release-wave.md) | [`federation-sync-prompt.md`](federation-sync-prompt.md) (`/sync`) |
| Moves | a **pin ceiling or published version** | **standards / docs / contract** conformance |
| Shape | Phase A (parallel edits) → barrier → Phase B (**sequential** ship) | Comprehend (read-receipt, no edits) → Conform (per-MUST ledger) → Verify |
| Use when | a `mgf-common` minor breaks a sibling's pin upper bound, or you're walking versions forward | a rule / doc / contract changed, or a repo drifted and needs truing up |

**They compose per repo, in this fixed micro-order:**

```
   /sync  (conform)   →   gates green   →   cohort-wave Phase B  (ship)
```

Conform **before** you ship, so the version you publish is also the conformant
one. Never ship first and sync later — that publishes drift.

---

## 4. The reliability guarantee — three nested checkpoints

What makes "everything updated" *provable* rather than hoped:

1. **Per-repo gate** (local truth). `ruff check` · `ruff format --check` ·
   `mypy --strict` · the test suite · `lint-imports` · `gitleaks`, plus
   **`mgf-fed app-doctor --standards`** (the partial-sync gate — exits non-zero
   when the [`MGF_STANDARDS_CONFORMANCE.md`](mgf-standards-conformance-md-template.md)
   ledger omits any current MUST). A repo is not "done" until this is green.
   **No `✅` without it.**
2. **devpi index verify** (publish truth). A new version is not "live" until it
   is listed in `GET …/magogi/prod/+simple/<name>/` (anonymous; PKG-05). This is
   the **barrier gate** between waves (§2).
3. **REL-13 registry regen** (global truth, R2). After the last library ships,
   `python scripts/gen_mgf_libraries.py --check` must exit **GREEN + CURRENT**.
   This is the one artifact that cross-checks *every* library's published pin
   against the cornerstone simultaneously — a missed pin-walk RED-flags here by
   name. **This is the federation-wide "everything is synced" proof.**

For **R3, the apps have no equivalent of checkpoint 3** (they're off-roster). So
the close-out there is a **wave-tracker file** —
`/tmp/fed-update-<date>.md`, one line per repo:

```
mgf-http       — /sync ✓ · gates ✓ (103 passed) · shipped 0.4.8 · REST-200 ✓
vm-vmanager-app— /sync ✓ · gates ✓ · pin<0.47 ✓ · (app, no publish)
inthewords     — /sync ✓ · gates n/a (flat layout) · pin<0.47 ✓
...
```

The tracker is the single source of truth during the run; archive it to
`docs/inprogress/` if any repo needs follow-up. **Silent omission is the
enemy** — an untracked app reads as "covered" when it isn't.

---

## 5. Efficiency rules

- **Parallel within a wave, sequential at the publish boundary.** Phase-A edits
  and `/sync` conformance fan out across all repos in a wave (one sub-agent
  each, independent worktrees). Phase-B publish is serialized so one repo's
  failing pytest can't race another's wheel upload (REL-11).
- **Skip with a recorded reason, never silently.** A *library-API-neutral*
  cornerstone bump (only `cli/` + `__version__` changed) lets you pin-walk
  siblings **without** source migration — that is what makes such a wave cheap.
  The pre-Phase-A `grep` audit for deleted symbols (cohort recipe, REL-10)
  decides skip-vs-migrate; whatever you skip, **`log` it**.
- **Targeted re-locks.** `uv lock --upgrade-package mgf-*` moves only the
  federation while third-party deps stay put — faster locks, smaller diffs,
  fewer surprises.
- **Worktree off `origin/<default>`** per repo, so no repo's WIP is touched and
  the fan-out can't collide.

---

## 6. The one subtlety that bites: `mgf-standard` is both first *and* last

`mgf-standard` sits at **both ends** of an R2/R3 wave, for two different
reasons — treat them as two distinct runbook steps, not one:

- **Front (Wave 0):** if the **standards corpus / contract** changed, ship
  `mgf-standard` first so `mgf-common[standards]` resolves the new rules —
  otherwise every downstream `/sync` reads stale standards.
- **Back (after the last wave):** it owns the **registry**, which records
  everyone's *final* shipped versions — so **regenerate + republish it last**.
  **Bump-before-regen** so its self-version record is correct.

---

## 7. End-to-end runbook (R2, the common case)

```
0. SCOPE       pick R1 / R2 / R3; regen the registry; read the waves
               (python scripts/gen_mgf_libraries.py && python scripts/fed_waves.py)
1. AUDIT       grep every repo for the cornerstone's deleted symbols
               → the skip-vs-migrate list (cohort recipe, REL-10)
2. CORPUS      if standards changed: ship mgf-standard FIRST; REST-verify (§6)
3. CORNERSTONE ship mgf-common; REST-verify  (barrier)
4. WAVES 1→N   per wave, per repo:  /sync conform → gates green → Phase-A edits
               then barrier → sequential Phase-B ship + REST-verify each
               ·  do NOT enter wave k+1 until all of wave k is live
5. APPS        (R3 only) the final wave: same per-repo flow; track in the file
6. REGISTRY    regen mgf-standard registry, bump-before-regen, publish LAST
               (gen_mgf_libraries.py --check must be GREEN + CURRENT)
7. PROVE       REL-13 GREEN + CURRENT (libraries)  +  tracker all-green (apps)
```

---

## 8. Picking the right ring (decision shortcut)

- A change **only** to one library's own code, no pin/version ripple → **R1**,
  and you likely don't need a wave at all (a normal single-package release).
- A `mgf-common` **minor** that any sibling's `<X.Y` ceiling excludes, OR a
  standards/contract change every repo must adopt → **R2** (full wave).
- The above **and** the consumer apps must end the run current (a breaking
  change they call, or an explicit "everything current") → **R3** (R2 + the app
  wave, tracked by hand).

When unsure, R2 is the safe default for anything touching the cornerstone or the
corpus: the REL-13 gate (§4) will tell you, by name, if you under-scoped.

---

## Related

- [`cohort-release-wave.md`](cohort-release-wave.md) — the **version-propagation
  engine** this recipe sequences: Phase A parallel edits / Phase B sequential
  ship, REL-10/11/12.
- [`federation-sync-prompt.md`](federation-sync-prompt.md) — the **conformance
  engine** (`/sync`): comprehend → conform → verify, one repo at a time.
- [`umbrella-drain.md`](umbrella-drain.md) — the special case when the update is
  *retiring* an umbrella package onto granular siblings.
- [`sibling_rename.md`](sibling_rename.md) — the special case when a sibling's
  name/slug changes mid-update.
- [`commit-signing.md`](commit-signing.md) — the AG-03 trailers every commit in
  the run must carry.
- [`codeberg-pr.md`](codeberg-pr.md) — landing each repo's branch via `tea`.
- [`../standards/RELEASING.md`](../standards/RELEASING.md) — REL-10..REL-13, the
  rules the close-out proof (§4) enforces.
- [`MGF_LIBRARIES.md`](../../MGF_LIBRARIES.md) / its *Release order (waves)*
  section · [`scripts/fed_waves.py`](../../scripts/fed_waves.py) — the generated
  walk order.
- [`../../FEDERATION.md`](../../FEDERATION.md) — the federation contract this
  procedure operationalizes.
