# Changelog

All notable changes to `mgf-fastapi` 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-fastapi = ">=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.6.12] - 2026-06-26 — L3 uplift + mgf-common 0.49

### Changed
- Delegate the Svix/Clerk webhook HMAC dance to the shared `mgf.common.webhooks.verify_svix_hmac` primitive (mgf-common 0.49); drop the
  duplicated crypto in `webhooks/_verifier.py`. Widen the pin to `<0.50`.

### Security
- **SC-08:** `verify_request` now enforces a configurable `max_body_bytes` cap
  (default 1 MiB) on the webhook body, rejecting over-cap requests *before*
  buffering or HMAC — first on a declared `Content-Length`, then mid-stream —
  to close the pre-auth memory/CPU DoS.
- Added a `## Threat model` section to `docs/ARCHITECTURE.md` (SC §12) covering
  the webhook-HMAC / bearer / IP-allowlist boundaries.

### Changed
- **LG-15:** every webhook rejection now emits a single
  `mgf.fastapi.webhook.verification_failed` audit log (WARNING, `reason=…`,
  `audit=True`) so forged/replayed/oversized webhooks leave a trail instead of a
  silent 401. New `HmacVerificationError` reasons: `body_too_large`,
  `body_consumed`.
- **LG-15:** the engine-dispose event is renamed `mgf_fastapi.db.dispose_failed`
  → `mgf.fastapi.db.dispose_failed` (one-time alert-target rename); the full
  event catalogue (`docs/reference/EVENTS.md`) now registers every emitted
  `event` (webhook / ip_allowlist / auth / db rows).
- **AP-14:** repointed the `db.py` docstring + two test imports off
  `mgf.common._*` private modules onto the public `mgf.common` surface, and added
  an import-linter contract banning `mgf.fastapi → mgf.common._*`.

### Docs
- **DOC-12 / DOC-17:** swept the dead `magogi-admin/mgf-doc-standard` slug →
  `magogi-admin/mgf-standard` repo-wide; fixed the mislabeled FEDERATION.md link.
- **CFM-02 / OP-04:** softened the README conformance headline to "targets L2"
  (below L2 today, gaps tracked in the ledger) and synced the ledger version to
  0.6.11.
- **PF-06:** annotated the hot-path rows in `PUBLIC_API.md`
  (`HmacWebhookVerifier.verify`, `IpAllowlist`, `TokenBucket`) with time/memory
  cost classes.


## [0.6.11] - 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).

## [0.6.10] — 2026-06-07 — depth-audit gap closing (logging hardening)

- **LG-09 / LG-16 / SC-17** — `IpAllowlist` denials + `bearer_session` auth
  records now carry structured `extra={}` (event, reason, client_host, audit
  marker) instead of burying facts in the message string. Messages unchanged.
- **LG-11** — `setup_db` emits stable `operation=db.setup` start/complete/teardown
  records (a storage-boundary crossing).
- **LG-17** — verified: exception-logging sites set `exc_info=True` so mgf-common's
  formatter computes the error fingerprint.

## [0.6.9] — 2026-06-07 — depth-audit gap closing (code: errors + reliability)

- **EH-08** — `HmacVerificationError.reason` machine-readable failure mode
  (`missing_header` / `bad_timestamp` / `replay_window` / `signature_mismatch`);
  callers can branch without parsing the human message. Messages unchanged.
- **EH-10** — `LogBroadcaster.publish_record` docstring now states the literal
  "Never raises." (the §7 hard MUST; the never-raise behavior + test already held).
- **DP-11** — the log broadcaster now retains strong refs to in-flight publish
  tasks (`add_done_callback` discard) so the event loop can't GC them mid-publish
  and silently drop a record.
- **SC-07** — the `run_test_app` httpx client is constructed with `timeout=30.0`.

## [0.6.8] — 2026-06-07 — depth-audit gap closing (batch A+B)

Follow-up to the 2026-06-07 depth audit. Closes the CI-publish, import-linter,
and public-API/docs clusters.

