# Changelog

All notable changes to `mgf-django` are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
the project follows [SemVer](https://semver.org/spec/v2.0.0.html). Per
the 0.x window ([SemVer 0.x](https://semver.org/#spec-item-4) and rule
**AP-03** from `mgf-standard/docs/standards/API_DESIGN.md`), MINOR
releases MAY break the public API. Pin tightly:

```toml
mgf-django = ">=0.X.0,<0.Y"   # one minor at a time
```

The release-engineering discipline that produces this changelog is in
[`mgf-standard/docs/standards/RELEASING.md`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/RELEASING.md).

---

## [0.4.10] - 2026-07-14

### Changed
- **Released what was stranded on `main`.** `main` had moved past `v0.4.9` while still
  declaring `0.4.9`, so the *published* `0.4.9` was different code under the same version
  number. Two things consumers never received:
  - the **Apache-2.0 relicence** (landed on main 2026-07-12; every published wheel still
    carried `License: MIT`, with no `NOTICE` — a relicence is not real until it is released);
  - the **widened `mgf-common` pin** (`<1.0`, was `<0.50`). The stale published cap
    backtracked the whole federation's co-install to `mgf-common` 0.49.2.

  `REL-13` could not see this: it compares declared version to *registry* version, and both
  said `0.4.9`. The drift was main-vs-tag content. `mgf/tools/verify_release_currency.py`
  now catches the class — *a fix that isn't released isn't shipped.*

## [0.4.9] - 2026-06-26

### Changed
- Release the **mgf-common 0.49** pin (`<0.50`) onto devpi so the full
  fleet co-installs at the 0.49 cornerstone (devpi backfill; no API change).

## [0.4.8] - 2026-06-16

### Changed
- Widened the `mgf-common` pin to `<0.48` — mgf-common 0.47 is the pure-library release (the end-user CLI moved to `mgf-fed[cli]`); the importable API is unchanged.
- Regenerated `.woodpecker` so the supervisor lane calls `mtest` (the moved `mgf-common test` frontend is gone).

## [Unreleased]

Nothing yet.

---


## [0.4.7] — 2026-06-08 — depth-audit close-out (typed errors + public API)

### Added
- `mgf.django.__version__` (AP-13); CI tag-coherence asserts it.
- `PUBLIC_API.md` (tiers + exceptions + banner) + `PUBLIC_API.json` + generator +
  CI pins (AP-07/10/11, DOC-01/08/09).
- `mgf.django._errors`: typed `ThrottleConfigError` / `DjangoSettingTypeError`
  (subclass `mgf.common ConfigError`) with machine-readable fields + actionable
  hints (EH-01/08/09); docs/reference/EVENTS.md (LG-15).
- Tag-driven Codeberg publish pipeline + import-linter contract (earlier batches).

### Changed
- `TokenBucket.try_consume`/`reset` now carry docstrings (DP-07).
- Declined (CFM-04 §3): AP-14 (consumer-fixture import-linter).

## [0.4.6] — 2026-06-06 — federation cohort: mgf-common 0.46 walk

Walk the `mgf-common` pin ceiling `<0.46 → <0.47` to accept the
library-API-neutral `mgf-common 0.46.0` (CLI-only release: new
`federation`/`design` commands + a `doctor --standards` gate; no library
surface change).

---

## [0.4.5] — 2026-06-04 — federation cohort: mgf-common 0.45 walk

PATCH: pin widened to `mgf-common>=0.41,<0.46` to accept mgf-common 0.45
(Codeberg cutover — PKG-01..12). `mgf-*` resolve from the `codeberg-mgf`
index (PKG-06). No API change.

---

## [0.4.4] — 2026-06-01 — federation cohort: adopt mgf-common 0.44

PATCH: pin widened to `mgf-common>=0.41,<0.45` to accept the mgf-common 0.44 line
(which extracted the standards corpus + federation contract to the
`mgf-standard` sibling; pulled in transitively). `FEDERATION.md` stub +
`.claude/commands/sync.md` repointed at `mgf-standard`. No API change.

---

## [0.4.3] — 2026-05-28 — federation pin walk (mgf-common 0.42.x)

Federation cohort wave: mgf-django now accepts **mgf-common 0.42.x** (pin
widened to `>=0.41,<0.43`). mgf-common 0.42.0 is an additive release
(unified-logging waves); no source impact here.

---

## [0.4.2] — 2026-05-26 — federation pin walks + first PyPI publish

Federation cohort walk: mgf-django now accepts mgf-common 0.41.x.
ALSO mgf-django's first PyPI publish — the federation's last
sibling to land on PyPI (per Q-4 backlog). 0.4.1 existed on
Codeberg only.

### Changed

- `pyproject.toml`: `mgf-common>=0.40,<0.41` → `mgf-common>=0.40,<0.42`.
- `pyproject.toml`: `mgf-test-supervisor @ git+ssh://...` →
  `mgf-test-supervisor>=0.1.3,<0.2`.
- `pyproject.toml`: removed `[tool.hatch.metadata]
  allow-direct-references = true` block.

### Source-level changes

None — mgf-django's source has zero usage of mgf-common 0.41
deletions. Verified by grep.

---

## [0.4.1] — 2026-05-18

**Federation sync v2.10 + first PyPI publish.**

This release is the FIRST PyPI publish for mgf-django (closes B-1
from `mgf-common/docs/inprogress/pre_release_check.md`). Versions
0.1.0 through 0.4.0 existed in-tree and on Codeberg but were never
pushed to PyPI. 0.4.1 is the first version available via `pip install`.

### Added
- `STARTHERE.md` + `CONTRIBUTING.md` (DOC-01 sibling MUSTs).
- `docs/CLAUDE.md` + `.claude/settings.json` (managed by `mgf-common catch-up`).

### Changed
- `mgf-common` pin: `>=0.36,<0.37` → `>=0.37,<0.38`.

### Federation
- Aligned with mgf-common v0.38.0 contract (DOC-13/14/15/16/17). Catch-up clean (one ⚠️ closed-box leak retained for separate cleanup).

---

## [0.4.0] — 2026-05-18

PyPI: <https://pypi.org/project/mgf-django/0.4.0/> · Tag: `v0.4.0`

> **Wave 2 sibling release — D-1 brute-force throttle middleware
> ships.** Skips v0.3.0 to align MINOR cadence with the
> mgf-common 0.36 / mgf-sqlalchemy 0.4 / mgf-fastapi 0.6 batch.
> Pin bump: mgf-common ≥0.33 → ≥0.36.

### Added — `ThrottleMiddleware` + `TokenBucket` (D-1)

Closes the D-1 federation row. inthewords (auth-heavy Django
app) is the immediate adopter. Mirrors the F-1 shape from
mgf-fastapi 0.6.0 — federation consistency.

Three public names (all `experimental` per AP-11):

- **`TokenBucket(*, capacity, refill_per_second)`** — pure-
  Python token-bucket primitive (same shape as
  `mgf.fastapi.TokenBucket`).
- **`ThrottleMiddleware`** — Django sync middleware. Default
  config: 5 attempts per IP per `/login/` / `/password-reset/`
  / `/api/auth/` paths, refill ~1/min (django-axes-shaped
  brute-force protection). Returns 429 + `Retry-After` JSON
  body on denial. `async_capable = False`.
- **`SENSITIVE_PATHS_DEFAULT`** — canonical tuple of paths
  throttled by default (login, allauth login, password-reset,
  DRF auth, JWT token endpoints).

Configure via Django settings::

    MGF_DJANGO_THROTTLE = {
        "bucket_capacity": 5,
        "bucket_refill_per_second": 1.0 / 60.0,
        "sensitive_paths": ("/login/", "/api/auth/"),
    }

15 new unit tests pin token-bucket correctness + middleware
integration (non-sensitive bypass, under-limit, 429-when-
exhausted, per-IP isolation, missing-REMOTE_ADDR bypass,
custom-paths override).

### Changed — pin bump

- **`mgf-common>=0.33,<0.34` → `mgf-common>=0.36,<0.37`**.

### Verification

- pytest tests/: 54 passed (39 + 15 new).
- ruff: clean.
- mypy --strict: clean.

---

## [0.2.0] — 2026-05-15

PyPI: <https://pypi.org/project/mgf-django/0.2.0/> · Tag: `v0.2.0`

> **Maiden voyage** — extracted from `mgf-common` v0.30.0 per the
> federation split plan ([mgf-common/docs/release/federation_roadmap.md](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/docs/release/federation_roadmap.md)).
> Cutover guide: [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md).
>
> This is the **fourth and final framework-adapter sibling extraction**
> in the v0.28-v0.31 ASPIRE-06 staged split (mgf-fastapi v0.28,
> mgf-http v0.29, mgf-sqlalchemy + mgf-alembic + mgf-fastapi v0.2 v0.30,
> mgf-django v0.31). The mgf-common federation leaf now ships pure-Python
> infrastructure only; framework-coupled adapters all live in their
> respective siblings.
>
> **Authored against mgf-common v0.31 (the extraction release);
> first-published-paired with mgf-common v0.33** (the dep pin was
> walked forward in the v0.32 stabilization window before the
> maiden PyPI publish — see `chore(deps): walk mgf-common pin to
> >=0.33,<0.34`). The pre-publish source is clean of the AP-12
> removals (`configure`, `make_settings_config`, `EnvironmentError`
> alias).
>
> **Why v0.2.0 not v0.1.0?** v0.1.0 was prepped as the maiden
> release but never published; the v2.6 + v2.7 standards bumps
> in `mgf-common` (project-shape taxonomy + `DEPLOYMENT.md`)
> landed before the publish, and folding the compliance files
> (SECURITY.md, docs/claude/CLAUDE.md, docs/standards/README.md
> pointer) into the maiden release was cleaner than shipping
> v0.1.0 → v0.2.0 in two adjacent commits. v0.1.0 was never on
> PyPI; v0.2.0 is the first published shape.

### Added — standards-compliance shape (v2.6 + v2.7)

- **`SECURITY.md`** at repo root — vulnerability-reporting
  policy per **SC-12** + the v2.6 universal-four root docs.
  Supported-versions table, private disclosure path, 90-day
  embargo default, scope/out-of-scope.
- **`docs/claude/CLAUDE.md`** — AI-agent dense-lookup entry
  point per **DOC-07**. File map of the five public names,
  cross-cutting patterns, build + release recipe, common
  pitfalls.
- **`docs/standards/README.md`** — pointer-only stub per
  **DOC-02** §2.0.4 Shape A. Cross-project standards live in
  `mgf-standard/docs/standards/`; mgf-django is a federation
  sibling and inherits them. Conformance target: L2.

### Added

- **`mgf.django.MgfCommonConfig`** — Django `AppConfig` that calls
  `mgf.common.bootstrap()` exactly once per process during
  `ready()`. Idempotent (multi-call protected via module-level
  guard); `atexit` hook unwinds the bootstrap stack on shutdown so
  OTel flushes, log handlers detach, crash hooks reset.
- **`mgf.django.MgfRequestIdMiddleware`** — sync-mode Django
  middleware that injects/forwards `X-Request-Id` per request,
  shares the same contextvar as `mgf.fastapi.RequestIdMiddleware`
  for cross-framework end-to-end correlation. Sets
  `request.mgf_request_id` for view access; echoes the id in the
  response.
- **`mgf.django.MgfContextMiddleware`** — sync-mode Django
  middleware that exposes the active `AppContext` on
  `request.mgf_context` for views and downstream middleware. Falls
  back to `None` if bootstrap hasn't run (defensive against
  pre-bootstrap test execution).
- **`mgf.django.JsonFormatter`** — no-arg wrapper around
  `mgf.common.observability.JsonFormatter` so Django's
  `LOGGING['formatters']` `'()': '...'` factory syntax works
  without consumers having to construct a `Redactor` first. Same
  redaction + OTel correlation as the underlying formatter.
- **`mgf.django.DjangoSettingsBridge`** — read-only
  `BaseAppSettings`-compatible view over `django.conf.settings`.
  Exposes a curated subset of keys (`debug`, `allowed_hosts`,
  `installed_apps`, `time_zone`, `use_tz`, `language_code`,
  `secret_key_present`, `mgf_app_name`, `mgf_app_version`) so
  `mgf-common explain` works for Django apps. `SECRET_KEY` itself
  is NEVER carried — only a presence boolean.

### Changed (BREAKING vs the pre-extraction `mgf.common.django` shape)

- **App label `"mgf_common"` → `"mgf_django"`** — Django app labels
  can't contain dots, so the package previously chose `mgf_common`
  as a stable label. At the v0.31 sibling rename, the label moves
  to `mgf_django` to match the new module path. **Consumer config
  hardcoding the old `"mgf_common"` label MUST update** — typical
  references are in `apps.get_app_config("mgf_common")` calls or
  migration table-prefix configs.

### Engineering

- 18 unit tests carried over from
  `mgf-common/tests/unit/django/` — every pre-extraction failure
  mode stays green at parity. Coverage threshold ≥80% (matches
  mgf-common's standard).
- Imports rewrite from `mgf.common.django.*` to `mgf.django.*`.
  No private-import leaks; the sibling reaches into mgf-common
  only through public names + the `mgf.common._request_id`
  contextvar (matching the existing pattern in
  `mgf.fastapi._request_id`).
- `mgf-common>=0.33,<0.34` runtime dependency (walked forward
  from the >=0.31 pin under which v0.1 was authored). AP-03 0.x
  window pin discipline applies — bump in lock-step with
  mgf-common's next minor.
- PEP 420 namespace package (no top-level `mgf/__init__.py`); the
  wheel ships `src/mgf/` as-is so `mgf.django`, `mgf.fastapi`,
  `mgf.fastapi.db`, `mgf.http`, `mgf.sqlalchemy`, `mgf.alembic`,
  and `mgf.common` coexist as siblings.
- `apps.py` at the package root (in addition to the underscore-
  prefixed `_apps.py` private impl) so Django's `INSTALLED_APPS`
  discovery can find the `AppConfig` subclass — Django's
  app-registry walks for `apps.py` files in installed packages.
