# Package Distribution — the Codeberg PyPI Registry

> **STATUS: FINALIZED — L1-binding.** The federation standard for `mgf-*` package
> distribution: published to and resolved from Codeberg's PyPI-compatible
> registry, with `pypi.org` dropped as the `mgf-*` publish/distribution channel
> (third-party deps still resolve from public PyPI — §1). Every endpoint, auth,
> and quota fact below was **verified against the live `magogi-admin` registry on
> 2026-06-03** — `mgf-standard` 0.1.5 was published, REST-verified, and
> installed into a clean venv from the Codeberg index end-to-end. The §11 wiring
> of the rest of the corpus landed in the same DOC-18 change wave as this doc
> (RELEASING / WORKFLOWS* / AGENTS AG-09 / NAMING NM-01 / the recipes / README
> Index of MUSTs all retargeted to Codeberg).

> **Conformance:** L1 (every Magogi-foundation project that publishes
> or consumes an `mgf-*` package).
> **Audience:** Human engineers AND AI coding agents acting as release
> maintainers or as supervisors applying a coupled change; consumers wiring an
> `mgf-*` dependency.
> **Propagation (DOC-18):** changing a rule here ripples —
> walk DOCUMENTATION.md §10.4 and update its dependents in the same commit
> (README *Index of MUSTs*, the `mgf-fed standards` / `agents`
> CLI, RELEASING.md / WORKFLOWS*.md, the release recipes, `.woodpecker.yml`, and
> any parallel copies / pins the matrix names).

> **Scope.** The federation's package **distribution channel**: where every
> `mgf-*` wheel/sdist is published, how it is authenticated, how a consumer
> resolves it, and how a release is verified. It **replaces `pypi.org` as the
> publish + distribution target for `mgf-*` packages** with the Codeberg
> (Forgejo) PyPI registry owned by the `magogi-admin` Codeberg account. It does **not** change
> what a package *is* (PEP 503 names, SemVer, the build) — those stay; only the
> *index* changes. See §1 for the one nuance that is not a full "drop": **third-
> party dependencies (pydantic, click, …) still resolve from public PyPI** — what
> we drop is publishing `mgf-*` to pypi.org and resolving `mgf-*` from it.

---

## 0. TL;DR — the one-screen version

**Registry owner:** `magogi-admin` (private Codeberg account — a user, not an org; the package API path is identical either way).
**Base:** `https://codeberg.org/api/packages/magogi-admin/pypi`

| Action | Command |
|---|---|
| **Publish** (twine) | `twine upload --repository-url https://codeberg.org/api/packages/magogi-admin/pypi -u "$CB_USER" -p "$CB_TOKEN" dist/*` |
| **Publish** (uv) | `uv publish --publish-url https://codeberg.org/api/packages/magogi-admin/pypi -u "$CB_USER" -p "$CB_TOKEN" dist/*` |
| **Verify** a version is live | `curl -fsS -H "Authorization: token $CB_TOKEN" https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<version>` |
| **Install** one package | `pip install --index-url https://$CB_USER:$CB_TOKEN@codeberg.org/api/packages/magogi-admin/pypi/simple/ --extra-index-url https://pypi.org/simple/ <name>` |
| **Recover** a botched upload | `curl -fsS -X DELETE -H "Authorization: token $CB_TOKEN" https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<version>` then re-upload |

- `CB_USER` = your Codeberg username (a member of `magogi-admin`).
- `CB_TOKEN` = a Codeberg access token with **`write:package`** (publish/delete) or **`read:package`** (install only).
- The dependency-confusion-safe consumer config (uv named index + per-package pinning) is in §6 — prefer it over the bare `--extra-index-url` above.

---

## 1. Why Codeberg, not pypi.org — and the one nuance

