# v0.28.0 — Cutover guide

> **Audience:** consumer agents (and humans relaying for them)
> upgrading to `mgf-common` 0.28.0.
> **What this is:** the canonical migration document for this
> release. Per [`RELEASING.md`](../standards/RELEASING.md) **REL-09**
> + the cutover-guide pattern, individual per-entry notification
> paragraphs are pointers into this guide.

This release executes the **first federation split** per
[ASPIRE-06](../../FEEDBACK.md) and the
[federation roadmap](../release/federation_roadmap.md). The entire
`mgf.common.fastapi` subpackage moves to a new sibling distribution
**`mgf-fastapi`** (import path `mgf.fastapi`). For consumers with a
FastAPI surface this is a 🔴 **breaking** ship; for everyone else
it's a pin bump.

---

## At-a-glance

| Entry | Severity | What landed | Code or docs? |
|---|---|---|---|
| [Subpackage extraction](#subpackage-extraction) | 🔴 | Every name in `mgf.common.fastapi.*` moves to `mgf.fastapi.*` (sibling `mgf-fastapi`). The `[fastapi]` and `[fastapi-test]` extras are deleted. | code |
| [Domain concrete moves with sibling (D3)](#domain-concrete-moves-with-sibling-d3) | 🔴 | `HmacVerificationError` moves from `mgf.common.exceptions` to `mgf.fastapi.exceptions`. | code |
| [Recipe relocation (D5)](#recipe-relocation-d5) | 🟡 | `docs/recipes/{fastapi,webhooks}.md` move to `mgf-fastapi/docs/recipes/`. Cross-references from the remaining `mgf-common` docs updated. | docs |

**Pin bump:** `mgf-common = ">=0.28,<0.29"` per the 0.x window
discipline (one minor at a time; AP-03). Consumers wanting the
FastAPI surface ALSO add: `mgf-fastapi = ">=0.1,<0.2"` (or whatever
the sibling's first-release pin is).

**Public-Python-surface change:** every public name that lived under
`mgf.common.fastapi.*` (subpackages included) is deleted from
`mgf-common` and ships from `mgf.fastapi.*` in the new sibling.
Total: ~17 public names + the `webhooks` / `security` / `testing`
subpackage shapes.

**Behaviour change:** none for the names themselves — the sibling
ships the same code, same signatures, same tests. The break is the
import path.

**Optional-extra change:** `mgf-common[fastapi]` and
`mgf-common[fastapi-test]` are removed entirely. The replacement is
`mgf-fastapi` (or `mgf-fastapi[testing]`) installed alongside.

---

## Subpackage extraction

The full rename map for any `from mgf.common.fastapi…` import:

```text
from mgf.common.fastapi import X                  →  from mgf.fastapi import X
from mgf.common.fastapi.webhooks import …         →  from mgf.fastapi.webhooks import …
from mgf.common.fastapi.security import IpAllowlist
                                                  →  from mgf.fastapi.security import IpAllowlist
from mgf.common.fastapi.testing import run_test_app
                                                  →  from mgf.fastapi.testing import run_test_app
```

Names covered by the rename (the full top-level `__all__` of
`mgf.fastapi` and its subpackages):

| Name | Sub-module | Notes |
|---|---|---|
| `setup_lifespan` | `mgf.fastapi` | bootstrap-friendly lifespan factory |
| `RequestIdMiddleware` | `mgf.fastapi` | `X-Request-Id` per request |
| `current_request_id` | `mgf.fastapi` | reads the contextvar |
| `REQUEST_ID_HEADER` | `mgf.fastapi` | header name constant |
| `ExceptionTranslationMiddleware` | `mgf.fastapi` | reads `HttpError.http_status` |
| `DEFAULT_STATUS_MAP` | `mgf.fastapi` | the standard map |
| `get_app_context` / `get_settings` / `get_request_id` | `mgf.fastapi` | `Depends`-friendly helpers |
| `IpAllowlist` / `LOOPBACK_CIDRS` | `mgf.fastapi.security` | unchanged from v0.27 |
| `run_test_app` | `mgf.fastapi.testing` | unchanged from v0.27 |
| `HmacWebhookVerifier`, `verify_request`, `WebhookHeaderSchema`, `SVIX_SCHEMA` | `mgf.fastapi.webhooks` | unchanged from v0.26 |

### `pyproject.toml` migration

```toml
# Before (v0.27 and earlier)
dependencies = [
    "mgf-common[fastapi]>=0.27,<0.28",
]

[project.optional-dependencies]
test = [
    "mgf-common[fastapi-test]>=0.27,<0.28",
]

# After (v0.28)
dependencies = [
    "mgf-common>=0.28,<0.29",
    "mgf-fastapi>=0.1,<0.2",
]

[project.optional-dependencies]
test = [
    "mgf-fastapi[testing]>=0.1,<0.2",
]
```

The `mgf-fastapi` distribution itself depends on
`mgf-common>=0.28,<0.29`, so consumers don't need to repeat the
mgf-common pin if they only depend on `mgf-fastapi`. Pinning both
explicitly is fine and recommended for direct-import consumers.

### `import` rewrite — sed-friendly shape

For mechanical rewrites:

```bash
# At the repo root of any consumer:
git ls-files '*.py' | xargs sed -i \
    -e 's|from mgf\.common\.fastapi\b|from mgf.fastapi|g' \
    -e 's|import mgf\.common\.fastapi\b|import mgf.fastapi|g'

# Then audit specifically for HmacVerificationError:
git grep -n 'from mgf\.common\.exceptions import.*HmacVerificationError'
# rewrite hits to: from mgf.fastapi.exceptions import HmacVerificationError
```

Both replacements are exact-prefix; there's no ambiguity with
`mgf.common.fastapi` vs other names because nothing else uses that
prefix in the public API.

---

## Domain concrete moves with sibling (D3)

`HmacVerificationError` was the one concrete exception leaf in
`mgf.common.exceptions` that was domain-specific to the
FastAPI / webhooks adapter. Per **D3** in the federation roadmap
(*"sibling-domain concrete leaves move with their sibling"*) it
moves to `mgf.fastapi.exceptions`.

What stays in `mgf.common.exceptions`:

- The full `AppError` hierarchy + 7 categories.
- The HTTP-01 hierarchy roots: `HttpError`, `HttpClientError` /
  `HttpServerError`, the per-status leaves (`HttpUnauthorized`,
  `HttpForbidden`, `HttpNotFound`, `HttpConflict`, `HttpBadRequest`,
  `HttpRateLimited`, `HttpInternalServerError`,
  `HttpServiceUnavailable`).

The pattern generalises: future domain concretes filed by web
consumers (`CsrfVerificationError` for mgf-django,
`HttpRetryExhaustedError` for mgf-http) will land in their respective
sibling, not in mgf-common.

```python
# Before (v0.27 and earlier)
from mgf.common.exceptions import HmacVerificationError

# After (v0.28)
from mgf.fastapi.exceptions import HmacVerificationError
```

`HmacVerificationError` still inherits from
`mgf.common.exceptions.HttpUnauthorized`, so the
`ExceptionTranslationMiddleware` 401-mapping behaviour is identical.

---

## Recipe relocation (D5)

Per **D5** in the federation roadmap (*"framework-specific recipes
split with their package"*):

- `docs/recipes/fastapi.md` — moved to
  `mgf-fastapi/docs/recipes/fastapi.md`.
- `docs/recipes/webhooks.md` — moved to
  `mgf-fastapi/docs/recipes/webhooks.md`.

The recipe README in `mgf-common` (`docs/recipes/README.md`) keeps
the entries as **pointers** at the sibling path, with `(sibling)` in
the column. The cross-cutting `docs/standards/web/` subtree stays in
`mgf-common` per **D4** (it spans mgf-fastapi + mgf-django + future
mgf-flask collectively).

---

## Per-consumer migration notes

### vm-vmanager-app (Qt desktop)

**No changes required** beyond pin bump. vm-vmanager-app has no FastAPI
surface; the `mgf.common.fastapi.*` rename doesn't touch your
project. Bump the pin to `mgf-common = ">=0.28,<0.29"`.

### inthewords (Django web)

**No changes required** beyond pin bump. inthewords is Django-
shaped, not FastAPI-shaped; the rename doesn't touch your project.
Bump the pin to `mgf-common = ">=0.28,<0.29"`.

If you ever add a FastAPI sidecar (admin tool, webhook receiver),
install `mgf-fastapi` as a separate dep then.

### PlasmaMapper (FastAPI scientific platform — migration consumer)

**You are the migration consumer.** Three steps, all mechanical:

1. **Pin bump.** Replace `mgf-common[fastapi,db,alembic,http]>=0.27,<0.28`
   in `pyproject.toml` with:

   ```toml
   "mgf-common[db,alembic,http]>=0.28,<0.29",
   "mgf-fastapi>=0.1,<0.2",
   ```

   Also update any test extra: `mgf-common[fastapi-test]` →
   `mgf-fastapi[testing]`.

2. **Import rewrite.** Run the sed pair from
   [Subpackage extraction](#subpackage-extraction) above against
   `src/` and `tests/`. Audit the `HmacVerificationError` import if
   you were using it from `mgf.common.exceptions`.

3. **Re-run the test suite.** No code-shape change is expected; if
   the suite stays green the migration is complete.

If the migration surfaces any rough edge in the rename (`mgf-fastapi`
v0.1 missing a name, behaviour drift on a re-shipped helper, etc.),
file a follow-up paper against `mgf-fastapi` directly.

---

## Engineering note — first federation extraction

v0.28.0 is the first execution of the staged federation split per
[ASPIRE-06](../../FEEDBACK.md) and the
[federation roadmap](../release/federation_roadmap.md). The shape
this extraction proves out is the template for the rest of the v0.28
→ v1.0 sequence:

| Release | Extracts | Status |
|---|---|---|
| **v0.28.0** | `mgf-fastapi` | this release |
| v0.29.0 | `mgf-http` | next |
| v0.30.0 | `mgf-sqlalchemy` + `mgf-alembic` | future |
| v0.31.0 | `mgf-django` | future |
| v1.0.0 | (federated surface lock) | future |

The pattern is **clean break, no deprecation cycle**: the federation
leaf deletes the subpackage entirely in the same release the sibling
ships v0.1.0. No `.fastapi` stub re-exporting from `mgf.fastapi`.
The 0.x window discipline (AP-03) absorbs the breakage; consumers
pin tightly and migrate one extraction at a time.

The rename map shape (this guide) is the consumer-facing artefact;
the federation roadmap is the maintainer-facing one. Both stay
load-bearing through v1.0.