- **AP-13** — `mgf.fastapi.__version__` added (mirrors pyproject); CI `tag-coherence` now asserts it.
- **AG-16** — `mgf-common[standards]` added to the `[dev]` extra (corpus resolvable to agents at dev-time; NOT forced on production consumers).
- **DOC-08 / REL-03** — `PUBLIC_API.json` + generator (`tools/generate_public_api_json.py`) + CI pin test.
- **AP-10 / AP-11** — `PUBLIC_API.md` now documents the `logs` + rate-limit names (with tiers); pinned md↔`__all__` test.
- **PKG-12** — README badges/links retargeted off pypi.org → Codeberg.
- (Batch A, prior commits) import-linter contracts + `lint-imports`/OP-03 in CI; the tag-driven Codeberg publish pipeline.

Ledger: 108 ✅ / 43 ⏳ / 67 N/A.

## [Unreleased]

Nothing yet.

---

## [0.6.7] — 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.6.6] — 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.6.5] — 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.6.4] — 2026-05-28 — per-app web log surface + federation cohort publish (mgf-common 0.42.x)

Ships the unified-logging Wave 3 web surface (+ the Wave 8 F2 stats wiring),
accepts **mgf-common 0.42.x** (pin widened to `>=0.41,<0.43`), and publishes to
PyPI (carrying the previously-unpublished 0.6.3 work).

### Added — `mgf.fastapi.logs` (unified-logging Wave 3)