**Why move.** The federation is hosted end-to-end on Codeberg (source, issues,
PRs, CI). Publishing to a third party (pypi.org) splits the trust root, exposes
internal library names to the public index, couples releases to a PyPI account +
token lifecycle the federation does not control (the 2026-06-02 token revocation
that blocked `mgf-standard` 0.1.5 is the motivating incident), and makes the
public index the source of truth for what are, in practice, **private federation
internals**. Co-locating distribution with the code on the same host the
federation already controls removes a moving part and a class of outage.

**The one nuance — this is a channel switch, not "never touch PyPI".** `mgf-*`
packages depend on third-party libraries (`pydantic`, `pydantic-settings`,
`click`, …) that live on **public PyPI**. Those wheels still come from PyPI's
index at install time — there is no federation copy and no goal to make one.
What we drop is precise:

- **DROP:** publishing any `mgf-*` package to pypi.org.
- **DROP:** resolving any `mgf-*` package *from* pypi.org (consumers pull `mgf-*`
  only from Codeberg).
- **DROP:** every pypi.org URL / token / Trusted-Publisher reference in the
  release tooling and standards as the `mgf-*` distribution mechanism.
- **KEEP:** public PyPI as the default index for **third-party** dependencies.

The clean way to express both at once (mgf-* from Codeberg, everything else from
PyPI) without a dependency-confusion hole is **explicit per-package index
pinning** — see §6. This is *more* secure than the pypi.org status quo, not less.

---

## 2. Rules box (read this first)

| ID | Rule |
|---|---|
| <a id="PKG-01"></a>**PKG-01** | **Every `mgf-*` package MUST be published to, and resolved from, the Codeberg PyPI registry** owned by `magogi-admin`. Publishing an `mgf-*` package to `pypi.org`, or resolving an `mgf-*` dependency from `pypi.org`, is non-conformant. (Third-party deps still resolve from public PyPI — §1.) |
| <a id="PKG-02"></a>**PKG-02** | **The canonical endpoints in §3 are the only ones used.** Upload → `…/api/packages/magogi-admin/pypi`; pip/uv index → `…/api/packages/magogi-admin/pypi/simple/`; verification → the Forgejo REST API `…/api/v1/packages/magogi-admin/pypi/<name>/<version>`. No hand-typed variants. |
| <a id="PKG-03"></a>**PKG-03** | **Auth is a Codeberg access token** on the `magogi-admin` account: **`write:package`** to publish or delete, **`read:package`** to install. Tokens MUST follow REL-04 discipline — never pasted into chat/shell history/unmanaged env; local in a `chmod 600` file or `~/.pypirc`; in CI as a platform secret. There is **no OIDC / Trusted Publisher** on Forgejo — do not reference one. |
| <a id="PKG-04"></a>**PKG-04** | **The publish flow is strictly ordered** (§5): gates green → build → `twine check` → **upload to Codeberg** → **verify via the REST API (PKG-05)** → annotated tag `v<version>` → push tag. The wheel/sdist uploaded MUST be the exact artifact that was built and checked in this run (no rebuild between check and upload). |
| <a id="PKG-05"></a>**PKG-05** | **Post-publish verification MUST query the Codeberg registry, never pypi.org.** A release is "live" only when `GET …/api/v1/packages/magogi-admin/pypi/<name>/<version>` returns `200` (or the package appears in the `…/pypi/simple/<name>/` index). Reading a local `dist/` wheel is never proof of publication (AG-09 still holds; the target is now Codeberg). |
| <a id="PKG-06"></a>**PKG-06** | **Consumers MUST configure the Codeberg index with explicit per-package pinning** so `mgf-*` resolve *only* from Codeberg and all other packages from the default public index. The bare `--extra-index-url` form (both indexes searched for every name) is permitted only for throwaway/manual installs, never committed config — it is a dependency-confusion surface (a public package shadowing an `mgf-*` name). The canonical shape is the uv named-index + `[tool.uv.sources]` mapping in §6. |
| <a id="PKG-07"></a>**PKG-07** | **A published version is immutable by policy.** The Forgejo registry *technically* allows deleting and re-uploading the same `name+version` (unlike pypi.org) — that capability is reserved **only** for botched-upload recovery within the same release session, and MUST be recorded (CHANGELOG note or release log). Never silently replace a version that consumers may have already resolved. |
| <a id="PKG-08"></a>**PKG-08** | **Package storage is finite — a retention policy is mandatory.** Codeberg applies a storage quota and package data counts against the `magogi-admin` account quota. **Verified 2026-06-03:** the account has a ~200 GiB assets/packages allowance (0 B used at cutover), so storage is comfortable today — but retention is still mandatory hygiene. Each package MUST retain at least its **current + previous minor** release; older pre-release/yanked/superseded versions MAY be pruned (DELETE per §8). If the allowance is ever approached, request an increase via `Codeberg-e.V./requests`. |
| <a id="PKG-09"></a>**PKG-09** | **CI publishing MUST be tag-driven, build-in-CI, and secret-token-authenticated.** A `v*` tag push triggers: build → check → upload-to-Codeberg → verify. The token is a Woodpecker secret (`codeberg_package_token`) scoped to tag events. No token is ever echoed; no publish runs on non-tag events. |
| <a id="PKG-10"></a>**PKG-10** | **Naming defers to NM-01..NM-04** — hyphenated distribution names (`mgf-common`), dotted imports (`mgf.common`). Forgejo normalizes names per PEP 503 exactly as PyPI does. The canonical "where to find it" URL for an `mgf-*` package becomes its Codeberg package page, not `pypi.org/project/<name>`. |
| <a id="PKG-11"></a>**PKG-11** | **Agent/federation version checks resolve against Codeberg (AG-09 amendment).** When a recommendation depends on a sibling's *published* version, the agent MUST verify against the Codeberg registry (PKG-05), not pypi.org and not a local `dist/`. |
| <a id="PKG-12"></a>**PKG-12** | **No residual pypi.org publish/distribution surface.** After cutover, the only permitted pypi.org references in the corpus + tooling are (a) links to **third-party** packages and (b) clearly-historical notes. No `mgf-*` publish command, token, index, or verify step may target pypi.org. **Enforced** by a `no-pypi-publish-surface` grep gate (§ Mechanical enforcement) — reference implementation: mgf-common `scripts/check_no_pypi_surface.py` + the `no-pypi-surface` CI step (the cornerstone template siblings copy). |

