# For release engineers

> **Who you are.** You're cutting a release of `mgf-common`,
> running CI for it, or designing the same shape for a sibling
> Magogi library. You want the runbook + the rules.

## In one paragraph

`mgf-common` releases follow a **per-batch minor-bump cadence**
in the 0.x window (per AP-03). Each release closes 1+
`FEEDBACK.md` entries OR ships a feature batch OR is
docs-only. Every release ships with a **cutover guide** at
`docs/cutover/v<X.Y.Z>.md` (REL-09); old guides archive after
3 minor versions. Token-based PyPI publish via `uv build` +
`twine upload`. Codeberg is canonical SCM; PyPI is the
contract.

## Read first

1. `mgf-fed standards RELEASING` — the REL-* rule corpus.
   REL-09 is the cutover-guide pattern; REL-01/02/03 are the
   bump/tag/publish rules.
2. [`docs/release/ci.md`](../release/ci.md) — the three-flow
   runbook (cut a release / set up CI / diagnose a failure).
3. `mgf-fed standards WORKFLOWS` — WF-* rules (CI
   organisation, gates, branches).
4. `mgf-fed standards WORKFLOWS_PATTERNS` — paste-ready
   Woodpecker recipes.

## The release flow

```
1. Triage: ACCEPT N FEEDBACK entries; group into a topic batch.
2. Implement + tests pass (1445+).
3. Bump version in src/mgf/common/__init__.py + pyproject.toml.
4. Regenerate PUBLIC_API.json: `python tools/generate_public_api_json.py`.
5. Update CHANGELOG.md [<X.Y.Z>] section.
6. Write docs/cutover/v<X.Y.Z>.md (per REL-09).
7. Update PUBLIC_API.md if surface changed.
8. Commit + tag + push (commit, tag v<X.Y.Z>, push origin master + tag).
9. uv build → dist/ has wheel + sdist.
10. twine upload (token from .pypirc OR env).
11. Smoke install: uv venv /tmp/v<X.Y.Z>_smoke + uv pip install --refresh.
12. Verify: __version__ + key new surface.
13. Relay consumer notification paragraphs (per REL-09).
14. If oldest cutover guide is now > 3 minor versions back, archive it.
```

The CHANGELOG entry per release follows Keep-a-Changelog +
links to the cutover guide.

## Versioning policy (AP-03)

While in the 0.x window:

- **Minor bump** (`0.X.0` → `0.Y.0`) — every closure batch,
  feature addition, OR docs-only release that ships through
  the wheel.
- **Patch bump** (`0.X.Y` → `0.X.Z`) — defensible but
  underused; we prefer minor bumps for visibility.
- **No 1.0** until the user explicitly green-lights.

Pin discipline: `mgf-common = ">=0.X,<0.Y"` (one minor at a
time). Per-release cutover guides document migration cost;
consumers read them before bumping.

## The cutover guide pattern (REL-09)

Every release closing ≥ 1 `FEEDBACK.md` entry MUST ship a
cutover guide. Structure:

```
1. Audience + framing (one paragraph).
2. At-a-glance table (per-entry severity + summary).
3. Per-entry deep-dive sections.
4. Per-consumer migration notes (vm-vmanager-app / inthewords / PlasmaMapper).
5. Engineering note (what pattern this release establishes).
```

Last 3 minor versions live at `docs/cutover/v*.md`; older
ones move to `docs/cutover/archive/`.

## Consumer notification (REL-09)

After a release is published to PyPI + smoke-verified, the
maintainer relays a paragraph per consumer agent (or a single
uniform paragraph if the release is docs-only). The
notification points the agent into the relevant § of the
cutover guide.

This is the canonical surface area where `mgf-common`'s
maintainer-side AI agent talks to consumer-side AI agents.

## CI (Codeberg Woodpecker)

Current state:
- `.woodpecker.yml` ships at the repo root.
- Tag-driven publish flow: `tag-coherence` → `smoke` → `publish` →
  `verify-pypi` (SHA256 polling).
- Codeberg CI access is per-user-request via
  [`codeberg.org/Codeberg-e.V./requests`](https://codeberg.org/Codeberg-e.V./requests).
  Currently **pending** for this account → manual `uv build` +
  `twine upload` is the working flow until access lands.

When CI access lands, the manual flow becomes the
fallback; see [`docs/release/ci.md`](../release/ci.md) for
the three-flow runbook.

## PyPI publish

Manual flow:

```bash
uv build                                                # → dist/mgf_common-X.Y.Z*
TWINE_USERNAME=__token__ \
TWINE_PASSWORD=<pypi-AgEI...> \
  twine upload dist/mgf_common-X.Y.Z*
```

Token (PyPI scoped to project `mgf-common`) lives in `.pypirc`
or env. Never commit it.

Smoke verify:

```bash
cd /tmp && rm -rf vXYZ_smoke && uv venv vXYZ_smoke
UV_NO_CACHE=1 uv pip install --refresh \
  --python /tmp/vXYZ_smoke/bin/python "mgf-common==X.Y.Z"
/tmp/vXYZ_smoke/bin/python -c "import mgf.common; print(mgf.common.__version__)"
```

PyPI's index cache lags ~60 seconds after twine upload — the
smoke install retry pattern is documented in
[`docs/release/ci.md`](../release/ci.md).

## What to do when something goes wrong

- **Tag pushed, twine upload failed (token revoked).** Mint a
  fresh token, re-run upload. The tag is fine.
- **PyPI rejects upload (filename collision).** Bump the
  patch (we don't re-publish the same version).
- **Smoke install fails after PyPI cache propagates.** Real
  bug — the wheel is broken. Yank the version on PyPI; cut a
  patch.
- **Pre-commit hooks fail mid-release.** Per the maintainer's
  convention: NEVER `--no-verify`. Fix the underlying issue;
  re-run.

## What stays maintainer-only

- The PyPI token rotation cadence (rotate after every N uses
  or T months).
- The Codeberg CI access request status.
- The `.pypirc` configuration.

Documented in [`docs/internal/lift_checklist.md`](../internal/lift_checklist.md)
when relevant to a contribution flow.