- **Per-app web log surface** — the web half of "access any Magogi app's logs
  at runtime" (mgf-common unified-logging design, decision D3: each app exposes
  its own auth-gated endpoint, no central UI). New module `mgf.fastapi.logs`,
  behind the **`[livepush]`** extra:
  - **`create_logs_router(sse_service, *, auth, ...)`** → an `APIRouter` with
    `GET /logs/info` (the `describe_logging()` introspection as JSON: where the
    logs live + effective levels + active sinks) and `GET /logs/stream` (a live
    SSE stream of canonical `LogRecord` JSON, one `data:` frame per record).
    Both routes are auth-gated by the supplied dependency (`bearer_session()` or
    `IpAllowlist(...)`) — auth is required by design (the surface exposes logs
    off-box).
  - **`LogBroadcaster`** — bridges the stdlib logging pipeline to a livepush
    broker: each emitted record becomes a **redacted** canonical `LogRecord`
    line published to the stream channel. Redaction is mandatory (the stream
    leaves the process); publishing is **fail-open** (a slow/down broker never
    blocks or crashes the app) and re-entrancy-guarded (never feeds itself).
  - **`build_log_stream_service(broker, redis, ...)`** — convenience to build
    the livepush `SSEStreamService` (broker + connection-slot cap).
  - `GET /logs/info` now also returns a **`stats`** key — the process-wide
    `LogStats` snapshot (`mgf.common.observability.current_log_stats`, mgf-common
    0.42+; `null` when stats aren't enabled or on 0.41). Override via
    `create_logs_router(..., stats_provider=…)`. (unified-logging review F2.)
- Composes **mgf-livepush** (SSE + broker + slot-cap) and **mgf-auth**
  (verifier) — both via existing seams; no new hard dependency for consumers
  who don't mount the router.

---

## [0.6.3] — 2026-05-28 — reliability tests (async-cancellation + wire contract)

### Added

- Tests pinning that `ExceptionTranslationMiddleware` lets
  `asyncio.CancelledError` propagate (it is a `BaseException`, outside the
  `except AppError` catch) rather than swallowing it into a 500, plus
  `@contract` tests for the translated-error JSON body + RequestId header.
- `USERS.md` (DOC-16) — present.

### Notes

- Test only; no public API change.

---

## [0.6.2] — 2026-05-26 — federation pin walks (mgf-common 0.41.x + mgf-test-supervisor PyPI)

Federation cohort walk: mgf-fastapi now accepts mgf-common 0.41.x.
Previously pinned `mgf-common>=0.40,<0.41`, which is what made
PAPER-84 (cross-sibling consistency check in mgf-common 0.41.2)
correctly REFUSE bumps for consumers depending on both mgf-fastapi
and mgf-common. This release widens mgf-fastapi's pin to `<0.42`
so the federation cohort gate opens for downstream consumers.

### 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`. mgf-test-supervisor v0.1.3
  is now on PyPI (was Codeberg-only at 0.1.0/0.1.1/0.1.2).
- `pyproject.toml`: removed `[tool.hatch.metadata]
  allow-direct-references = true` block.

### Source-level changes

None — mgf-fastapi's source has zero usage of mgf-common 0.41
deletions. Verified by grep before the bump.

---

## [0.6.1] — 2026-05-18

**Federation sync v2.10** — docs + tooling refresh; no library surface changes.

### 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.

---

## [0.6.0] — 2026-05-18

PyPI: <https://pypi.org/project/mgf-fastapi/0.6.0/> · Tag: `v0.6.0`

> **Wave 2 sibling release — F-1 rate-limit middleware ships
> as a stdlib-only token-bucket primitive + Starlette/FastAPI
> middleware.** Skips v0.5.0 to align MINOR cadence with the
> mgf-common 0.36 / mgf-sqlalchemy 0.4 batch.

### Added — `RateLimitMiddleware` + `TokenBucket` (F-1)

Closes the F-1 federation row. PlasmaMapper is the immediate
adopter (the inthewords equivalent ships in mgf-django 0.4.0
as `D-1`).

Two public names (both `experimental` tier per AP-11):

- **`TokenBucket(*, capacity, refill_per_second)`** — pure-
  Python in-process token-bucket. Thread-safe; one bucket per
  key. `.try_consume(key) -> (allowed: bool, retry_after: float)`
  is the primitive. Multi-replica deployments wire a Redis-
  backed impl with the same shape externally.
- **`RateLimitMiddleware`** — Starlette/FastAPI middleware
  wrapping a `TokenBucket`. Accepts a `key(request) -> str | None`
  callable. Returns 429 with `Retry-After` (RFC 7231 integer-
  seconds) + JSON body `{"error": "rate-limit-exceeded",
  "retry_after": N}`. Returning `None` from the key function
  disables limiting per-request.

15 new unit tests cover algorithmic correctness (burst,
refill timing, per-key isolation, cap-at-capacity, reset
ergonomics) + middleware integration (passes-through,
429-when-exhausted, Retry-After header shape, None-key
bypass, multi-key isolation).

### Changed — pin bumps

- **`mgf-common>=0.33,<0.34` → `mgf-common>=0.36,<0.37`**
  (Wave 1 catch-up).
- **`mgf-sqlalchemy>=0.1,<0.2` → `mgf-sqlalchemy>=0.4,<0.5`**
  in the `[sqlalchemy]` extra (catches the new audit-trail +
  UUID v7 work).

### Verification

- pytest (excluding slow `lifespan_startup`): 152 passed
  (137 + 15 new).
- ruff: clean.
- mypy --strict: clean.

---

## [0.4.0] — 2026-05-15

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

> **v2.6 + v2.7 standards-compliance release.** The
> `mgf-common` documentation standard bumped to v2.6
> (project-shape taxonomy + LICENSE in the universal four)
> and v2.7 (DEPLOYMENT.md cloud-deployment standard). This
> release brings `mgf-fastapi` to the federation-sibling
> compliant shape: SECURITY.md + docs/claude/CLAUDE.md +
> docs/standards/README.md pointer-only stub. No code
> changes; documentation-only.

### 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 (`0.4.x` is the supported MINOR),
  private disclosure path (email or Codeberg security tab),
  90-day embargo default, scope (HMAC verifier + bearer
  session + IP allowlist + exception translation middleware
  + test API server). Out-of-scope notes for `fastapi` /
  `starlette` / `pydantic` (upstream).
- **`docs/claude/CLAUDE.md`** — AI-agent dense-lookup entry
  point per **DOC-07**. File map of every public name + the
  PAPER-24..29 paper provenance; cross-cutting patterns
  (request-id contextvar sharing with mgf-django, HMAC
  replay window, IP-allowlist proxy-trust, IdP-outage
  handling); 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-fastapi is a federation
  sibling and inherits them. Conformance target: L2.

### Engineering

- No code changes. This release is documentation-only; the
  `src/mgf/fastapi/` tree is byte-identical to v0.3.1.
- `mgf-common>=0.33,<0.34` runtime dependency unchanged from
  v0.3.1.

---

## [0.3.1] — 2026-05-11

PyPI: <https://pypi.org/project/mgf-fastapi/0.3.1/> · Tag: `v0.3.1`

> **Pin-walk patch release — no source change.** Walks the
> `mgf-common` pin forward to `>=0.33,<0.34`, the AP-12
> deprecation-removal release. `mgf-fastapi` source is clean of the
> three removed names (`mgf.common.configure`,
> `mgf.common.config.make_settings_config`,
> `mgf.common.exceptions.EnvironmentError`), so no code change is
> needed — only the dependency declaration moves.

### Changed

- **Dependency pin** — `mgf-common>=0.31,<0.32` → `mgf-common>=0.33,<0.34`.
  Lets consumers install `mgf-fastapi` alongside the current
  `mgf-common` line.

---

## [0.3.0] — 2026-05-10

PyPI: <https://pypi.org/project/mgf-fastapi/0.3.0/> · Tag: `v0.3.0`

> **Closes FEEDBACK PAPER-29** — `bearer_session` + `AuthVerifier`
> Protocol for "Authorization: Bearer → typed session principal" routes.
> Filed by PlasmaMapper after re-implementing the same ~30 LOC dependency
> on its own. Cutover guide: [`docs/cutover/v0.3.0.md`](docs/cutover/v0.3.0.md).

### Added

- **`mgf.fastapi.security.bearer_session(verifier_attr, *, cookie_name=None)`** —
  factory that returns a FastAPI dependency resolving
  `Authorization: Bearer <token>` (and, optionally, a session cookie for
  browser clients) to a typed principal. Reads an `AuthVerifier` from
  `app.state.<verifier_attr>` (`auth_provider` by default), calls
  `await verifier.verify_session(token)`, returns the principal.
- **`mgf.fastapi.security.AuthVerifier[P]`** — Protocol consumers
  implement to define the bearer-token → principal contract. Covariant
  in the principal type; consumers pick `P` (a dataclass, pydantic
  model, tenant-membership tuple, whatever).
- Case-insensitive Bearer scheme matching (RFC 7235).
- Optional `cookie_name` fallback for browser clients that store the
  token in an `HttpOnly` cookie. Header takes precedence.

### Behaviour guarantees

- Missing header, missing/empty token, wrong scheme: `HttpUnauthorized`
  ("missing bearer token").
- Verifier not wired on `app.state`: `HttpUnauthorized` to the caller +
  a noisy `LOG.error` to ops (deployment misconfig must not leak).
- Verifier raises `HttpUnauthorized` directly: pass-through (the
  verifier's message reaches the client).
- Verifier raises anything else (provider 500, expired JWT, network
  blip): translated to `HttpUnauthorized("invalid or expired session")`
  with the original exception chained via `__cause__`. The exception
  type name lands in a `LOG.info` line for debugging.
- `ExceptionTranslationMiddleware` resolves all `HttpUnauthorized`
  cases to a 401 JSON response via the HTTP-01 `http_status` fallback
  (no `status_map` extension required).

### Dependency bump

- **`mgf-common>=0.31,<0.32`** (was `>=0.30,<0.31`). Bumped in lock-step
  with mgf-common v0.31's release. The AP-03 0.x window pin discipline
  applies; consumers running mgf-common 0.30 keep their existing
  mgf-fastapi 0.2.x installs.

### Federation note

mgf-common is in its v0.32+ stabilization window (no new public surface
in mgf-common itself). Siblings retain autonomy to add public surface
in their own minors — this release exercises that autonomy.

---

## [0.2.0] — 2026-05-10

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

> **Paired with mgf-common v0.30.0** — picks up the FastAPI-shaped DB
> helpers extracted out of `mgf.common.db` at the v0.30 federation
> paired-extraction. The companion siblings (mgf-sqlalchemy v0.1.0,
> mgf-alembic v0.1.0) ship simultaneously. Cutover guide:
> [`docs/cutover/v0.2.0.md`](docs/cutover/v0.2.0.md).

### Added

- **`mgf.fastapi.db`** — submodule with `get_session` and `setup_db`,
  the FastAPI-shaped DB helpers that previously lived under
  `mgf.common.db.*` (mgf-common ≤ v0.29). They moved here at v0.30
  because the roadmap specified mgf-sqlalchemy as
  *framework-agnostic* — pulling Starlette into a "pure-SQLAlchemy"
  sibling would break that promise. mgf.fastapi.db imports from
  `mgf.sqlalchemy` (the new sibling).
- **`mgf.fastapi.db.get_session`** — FastAPI-Depends-compatible async
  generator. Yields one `AsyncSession` per request from
  `request.app.state.mgf_db_sessionmaker`. Closes via the context
  manager when the handler returns. Raises `AppConfigError` if
  the lifespan didn't run.
- **`mgf.fastapi.db.setup_db`** — async context manager for FastAPI
  lifespans. Creates the engine + sessionmaker via
  `mgf.sqlalchemy.create_engine` / `create_sessionmaker`, stashes
  them on `app.state.mgf_db_engine` / `app.state.mgf_db_sessionmaker`,
  and disposes the engine cleanly at lifespan exit.
- **`[sqlalchemy]` optional extra** — pulls `mgf-sqlalchemy>=0.1,<0.2`.
  Without it, `import mgf.fastapi.db` raises `ImportError` at load
  time with a message pointing at the missing extra.

### Changed

- **Runtime dep `mgf-common>=0.30,<0.31`** (was `>=0.28,<0.29`).
  AP-03 0.x window discipline — pin one minor at a time. mgf-common
  v0.30 strips out `mgf.common.db` and `mgf.common.alembic` (their
  surface lives in mgf-sqlalchemy and mgf-alembic now).

### Engineering

- 6 new tests (`tests/unit/test_db.py`) parity-preserved from
  `mgf-common/tests/unit/db/test_session.py` (FastAPI-Depends
  portions) and `tests/unit/db/test_db_lifespan.py`. The non-FastAPI
  portions of those files moved to `mgf-sqlalchemy/tests/unit/`.
- Total test count: 89 (was 83 in v0.1.0).
- mypy --strict still clean. Ruff still clean (in the new file).

---

## [0.1.0] — 2026-05-09

PyPI: <https://pypi.org/project/mgf-fastapi/0.1.0/> · Tag: `v0.1.0`

> **Maiden voyage** — extracted from `mgf-common` v0.28.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).

### Added

- **`mgf.fastapi`** — top-level FastAPI integration. `bootstrap` ↔
  FastAPI lifespan (`setup_lifespan`); `RequestIdMiddleware` +
  `current_request_id()`; `ExceptionTranslationMiddleware` +
  `DEFAULT_STATUS_MAP`; `Depends()` helpers (`get_app_context`,
  `get_settings`, `get_request_id`); `REQUEST_ID_HEADER` constant.
- **`mgf.fastapi.webhooks`** — Svix-shape HMAC webhook verification.
  `WebhookHeaderSchema` (frozen dataclass; case-folds header names);
  `SVIX_SCHEMA` (Svix preset; used verbatim by Clerk);
  `HmacWebhookVerifier` (transport-agnostic core; thread-safe;
  `verify(headers, body) -> (event_id, ts)`); `verify_request`
  (async FastAPI request adapter).
- **`mgf.fastapi.security`** — `IpAllowlist` FastAPI dependency.
  CIDR allowlist with IPv4 + IPv6 dual-stack; cross-version matches
  deliberately rejected. `trust_proxy=False` default concentrates
  the proxy-trust footgun. Default `cidrs=LOOPBACK_CIDRS` so tests
  work out-of-the-box.
- **`mgf.fastapi.testing`** — `run_test_app` async context manager.
  Starts uvicorn for a FastAPI app on a free port; yields the base
  URL; tears down cleanly on context exit. Surfaces server-startup
  exceptions immediately instead of timing out silently. Optional
  `[testing]` extra pulls in `uvicorn>=0.30` + `httpx>=0.27`.
- **`mgf.fastapi.exceptions`** — `HmacVerificationError(HttpUnauthorized)`.
  Inherits the HTTP-01 status leaf from `mgf.common.exceptions` so
  PAPER-24's `http_status` resolution maps to 401 in
  `ExceptionTranslationMiddleware` without an explicit `status_map`
  entry. (Other concrete leaves can be added here in subsequent
  minors per the federation rule "siblings own their domain
  concretes; mgf-common owns hierarchy roots + status leaves.")

### Engineering

- 83 tests carried over from `mgf-common/tests/unit/fastapi/` —
  every pre-extraction failure mode and integration path stays
  green at parity. Coverage threshold ≥80% (matches mgf-common's
  standard).
- Imports from `mgf.common.fastapi.*` rewrite to `mgf.fastapi.*`.
  `HmacVerificationError` re-exported from `mgf.fastapi.exceptions`
  rather than `mgf.common.exceptions` (the latter no longer ships
  it as of mgf-common v0.28).
- `mgf-common>=0.28,<0.29` runtime dependency. 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.fastapi` and `mgf.common`
  coexist as siblings.