---

## 3. The registry — canonical endpoints

Owner: **`magogi-admin`** (a private Codeberg user account; the package namespace
path is identical to an org's). **Verified live on 2026-06-03:** the registry is
enabled, the upload + simple-index + REST endpoints below all answer authenticated
requests, and `mgf-standard` 0.1.5 round-tripped publish → REST 200 →
clean-venv install from the Codeberg index.

| Purpose | URL | Used by |
|---|---|---|
| **Upload (publish)** | `https://codeberg.org/api/packages/magogi-admin/pypi` | `twine upload --repository-url …`, `uv publish --publish-url …` |
| **Simple index (install)** | `https://codeberg.org/api/packages/magogi-admin/pypi/simple/` | `pip --index-url …`, uv `[[tool.uv.index]] url = …` |
| **Per-package index** | `https://codeberg.org/api/packages/magogi-admin/pypi/simple/<name>/` | resolution + the lightweight "is it there?" check |
| **REST: one version** | `https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<version>` | PKG-05 verify (`200` = live) |
| **REST: list / delete** | `https://codeberg.org/api/v1/packages/magogi-admin?type=pypi` (list) · `DELETE …/api/v1/packages/magogi-admin/pypi/<name>/<version>` (prune) | retention (§8), recovery (PKG-07) |

All endpoints require authentication (private account). Use `-H "Authorization:
token <CB_TOKEN>"` for the REST API, or HTTP basic `-u "$CB_USER:$CB_TOKEN"` for
the simple index / upload.

---

## 4. Authentication & tokens

**Token type.** A Codeberg **access token** (PAT) on the `magogi-admin` account,
created at *Codeberg → Settings → Applications → Generate New Token* with package
scope. Scopes:

- **`write:package`** — publish (upload) and delete. The release/CI token.
- **`read:package`** — install / list. The consumer token.

Create the most restrictive token that does the job (a consumer/CI-install token
gets `read:package` only). **Verified 2026-06-03:** the active org-wide token
authenticates the package API and `write:package` round-tripped a real publish.

**Storage (REL-04 discipline carries over):**

- **Local:** `~/.pypirc` (chmod 600), or a `chmod 600` token file read into an
  env var at call time. Example `~/.pypirc`:
  ```ini
  [distutils]
  index-servers = codeberg

  [codeberg]
  repository = https://codeberg.org/api/packages/magogi-admin/pypi
  username = <your-codeberg-username>
  password = <token-with-write:package>
  ```
  then `twine upload --repository codeberg dist/*`.
- **CI:** a Woodpecker secret `codeberg_package_token` (write:package), exposed
  only on tag events (§10). Never in plaintext in `.woodpecker.yml`.
- **Consumers:** a `read:package` token supplied via env (§6), never committed.

**No Trusted Publisher / OIDC.** Forgejo has no PyPI-style Trusted Publisher.
Every reference to `pypa/gh-action-pypi-publish`, OIDC, or
`docs.pypi.org/trusted-publishers/` is removed at cutover (§11). The
token-in-CI-secret model is the standing answer, not a fallback.

---

## 5. Publishing flow (the ordered ritual)

Replaces the pypi.org-targeted REL-06 / WORKFLOWS_PATTERNS §4.2 flow. Same
discipline, new index.

1. **Gates green** — the project's full gate set (lint/format/type/imports/tests
   + coverage) passes.
