# mgf-http v0.1.0 — Maiden voyage cutover guide

> **Audience:** consumer agents (and humans relaying for them)
> migrating from `mgf-common[http]` (v0.28 and earlier) to
> `mgf-http` v0.1.0 + `mgf-common` v0.29.0.
> **What this is:** the canonical migration document for the
> federation extraction. The complementary mgf-common cutover
> guide is at
> [`mgf-common/docs/cutover/v0.29.0.md`](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/cutover/v0.29.0.md).

This is the **second sibling extraction** under the federation
split plan recorded in
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/release/federation_roadmap.md).
mgf-fastapi shipped first at v0.1.0 paired with mgf-common v0.28.0;
mgf-http ships second at v0.1.0 paired with mgf-common v0.29.0.

---

## At-a-glance

| Was (≤ mgf-common v0.28) | Is (mgf-common v0.29 + mgf-http v0.1) |
|---|---|
| `pip install 'mgf-common[http]'` | `pip install mgf-http` |
| `pip install 'mgf-common[http-test]'` | `pip install 'mgf-http[test]'` |
| `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` (renamed) |

**One BREAKING name change:** `HttpClientError` →
`HttpTransportError`. See [The HttpClientError rename](#the-httpclienterror-rename)
section below for the rationale and a sed-friendly rewrite.

**No code body changes** otherwise. The `AsyncHttpClient` /
`RetryPolicy` implementations are byte-identical to what shipped
in mgf-common v0.28.0 (modulo the import path and one private
import that moved to the public accessor — see *What changed
internally* below).

---

## What landed in mgf-http v0.1.0

| Submodule | What |
|---|---|
| `mgf.http` | `AsyncHttpClient`, `RetryPolicy`, `HttpTransportError` (convenience re-export). |
| `mgf.http.exceptions` | `HttpTransportError(OperationError)` — the transport-failure concrete leaf. |

The User-Agent suffix changed from `mgf-common/<libver>` to
`mgf-http/<libver>` — accurate attribution now that the client
ships from the sibling.

---

## The HttpClientError rename

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 a natural moment to fix it: the
transport-failure concrete moves to a sibling, the HTTP-01
hierarchy parent stays in mgf-common. **Both keeping the same name
across the split would multiply the confusion** (now they'd be in
different distributions, different import paths, harder to tell
apart in logs).

The fix at v0.29: rename **the transport-failure leaf** to
`HttpTransportError`. The HTTP-01 4xx-class parent in mgf-common
keeps the `HttpClientError` name (no break to consumers using the
status hierarchy). Disambiguation is by name now, not just by
module.

### Mechanical rewrite

For consumers using `HttpClientError` to catch transport failures
from `AsyncHttpClient.request()`:

```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'

# Then audit anywhere "HttpClientError" might still legitimately mean
# the HTTP-01 4xx-class parent (rare; usually only in
# `except HttpClientError` over a status hierarchy):
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 hierarchy), NOT the transport leaf. Distinct concepts; the
# sibling-side rename is only for the transport leaf.
```

The sed above is intentionally a little aggressive — it renames every
`HttpClientError` token. The audit step catches the rare case where a
consumer was using the HTTP-01 parent for `except` chains over 4xx
statuses (which keeps the name `HttpClientError`).

For most consumers (PlasmaMapper, future web siblings) the transport
rename is the only `HttpClientError` reference; the audit step is a
no-op.

---

## Migration steps

### 1. Update your `pyproject.toml`

Replace the `mgf-common[http]` extra with a direct `mgf-http`
dependency:

```toml
# Before
dependencies = [
    "mgf-common[http]>=0.28,<0.29",
]

# After
dependencies = [
    "mgf-common>=0.29,<0.30",
    "mgf-http>=0.1,<0.2",
]
```

For test consumers (using `respx` for mocking httpx):

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

# After
test = [
    "mgf-common>=0.29,<0.30",
    "mgf-http[test]>=0.1,<0.2",
]
```

### 2. Rewrite imports + the HttpClientError rename

Run the sed pair from
[The HttpClientError rename](#the-httpclienterror-rename) above.
This handles ~99% of the migration mechanically.

For non-import contexts (docstrings, log messages, comments):

| Search | Replace |
|---|---|
| `mgf.common.http.` | `mgf.http.` |
| `HttpClientError` *(referring to transport failures)* | `HttpTransportError` |

### 3. Re-run the four gates

```bash
uv sync
uv run ruff check
uv run mypy --strict src/
uv run pytest
```

If your test suite was green against mgf-common v0.28, it should
be green against mgf-common v0.29 + mgf-http v0.1 immediately
after the import rewrite + class rename.

### 4. Re-run `mgf-common catch-up`

```bash
mgf-common catch-up
```

The audit's pin-checker recognizes the new `mgf-http` dep and
won't flag it. The closed-box-leak checker also understands
`mgf.http` as a sibling import path.

---

## What changed internally

Two non-breaking improvements landed during the extraction:

1. **Private-import fix.** `mgf.common.http._client` previously
   reached into `mgf.common._identity._APP_NAME` /
   `_APP_VERSION` to compute the User-Agent. The closed-box rule
   says "no consumer reaches into another module's private
   names" — and `mgf.common.http` IS a consumer of
   `mgf.common._identity`. The sibling now uses the public
   accessors `mgf.common.app_name()` /
   `mgf.common.app_version()`. Behaviour is identical.
2. **User-Agent attribution.** The library suffix in the
   User-Agent header changed from `mgf-common/<libver>` to
   `mgf-http/<libver>`. Accurate attribution now that
   `AsyncHttpClient` ships from this sibling. If you have
   server-side log filtering that matches on
   `mgf-common/<libver>` for HTTP traffic specifically, update
   to `mgf-http/<libver>`.

---

## Why this split happened

Per the federation roadmap. The reasoning is shared across all
sibling extractions, but for `mgf-http` specifically:

- The `httpx` runtime dep is large and hot; consumers without
  HTTP needs (CLI tools, data-pipeline workers using only
  `mgf.common.config` + `mgf.common.observability`) shouldn't pay
  the install cost. The `[http]` extra already enforced that, but
  the sibling split is a cleaner barrier.
- The `HttpClientError` collision in mgf-common (transport leaf
  vs HTTP-01 hierarchy parent) was a long-standing footgun. The
  extraction is the natural moment to fix it.
- Future HTTP-shaped concretes (`HttpRetryExhaustedError` if
  ever filed) land in `mgf.http.exceptions`, not in
  `mgf.common.exceptions` — keeping mgf-common as the federation
  leaf with hierarchy roots only.

This is the **second** of four planned extractions:

| mgf-common release | Extracts | Status |
|---|---|---|
| v0.28 | mgf-fastapi | shipped |
| v0.29 (this one) | mgf-http | this release |
| v0.30 | mgf-sqlalchemy + mgf-alembic | future |
| v0.31 | mgf-django | future |
| v0.32+ | stabilization | future |
| v1.0 | lock the federated surface | future |

The full plan is in
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/release/federation_roadmap.md).

---

## Per-consumer migration notes

### PlasmaMapper (FastAPI scientific platform)

**Migration touches every provider integration** that uses the
typed httpx wrapper (Teltonika, Stripe, map providers, etc.).
Steps:

1. Bump pins: drop `mgf-common[http]`; add `mgf-http>=0.1,<0.2`.
2. Run the sed pair from
   [The HttpClientError rename](#the-httpclienterror-rename).
   Audit any remaining `HttpClientError` references — most likely
   none in PlasmaMapper (the HTTP-01 hierarchy parent isn't
   typically caught directly).
3. Re-run unit + integration tests. Wire shape unchanged.

### inthewords (Django)

**Not affected.** Django consumer doesn't import
`mgf.common.http` anywhere. Bump pin to
`mgf-common>=0.29,<0.30`. No rewrite.

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

**Not affected — by deliberate decision.** Per vm-vmanager-app's
`R2-MGF-HTTP` audit (recorded as ACCEPTED NOT MIGRATED in
2026-05-02), vm-vmanager-app uses raw `httpx` directly for its
streaming-download + cancellation-token shape; the typed wrapper
adds marginal value for vm-vmanager-app's specific use cases. Bump pin to
`mgf-common>=0.29,<0.30`. No rewrite.

### mgf-apiprobe

**Migrates.** mgf-apiprobe's `HttpxTransport` was migrated to
`AsyncHttpClient` in v0.6. Steps:

1. Bump pins as above.
2. Run the sed pair.
3. Re-run probe-suite tests.

---

## What's deliberately NOT in v0.1.0

The first mgf-http release is **byte-identical** to mgf-common
v0.28.0's HTTP surface (modulo the rename + private-import fix).
New surface waits for v0.2.0+:

- **`HttpRetryExhaustedError`** — a distinct leaf for "retried
  N times, all failed." Currently surfaces as `HttpTransportError`
  with `attempts > 1`. File a paper if a consumer wants
  separate-leaf handling.
- **Per-host rate limits.** `RetryPolicy` retries on 429 but
  doesn't honour `Retry-After`. Future enhancement.
- **Sync `HttpClient`.** Async-only today; sync wrapper is a
  follow-up paper if a consumer asks.
- **Streaming download / multipart upload helpers.** Use the
  `.client` escape hatch for these today; promotion to typed
  helpers if a second consumer signals need.

---

## Engineering note

mgf-http v0.1.0 carries 46 tests parity-preserved from
mgf-common v0.28.0 (every test_client + test_retry case moves over
unchanged in shape, with imports rewritten and `HttpClientError`
references renamed to `HttpTransportError`). Coverage threshold
≥80% matches mgf-common's standard.

The PEP 420 namespace package (no top-level `mgf/__init__.py` in
either repo) means `mgf.http`, `mgf.fastapi`, and `mgf.common`
coexist seamlessly in the same Python process. No `__init__`
conflicts; no namespace collision; consumers can install all three
without surprise.
