# v0.29.0 — Cutover guide

> **Audience:** consumer agents (and humans relaying for them)
> upgrading to `mgf-common` 0.29.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 **second federation split** per
[ASPIRE-06](../../FEEDBACK.md) and the
[federation roadmap](../release/federation_roadmap.md). The entire
`mgf.common.http` subpackage moves to a new sibling distribution
**`mgf-http`** (import path `mgf.http`). For consumers using
`AsyncHttpClient` / `RetryPolicy` 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.http.*` moves to `mgf.http.*` (sibling `mgf-http`). The `[http]` and `[http-test]` extras are deleted. | code |
| [HttpClientError → HttpTransportError](#httpclienterror--httptransporterror) | 🔴 | The transport-failure leaf renames to disambiguate from the HTTP-01 4xx-class hierarchy parent (which keeps the `HttpClientError` name in mgf-common). | code |
| [Recipe relocation (D5)](#recipe-relocation-d5) | 🟡 | `docs/recipes/http.md` moves to `mgf-http/docs/recipes/`. Cross-references from the remaining `mgf-common` docs updated. | docs |

**Pin bump:** `mgf-common = ">=0.29,<0.30"` per the 0.x window
discipline (one minor at a time; AP-03). Consumers wanting the
HTTP-client surface ALSO add: `mgf-http = ">=0.1,<0.2"` (or the
`[test]` extra for `respx` mocking in tests).

**Public-Python-surface change:** every public name that lived
under `mgf.common.http.*` is deleted from `mgf-common` and ships
from `mgf.http.*` in the new sibling. Three top-level names
(`AsyncHttpClient`, `RetryPolicy`, plus the renamed
`HttpTransportError`) and the `mgf.http.exceptions` submodule.

**Behaviour change:** none for `AsyncHttpClient` / `RetryPolicy`
themselves — same signatures, same retry semantics, same
TLS-default-on warning. Two non-breaking improvements landed
during the extraction:

- Replaced a private `mgf.common._identity` import with the public
  `mgf.common.app_name()` / `mgf.common.app_version()` accessors.
- User-Agent suffix changed from `mgf-common/<libver>` to
  `mgf-http/<libver>` — accurate attribution now that the client
  ships from the sibling.

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

---

## Subpackage extraction

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

```text
from mgf.common.http import AsyncHttpClient    →  from mgf.http import AsyncHttpClient
from mgf.common.http import RetryPolicy        →  from mgf.http import RetryPolicy
from mgf.common.http import HttpClientError    →  from mgf.http import HttpTransportError
                                                  (NOTE: rename — see next section)
```

Names covered (the full top-level `__all__` of `mgf.http`):

| Name | Module | Notes |
|---|---|---|
| `AsyncHttpClient` | `mgf.http` | unchanged from v0.28 (modulo the User-Agent suffix swap) |
| `RetryPolicy` | `mgf.http` | unchanged from v0.28 |
| `HttpTransportError` | `mgf.http` (re-exported from `mgf.http.exceptions`) | **renamed** from `HttpClientError` |

### `pyproject.toml` migration

```toml
# Before (v0.28 and earlier)
dependencies = [
    "mgf-common[http]>=0.28,<0.29",
]

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

# After (v0.29)
dependencies = [
    "mgf-common>=0.29,<0.30",
    "mgf-http>=0.1,<0.2",
]

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

The `mgf-http` distribution depends on `mgf-common>=0.29,<0.30`,
so consumers pinning only `mgf-http` automatically pull a
compatible mgf-common.

---

## HttpClientError → HttpTransportError

Before v0.29, mgf-common had **two** classes named
`HttpClientError`:

| Module | Class | Role |
|---|---|---|
| `mgf.common.http._errors` | `HttpClientError(OperationError)` | Transport-layer failure (timeout, connect-refused, network unreachable). |
| `mgf.common.exceptions._well_known` | `HttpClientError(HttpError)` | HTTP-01 hierarchy parent for all 4xx-class HTTP responses. |