2. **Bump** version in every coupled surface (pyproject + `__version__` + lock +
   CHANGELOG) — unchanged from today.
3. **Build** — `uv build` (or `python -m build`) → `dist/<name>-<ver>*`.
4. **`twine check dist/<name>-<ver>*`** — metadata still validates (the check is
   index-agnostic; keep it).
5. **Upload to Codeberg:**
   ```bash
   TWINE_USERNAME="$CB_USER" TWINE_PASSWORD="$CB_TOKEN" \
     twine upload --repository-url https://codeberg.org/api/packages/magogi-admin/pypi \
     dist/<name>-<ver>*
   # or: uv publish --publish-url https://codeberg.org/api/packages/magogi-admin/pypi \
   #        -u "$CB_USER" -p "$CB_TOKEN" dist/<name>-<ver>*
   ```
6. **Verify (PKG-05)** — poll until live:
   ```bash
   until curl -fsS -H "Authorization: token $CB_TOKEN" \
     "https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<ver>" >/dev/null; do
     sleep 5; done
   echo "<name> <ver> live on Codeberg."
   ```
7. **Tag + push** — annotated `v<version>` (REL-01), `git push origin v<version>`.
8. **Fresh-install smoke** — in a clean venv, install the *just-published* wheel
   from the Codeberg index (§6) and assert `__version__` + a content probe.

Ordering note: unlike pypi.org (one-shot, irreversible), Codeberg lets you delete
+ re-upload, so a botched step 5 is recoverable (PKG-07) — but treat the version
as immutable the moment step 6 confirms it and any consumer could have resolved
it.

---

## 6. Consuming / installing (dependency-confusion-safe)

The canonical, committed consumer config — `mgf-*` resolve **only** from
Codeberg, everything else from public PyPI (PKG-06):

```toml
# pyproject.toml
[[tool.uv.index]]
name = "codeberg-mgf"
url = "https://codeberg.org/api/packages/magogi-admin/pypi/simple/"
explicit = true          # only packages mapped below resolve here

[tool.uv.sources]
mgf-common       = { index = "codeberg-mgf" }
mgf-standard = { index = "codeberg-mgf" }
# … one line per mgf-* dependency the project pins (see the two rules below)
```

