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

> **Audience:** consumer agents (and humans relaying for them)
> migrating from `mgf-common[alembic]` (v0.29 and earlier) to
> `mgf-alembic` v0.1.0 + `mgf-sqlalchemy` v0.1.0 + `mgf-common` v0.30.0.
> **What this is:** the canonical migration document for the alembic
> half of the v0.30 paired federation extraction. Companion guides:
> [`mgf-sqlalchemy/docs/cutover/v0.1.0.md`](https://codeberg.org/magogi-admin/mgf_sqlalchemy/src/branch/main/docs/cutover/v0.1.0.md)
> and
> [`mgf-common/docs/cutover/v0.30.0.md`](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/cutover/v0.30.0.md).

mgf-alembic ships **paired with mgf-sqlalchemy** at v0.30. They form
one logical extraction split into two distributions because alembic
is genuinely optional — consumers using SQLAlchemy without migrations
don't need to install alembic, and the federation pattern keeps each
3rd-party dep behind its own sibling.

---

## At-a-glance

| Was (≤ mgf-common v0.29) | Is (mgf-common v0.30 + mgf-alembic v0.1) |
|---|---|
| `pip install 'mgf-common[alembic]'` | `pip install mgf-alembic` |
| `pip install 'mgf-common[alembic-test]'` | `pip install 'mgf-alembic[test]'` |
| `from mgf.common.alembic import configure_env` | `from mgf.alembic import configure_env` |

**One import-path rename. No code body changes.** The
`configure_env` implementation (including the PAPER-22
`include_object` / `include_schemas` knobs that shipped in mgf-common
v0.19) is byte-identical to what shipped in mgf-common v0.29.0
modulo the import path.

---

## What landed in mgf-alembic v0.1.0

| Submodule | What |
|---|---|
| `mgf.alembic` | `configure_env` — one call replaces the ~40-line `env.py` boilerplate. Auto-detects sync vs async drivers from the URL prefix. Plumbs `compare_type`, `compare_server_default`, `naming_convention`, `include_object`, `include_schemas` through to `alembic.context.configure`. |

One name. Same shape. Tested against all five async URL prefixes.

---

## Migration steps

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

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

# After
dependencies = [
    "mgf-common>=0.30,<0.31",
    "mgf-sqlalchemy>=0.1,<0.2",
    "mgf-alembic>=0.1,<0.2",
]
```

`mgf-sqlalchemy` is pulled in transitively via mgf-alembic's runtime
dep, but list it explicitly so version updates are visible at the
consumer level.

For test consumers (using aiosqlite for async-driver round-trip
tests):

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

# After
test = [
    "mgf-common>=0.30,<0.31",
    "mgf-sqlalchemy[test]>=0.1,<0.2",
    "mgf-alembic[test]>=0.1,<0.2",
]
```

### 2. Rewrite imports in `alembic/env.py`

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

Most consumers have a single `alembic/env.py` file with a single
import line; the rewrite is one-shot.

### 3. Re-run the four gates

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

### 4. Verify migrations still run

```bash
uv run alembic upgrade head    # online — should be a no-op if up-to-date
uv run alembic upgrade --sql head    # offline — emits SQL to stdout
```

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

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

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

The audit's pin-checker recognises `mgf-alembic` as an expected
sibling dep. The closed-box-leak checker also understands
`mgf.alembic` as a sibling import path.

---

## What changed internally

- **No code changes** in `configure_env`. PAPER-22's
  `include_object` / `include_schemas` (shipped in mgf-common v0.19)
  carry over byte-identical.
- **Dep graph.** mgf-alembic depends on `mgf-common>=0.30,<0.31`
  (for the typed-exception hierarchy) AND
  `mgf-sqlalchemy>=0.1,<0.2` (paired-release siblings). The full
  install pulls all three.
- **PEP 420 namespace package.** `mgf.alembic` ships in a
  `src/mgf/` tree with no top-level `__init__.py`, matching the
  pattern of mgf-fastapi, mgf-http, mgf-sqlalchemy, mgf-common.

---

## Why this split happened

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

- Alembic is genuinely optional — consumers using SQLAlchemy without
  migrations (or running migrations through a different tool) don't
  need to install alembic. The mgf-common `[alembic]` extra already
  enforced this, but a sibling boundary makes the optionality
  unambiguous.
- The paired-release shape (mgf-sqlalchemy + mgf-alembic together at
  v0.30) reflects how consumers actually adopt the helpers:
  `configure_env` composes with `create_engine` (both auto-detect
  async drivers, both wire `pool_pre_ping`-friendly defaults), and
  the typed exception hierarchy from mgf-common ties them together.
- Future migration-shaped concretes (e.g.,
  `MigrationConflictError` if filed) land here, not in mgf-common —
  keeping mgf-common as the federation leaf with hierarchy roots
  only.

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

| mgf-common release | Extracts | Status |
|---|---|---|
| v0.28 | mgf-fastapi | shipped |
| v0.29 | mgf-http | shipped |
| v0.30 (this one) | mgf-sqlalchemy + mgf-alembic + mgf-fastapi v0.2 | this release |
| 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 one file:** `alembic/env.py`. Steps:

1. Bump pins per [Step 1](#1-update-your-pyprojecttoml).
2. Rewrite the import in `alembic/env.py` per [Step 2](#2-rewrite-imports-in-alembicenvpy).
3. Re-run `alembic upgrade --sql head` to verify the offline path
   still emits the same SQL (modulo whitespace).
4. PostGIS schema-filter callable (already plumbing through
   `include_object` per PAPER-22) is unchanged — no rewrite needed.

### inthewords (Django web)

**Not affected.** Django uses Django's own migration framework
(`makemigrations` / `migrate`), not alembic. Bump pin to
`mgf-common>=0.30,<0.31`. No rewrite.

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

**Not affected.** No alembic in vm-vmanager-app. Bump pin to
`mgf-common>=0.30,<0.31`. No rewrite.

### mgf-apiprobe

**Not affected.** No alembic in mgf-apiprobe.

---

## What's deliberately NOT in v0.1.0

The first mgf-alembic release is **byte-identical** to
mgf-common v0.29.0's `mgf.common.alembic` surface (modulo the import
path). New surface waits for v0.2.0+:

- **Typed extension-skip helper.** PAPER-22's deferred Approach B
  (`mgf.alembic.skip_extension_tables(*, postgis=False, timescaledb=False, pgvector=False)`)
  could ship as a typed convenience layered over `include_object`.
  Files when a consumer asks for it.
- **Multi-database orchestration.** Today's pattern (multiple env.py
  files, one per database, each calling `configure_env`) works but
  isn't sugared. Promotion to a typed helper if a second consumer
  signals need.
- **Migration linter.** Could ship as a CLI sub-command that flags
  unsafe migrations (raw SQL without rollback, schema renames
  without data, etc.). Out of scope for v0.1.

---

## Engineering note

mgf-alembic v0.1.0 carries 11 tests parity-preserved from
mgf-common v0.29.0's `tests/unit/alembic/test_env.py`:

- 2 sync-driver round-trip tests
- 1 async-driver round-trip test (with the aiosqlite GC-sweep
  workaround for finalizer-warnings-during-teardown)
- 1 offline-mode SQL-emission test
- 1 naming-convention smoke test
- 2 `include_object` / `include_schemas` pass-through tests
  (PAPER-22)
- 4 URL-prefix detection parametrized tests

Coverage threshold ≥80% matches mgf-common's standard. The PEP 420
namespace package (no top-level `mgf/__init__.py` in any sibling
repo) means `mgf.alembic`, `mgf.sqlalchemy`, `mgf.fastapi`,
`mgf.http`, and `mgf.common` coexist seamlessly in the same Python
process.