Two unrelated classes with the same name — a real footgun in
`except` chains and in error-message readability.

The federation split is the natural moment to fix the collision:
the **transport-failure leaf** moves to a sibling AND renames to
`HttpTransportError`. The HTTP-01 4xx-class parent in mgf-common
keeps the `HttpClientError` name (no break for status-hierarchy
consumers). Disambiguation is by name now, not just by module.

### Mechanical rewrite

```bash
# At your repo root:
git ls-files '*.py' | xargs sed -i \
    -e 's|from mgf\.common\.http\b|from mgf.http|g' \
    -e 's|import mgf\.common\.http\b|import mgf.http|g' \
    -e 's|\bHttpClientError\b|HttpTransportError|g'

# Audit for false positives — the rare consumer using the HTTP-01
# 4xx-class parent (NOT the transport leaf):
git grep -n 'HttpTransportError' -- '*.py'
# Manually revert any `from mgf.common.exceptions import HttpTransportError`
# back to HttpClientError — those are the HTTP-01 hierarchy parent
# (status-class hierarchy), distinct from the transport leaf.
```

For most consumers (PlasmaMapper, mgf-apiprobe) the only
`HttpClientError` reference is the transport leaf; the audit step
is a no-op.

---

## Recipe relocation (D5)

Per **D5** in the federation roadmap:

- `docs/recipes/http.md` — moved to
  `mgf-http/docs/recipes/http.md`.

The recipe README in `mgf-common` (`docs/recipes/README.md`) keeps
the entry as a **pointer** at the sibling path, with `(sibling)` in
the column.

---

## Per-consumer migration notes

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

**No changes required** beyond pin bump. vm-vmanager-app doesn't import
`mgf.common.http` (`R2-MGF-HTTP` was `ACCEPTED NOT MIGRATED` in
2026-05-02 — vm-vmanager-app uses raw `httpx` for its streaming-download +
cancellation-token shape). Bump pin to
`mgf-common = ">=0.29,<0.30"`.

### inthewords (Django web)

**No changes required** beyond pin bump. inthewords doesn't import
`mgf.common.http` anywhere. Bump pin to
`mgf-common = ">=0.29,<0.30"`.

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

**Three steps, mostly mechanical:**

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

   ```toml
   "mgf-common[db,alembic]>=0.29,<0.30",
   "mgf-fastapi>=0.1,<0.2",       # already pinned at v0.28 cutover
   "mgf-http>=0.1,<0.2",
   ```

   Update test extras: `mgf-common[http-test]` → `mgf-http[test]`.

2. **Sed pair.** Run the rewrite from
   [HttpClientError → HttpTransportError](#httpclienterror--httptransporterror)
   above. Audit `HttpTransportError` matches.

3. **Re-run tests.** Wire shape unchanged; should be green.

### mgf-apiprobe

mgf-apiprobe migrated `HttpxTransport` to `AsyncHttpClient` in
v0.6, so this extraction touches its source. Same three steps.

---

## Engineering note — second federation extraction

v0.29.0 is the **second execution** of the staged federation split
per [ASPIRE-06](../../FEEDBACK.md). Compared to v0.28's mgf-fastapi
extraction:

- **Smaller surface** — 4 src files, 2 test files, ~1281 LOC vs
  v0.28's 10 src files + 7 test files. The HTTP wrapper was
  intentionally minimal (rule "thin wrapper, escape hatch via
  `.client`").
- **Lower consumer impact** — vm-vmanager-app + inthewords don't use
  `mgf.common.http` at all; PlasmaMapper + mgf-apiprobe are the
  affected consumers. Compare v0.28 where PlasmaMapper had a
  multi-file FastAPI surface to migrate.
- **One additional rename** — the `HttpClientError` →
  `HttpTransportError` collision fix. v0.28 didn't rename anything
  on extraction; this one does.

The pattern remains: clean break, no deprecation cycle. The
federation leaf deletes the subpackage entirely in the same release
the sibling ships v0.1.0. The 0.x window discipline (AP-03) absorbs
the breakage.

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