> **Two rules make this work — both matter (learned the hard way at the 0.45 cutover):**
>
> 1. **`mgf-common`'s `mgf-standard` dependency is OPTIONAL (since
>    `mgf-common` 0.45.1).** The cornerstone *core* (exceptions / config /
>    logging / observability / vault) has **no `mgf-*` runtime dependency**, so a
>    consumer of the core resolves it with nothing from Codeberg transitively.
>    The standards corpus backs only the federation CLI (`standards` / `agents`
>    / `catch-up` / `sync-prompt`) and ships as the `mgf-common[standards]` extra.
>
> 2. **uv does NOT apply `[tool.uv.sources]` to *transitive* dependencies** —
>    only to a project's **direct** ones. A `mgf-*` package you reach only
>    transitively is fetched from public PyPI (where the cutover left the old
>    releases frozen), **not** from Codeberg, and the resolve breaks. So: **map
>    every `mgf-*` you need from Codeberg in `[tool.uv.sources]` AND declare it
>    as a direct dependency.** Concretely, a project that runs the federation
>    CLI must depend on `mgf-standard` **directly** (e.g. in its `dev`
>    extra) + map it — do NOT rely on `mgf-common[standards]`, which makes
>    `mgf-standard` transitive again and re-breaks the resolve. (This
>    transitive trap, with `mgf-standard` as a *hard* `mgf-common` dep, is
>    exactly what made `mgf-common 0.45.0` unresolvable until 0.45.1 demoted it.)

Auth without committing the token — uv reads per-index credentials from the
environment (`UV_INDEX_<NAME>_USERNAME` / `UV_INDEX_<NAME>_PASSWORD`, where
`<NAME>` is the index name upper-cased with `-`→`_`; smoke-verified during rollout):

```bash
export UV_INDEX_CODEBERG_MGF_USERNAME="$CB_USER"
export UV_INDEX_CODEBERG_MGF_PASSWORD="$CB_TOKEN"   # read:package
uv sync
```

`explicit = true` is the security property: a public package that happens to be
named like an `mgf-*` sibling can never shadow it, because only the names mapped
in `[tool.uv.sources]` are ever fetched from Codeberg, and those names are *never*
fetched from PyPI. This closes the dependency-confusion hole that a bare
`--extra-index-url` (both indexes searched for every name) would open.

**pip equivalent** (when uv isn't available) — keep PyPI primary, Codeberg extra,
and rely on the fact that only `mgf-*` exist on Codeberg:
```bash
pip install --extra-index-url "https://$CB_USER:$CB_TOKEN@codeberg.org/api/packages/magogi-admin/pypi/simple/" mgf-common
```
This is acceptable for `mgf-*` (which exist only on Codeberg) but is weaker than
the uv pinning; prefer uv for committed config.

**CI install:** inject `UV_INDEX_CODEBERG_MGF_USERNAME/PASSWORD` (or a netrc) from
a `read:package` CI secret; never bake the token into an image or lockfile.

---

## 7. Verify-after-publish (replaces WF-08)

The only authoritative "is `<name> <version>` published?" check is the Codeberg
registry, in increasing order of authority:

1. **Index presence:** `curl -fsS -u "$CB_USER:$CB_TOKEN"
   https://codeberg.org/api/packages/magogi-admin/pypi/simple/<name>/` lists the
   files (fast, good for "did it land?").
2. **REST version record:** `curl -fsS -H "Authorization: token $CB_TOKEN"
   https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<version>` →
   `200` + JSON metadata (the canonical PKG-05 gate).
3. **Fresh-venv install:** install from the Codeberg index (§6) and import — the
   end-to-end proof a consumer can actually get it.

No step here may reference `pypi.org/pypi/<name>/json` for an `mgf-*` package.

---

## 8. Immutability, re-upload & retention (replaces REL-05)

**PyPI was forever-immutable** ("once `twine upload` succeeds, frozen forever").
**Codeberg/Forgejo is not:** *"You cannot publish a package if a package of the
same name and version already exists. You must delete the existing package
first."* Two consequences:

- **Botched-upload recovery (better than PyPI).** If an upload landed wrong (bad
  metadata, missing file) and **no consumer has resolved it yet**, delete and
  re-upload the same version:
  ```bash
  curl -fsS -X DELETE -H "Authorization: token $CB_TOKEN" \
    https://codeberg.org/api/v1/packages/magogi-admin/pypi/<name>/<version>
  # then re-run the upload (§5 step 5)
  ```
  This is the PKG-07 escape hatch — recovery only, recorded, never a silent
  content swap of a version others may hold.

- **Retention against quota (new — PyPI never needed this).** Package storage
  counts against the `magogi-admin` account quota (Codeberg's Forgejo-v9 storage
  limits; verified ~200 GiB allowance, 0 used at cutover). Define and run a
  retention policy: keep current + previous minor of each `mgf-*`; prune older
  superseded/pre-release versions via the DELETE above. Track account package
  storage (`GET /api/v1/user/quota`); request an increase before it bites a release.

---

## 9. Naming (defers to NAMING.md NM-01..NM-04)

Hyphenated dist names, dotted imports — unchanged; Forgejo normalizes per PEP 503
just like PyPI. The only edit NAMING needs at cutover: the example "where it
lives" URL changes from `https://pypi.org/project/<name>/` to the Codeberg
package page, and NM-01's rationale loses its pypi.org-display-drift framing (the
hyphen convention still holds, registry-agnostic).

---

## 10. CI (Woodpecker) — the publish/verify steps rewritten

Replace the pypi.org publish + verify steps with Codeberg equivalents (sketch;
exact YAML produced during wiring):

```yaml
  - name: publish               # tag-only
    image: python:3.12-slim
    when: [ { event: tag, ref: refs/tags/v* } ]
    environment:
      TWINE_USERNAME: { from_secret: codeberg_package_user }
      TWINE_PASSWORD: { from_secret: codeberg_package_token }   # write:package
    commands:
      - pip install --quiet --upgrade pip build twine
      - python -m build
      - twine check dist/*
      - twine upload --non-interactive
          --repository-url https://codeberg.org/api/packages/magogi-admin/pypi dist/*

  - name: verify-codeberg       # tag-only — replaces verify-pypi
    image: python:3.12-slim
    when: [ { event: tag, ref: refs/tags/v* } ]
    environment:
      CB_TOKEN: { from_secret: codeberg_package_token }
    commands:
      - |
        V="${CI_COMMIT_TAG#v}"
        until curl -fsS -H "Authorization: token $CB_TOKEN" \
          "https://codeberg.org/api/v1/packages/magogi-admin/pypi/mgf-standard/$V" >/dev/null; do
          sleep 5; done
        echo "mgf-standard $V live on Codeberg."
```

The `tag-coherence` and `smoke` steps stay (smoke installs from the Codeberg
index per §6). One-time setup: add the `codeberg_package_user` +
`codeberg_package_token` Woodpecker secrets, tag-event-scoped.

---

## 11. Mapping — what this supersedes/amends (the wiring plan)

Applied as one DOC-18 change wave on finalization. Source: the repo-wide
pypi.org footprint audit.

| Surface | Today (pypi.org) | After (Codeberg) | Rule |
|---|---|---|---|
| `docs/standards/RELEASING.md` | "PyPI is the contract"; REL-04 `~/.pypirc`+Trusted-Publisher; REL-05 forever-immutable; REL-06 flow; `curl pypi.org/pypi/...` checks | Codeberg-is-the-contract; REL-04→PKG-03 token model; REL-05→PKG-07 (delete+re-push); REL-06→PKG-04 flow; REST verify | PKG-03/04/05/07 |
| `docs/standards/WORKFLOWS.md` | WF-03/04/06/08 PyPI publish + OIDC + poll pypi.org | retarget to Codeberg registry; drop OIDC | PKG-05/09 |
| `docs/standards/WORKFLOWS_PATTERNS.md` §4.2/4.3 | `twine upload` to PyPI; `from_secret: pypi_token`; "one-time pypi_token setup"; "Codeberg adds Trusted Publisher" line | Codeberg upload; `codeberg_package_token`; token-creation steps; drop the Trusted-Publisher aspiration | PKG-03/09 |
| `.woodpecker.yml` | publish (twine→PyPI, `pypi_token`) + verify-pypi comment | publish→Codeberg, `codeberg_package_token`; verify-codeberg step (§10) | PKG-09 |
| `docs/recipes/cohort-release-wave.md` | `uv publish` (PyPI token file); `curl pypi.org/.../json`; 429 first-publish cooldown | `uv publish --publish-url` Codeberg; REST verify; Codeberg quota/limits notes | PKG-04/05/08 |
| `docs/recipes/sibling_rename.md` | "publish under new name on PyPI"; "reserve old name on PyPI" | Codeberg publish; squatting risk differs on a private org (likely drop the reserve step) | PKG-01/10 |
| `docs/recipes/federation-sync-prompt.md` | "PyPI is the contract for latest released"; "curl PyPI" | Codeberg registry is the contract; REST/index check | PKG-05/11 |
| `docs/standards/AGENTS.md` AG-09 | verify against PyPI | verify against Codeberg registry | PKG-11 |
| `docs/standards/NAMING.md` NM-01 | `pypi.org/project/<name>` URL + display-drift framing | Codeberg package URL; registry-agnostic rationale | PKG-10 |
| `docs/standards/README.md` Index of MUSTs | WF-03/06/08, REL-01/03/04 rows naming PyPI | reworded for Codeberg + new PKG-* rows; `CHANGELOG.md` entry | DOC-18 |
| `scripts/gen_mgf_libraries.py` | comments "prefer a PyPI window"; "PyPI is the contract" | "Codeberg registry window/contract" (comments only; logic is registry-agnostic) | PKG-11 |
| `docs/standards/SECURITY_STANDARD.md` / `SECURITY.md` | "PyPI auto-yanks"; Trusted-Publisher | Codeberg delete/yank model; drop OIDC | PKG-07 |
| `docs/standards/DEPLOYMENT.md` DEP-03 | "no PyPI lookups at deploy" | "no registry lookups at deploy" (Codeberg too) | — |
| `docs/standards/COMMON_COMPONENTS.md`, `API_DESIGN.md`, `DESIGN_PRINCIPLES.md` | `pypi.org/project/...` links for `mgf-*` and 3rd-party | `mgf-*` → Codeberg; **3rd-party links stay** (apispec/qasync) | PKG-12 |
| `docs/standards/DOCUMENTATION.md` | "published to PyPI as a library" definitions | "published to the Codeberg registry" | PKG-01 |
| cookiecutter template `pyproject.toml` / docs / CI | (PyPI-ready, twine) | Codeberg index + uv sources scaffolding (§6) + Codeberg publish CI | PKG-06/09 |
| `.claude/settings.local.json` | `WebFetch(domain:pypi.org)` | add `codeberg.org`; keep pypi.org (3rd-party lookups) | — |
| conformance / adoption templates | release/publish sections | Codeberg publish + consume sections | PKG-* |

**New standards doc** `docs/standards/PACKAGE_DISTRIBUTION.md` (this file,
finalized) + README index rows for PKG-01..PKG-12 + a `CHANGELOG.md` entry +
`mgf-fed standards`/`agents` corpus regen at the next release.

---

## 12. Migration / cutover plan (one-time)

1. **Confirm the registry** is enabled for `magogi-admin`; if quota is tight,
   file the exception request first (PKG-08).
2. **Mint tokens:** one `write:package` (release/CI), one `read:package`
   (consumers/CI-install). Store per PKG-03. Add the Woodpecker secrets.
3. **Seed the registry:** publish the *current* released version of every `mgf-*`
   package to Codeberg (so consumers can resolve what they already pin), starting
   with the leaf-of-leaves `mgf-standard`, then `mgf-common`, then siblings.
4. **Switch consumers:** add the §6 uv index + sources to every consumer +
   sibling; verify `uv sync` resolves `mgf-*` from Codeberg.
5. **Cut over CI:** land the §10 Woodpecker changes per repo.
6. **Apply the §11 wiring wave** in `mgf-standard` (this doc → binding;
   RELEASING/WORKFLOWS/recipes/README updated; corpus regen).
7. **Decommission pypi.org publishing:** stop publishing `mgf-*` to pypi.org;
   PKG-12 grep gate green. (Decide the fate of existing pypi.org `mgf-*`
   releases — §13.)

---

## 13. Decisions (resolved 2026-06-03)

| Decision | Resolution |
|---|---|
| **Transition** — hard-drop vs dual-publish | **Hard-drop.** No dual-publish cycle; at cutover pypi.org stops being an `mgf-*` publish target outright. |
| **Existing pypi.org `mgf-*` releases** | **Leave frozen** at cutover; a **follow-up task** tracks deleting them from pypi.org afterward. (PyPI deletion is per-release via the project web UI — there is no bulk-delete API; the deleted version numbers stay reserved and cannot be reused.) |
| **Token model** — org-wide vs per-repo | **One `magogi-admin` org-wide token** to start: `write:package` for release/CI, the same org token (read use) for consumers. Revisit per-repo least-privilege later. |
| **Doc home + rule prefix** | **Confirmed:** `docs/standards/PACKAGE_DISTRIBUTION.md`, rules `PKG-01..PKG-12`. |

**Defaults applied unless overridden:**
- **Retention (PKG-08):** keep current + previous minor of each package; prune older superseded/pre-release versions.
- **Consumer-auth distribution:** the org-wide token (read use) placed per-repo at `my_stuff/tokens/codeberg_package_token.txt` (chmod 600) and as each repo's CI secret; injected via `UV_INDEX_CODEBERG_MGF_USERNAME` / `UV_INDEX_CODEBERG_MGF_PASSWORD`.
- **Seeding repos on feature branches (vm-*):** seed from each repo's latest release tag, not the in-flight branch; log any repo that can't build cleanly.

**Confirmed live (2026-06-03 cutover — `mgf-standard` 0.1.5 round-trip + 14-sibling seed):** the registry is enabled for `magogi-admin`, the `write:package` / `read:package` token scopes authenticate the package API, the uv per-index env-var names (`UV_INDEX_CODEBERG_MGF_USERNAME` / `_PASSWORD`) resolve credentials, and the ~200 GiB quota was read end-to-end. No open empirical questions remain.

---

## Mechanical enforcement

| Rule | Enforcement |
|---|---|
| PKG-01 / PKG-12 | **✅ Live** (mgf-common `scripts/check_no_pypi_surface.py` + the `no-pypi-surface` CI step, after `lint`; siblings copy the cornerstone). A `no-pypi-publish-surface` grep gate: fails CI if `pypi.org` appears in a publish command, token ref, index URL, or verify step for an `mgf-*` package — allowlisting third-party `--extra-index-url`, comments, and historical notes. |
| PKG-02 / PKG-05 | The CI `verify-codeberg` step (§10) — a release is not green until the REST API returns 200. |
| PKG-03 / PKG-09 | Token is a tag-scoped CI secret; a lint check that `.woodpecker.yml` has no plaintext token and publish is `event: tag` only. |
| PKG-06 | A consumer-side check that committed config uses the named-index + `[tool.uv.sources]` mapping (not a bare extra-index-url). |
| PKG-08 | A scheduled job reporting org package storage vs quota; retention prune script. |

---

_Finalized 2026-06-03 — L1-binding. Live-registry facts verified end-to-end (mgf-standard 0.1.5 round-trip)._
