# Changelog

All notable changes to `mgf-sqlalchemy` 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-sqlalchemy = ">=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_common/src/branch/main/docs/standards/RELEASING.md).

---

## [0.5.9] - 2026-06-26

### Changed
- Adopt **mgf-common 0.49** — widen the pin to `<0.50` (the framework-neutral
  Svix webhook HMAC primitive `mgf.common.webhooks` + the AP-14 identity /
  lifecycle / doctor public seams; additive). Republished so consumers that
  pin `mgf-common>=0.49` (mgf-fed, mgf-fastapi) can resolve `mgf-sqlalchemy`.

## [0.5.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.5.7] — 2026-06-07 — error-handling + tag hygiene (depth-audit tail)

### Changed

- `make_safe_url`: narrowed `except Exception` → typed `except ArgumentError`
  (what `make_url` raises on a malformed/None URL) — EH-07-compliant; the
  never-raise logging-safety intent is preserved.

### Fixed

- REL-01: the lightweight `v0.5.5` tag re-annotated on its original commit.
- AP-14 declined (CFM-04, see conformance §3): import-linter consumer-fixture
  enforcement needs an in-repo consumer harness this adapter doesn't have.

## [0.5.6] — 2026-06-07 — public-API surface + version (depth-audit batch)

### Added

- `mgf.sqlalchemy.__version__` (AP-13); CI `tag-coherence` now asserts it.
- `PUBLIC_API.json` + generator (`tools/generate_public_api_json.py`) + CI pin
  tests (DOC-08); `PUBLIC_API.md` now lists every public name (DOC-09).
- Canonical smoke test (imports + `__all__` reachability) (TS-21).

### Changed

- README badges/links retargeted off pypi.org → Codeberg (PKG-12).

## [0.5.5] — 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.5.4] — 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.5.3] — 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.5.2] — 2026-05-28 — federation cohort publish (mgf-common 0.42.x)

Federation cohort wave: mgf-sqlalchemy accepts **mgf-common 0.42.x** (pin widened to
`>=0.41,<0.43`) and ships to PyPI, carrying the previously-unpublished 0.5.1 work.

---

## [0.5.1] — 2026-05-28 — own NOSUPERUSER RLS-bite proof + pagination edge

### Added

- `tests/integration/test_rls_nosuperuser.py` — proves `tenant_session`
  isolates tenants against a real Postgres as a NOSUPERUSER role (FORCE ROW
  LEVEL SECURITY; asserts `rolsuper is False` so the proof can't pass
  vacuously). The isolation contract is now owned in-library, not only in a
  consumer. Isolation PROVED — no leak.
- `paginate_cursor` empty-result-set-with-cursor edge test.
- `asyncpg` dev dependency (drives the integration test; not a runtime dep).

### Notes

- Test + dev-dep only; no public API change.

---

## [0.5.0] — 2026-05-27 — `paginate_cursor` pagination helper

### Added

- `paginate_cursor` — pure cursor-pagination helper for the
  "fetch `limit + 1`" idiom. Given over-fetched rows, returns
  `(items, truncated, next_cursor)` with the page sliced to `limit`
  and the cursor derived (via a caller-supplied `cursor_of`
  extractor) from the last *included* row, so the dropped row's
  cursor never leaks. No session / no I/O — composes with any query
  shape. New public name in `mgf.sqlalchemy`; new module
  `mgf/sqlalchemy/_pagination.py`; 12 unit tests in
  `tests/unit/test_pagination.py`. Closes **PAPER-01** (federation
  [PAPER-94](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/FEEDBACK.md#paper-94)).
  Pairs with the existing `tenant_session` to cover the two patterns
  a Postgres-RLS multi-tenant consumer copy-pastes across read routes.

---

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

Federation cohort walk: mgf-sqlalchemy 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-sqlalchemy
and mgf-common. This release widens mgf-sqlalchemy'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.
- `pyproject.toml`: removed `[tool.hatch.metadata]
  allow-direct-references = true` block.

### Source-level changes

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

---

## [0.4.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.4.0] — 2026-05-18

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

> **Wave 2 sibling release — S-1 audit-trail mixin + S-3 UUID v7
> primary keys.** Two-consumer signal (PlasmaMapper + inthewords
> both ship the same shape per-model today). Skips v0.3.0 to
> align the MINOR cadence with the mgf-common 0.35/0.36 / mgf-http
> 0.4 batch. Pin bump: mgf-common ≥0.33 → ≥0.36.

### Added — `mgf.sqlalchemy._mixins`

Six new public names (all `experimental` per AP-11), all
re-exported from `mgf.sqlalchemy`:

- **`AuditTrailMixin`** — drop-in mixin adding `created_at` +
  `updated_at` + `created_by_id` + `updated_by_id` columns
  (all tz-aware UTC for the timestamps; `Uuid(as_uuid=True)`
  for the IDs). Auto-stamps timestamps on insert / update.
  Auto-stamps user IDs from the audit-user contextvar (see
  `set_audit_user` below).
- **`SoftDeleteMixin`** — adds `deleted_at` + `deleted_by_id`.
  Ships `.soft_delete()` (sets columns to now / audit-user) +
  `.restore()` (clears them). Idempotent on both. Does NOT
  install a default query filter — that's per-consumer policy.
  Composes with `AuditTrailMixin`.
- **`uuid7()`** — UUID version 7 generator (time-sortable per
  draft-ietf-uuidrev-rfc4122bis). 48-bit ms timestamp prefix +
  74 bits crypto-random. Better than UUID v4 for B-tree PK
  index locality (Postgres research: 2-5x insert-throughput win
  on high-write tables).
- **`uuid7_pk_column()`** — typed `mapped_column(Uuid(as_uuid=
  True), primary_key=True, default=uuid7)`. Drop-in for any
  declarative model's `id` column.
- **`set_audit_user(user_id)`** — context manager that binds
  the audit user (contextvar-based; works correctly with
  asyncio + threaded servers). Wire into a FastAPI middleware
  / Django middleware / pre-commit hook.
- **`current_audit_user()`** — direct read of the audit-user
  contextvar (useful for diagnostic / debug paths).

23 new unit tests cover UUID v7 (version + variant + ordering
+ uniqueness + integration with `uuid7_pk_column`), audit-trail
mixin (columns + insert-stamp + update-stamp + contextvar
respect + NULL-when-no-user), `set_audit_user` semantics (bind
+ unbind + nesting + explicit None), and soft-delete (columns
+ initial-state + delete + idempotent + restore + no-filter).

### Changed — pin

- **`mgf-common>=0.33,<0.34` → `mgf-common>=0.36,<0.37`**.
  Catches the mgf-common 0.35.0 + 0.36.0 Wave 1 work.

### Notes for consumers

- **Drop-in upgrade from 0.2.0** — the new mixins are purely
  additive. Existing consumers unaffected unless they adopt.
- **PlasmaMapper / inthewords**: ready to migrate per-model
  ad-hoc audit columns to the mixin (estimated ~30-50 LOC
  saved per consumer).

### Verification

- pytest tests/: 101 passed (78 from 0.2.0 + 23 new mixin).
- ruff: clean.
- mypy --strict: clean.

---

## [0.2.0] — 2026-05-15

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

> **v2.6 + v2.7 standards-compliance release.** Brings
> `mgf-sqlalchemy` 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`** — vulnerability-reporting policy.
  Particular attention to `tenant_session` (the RLS GUC
  setter) as the load-bearing security boundary; UUID
  validation BEFORE `SET LOCAL` interpolation is the
  SQL-injection defence.
- **`docs/claude/CLAUDE.md`** — AI-agent dense reference.
  File map; cross-cutting patterns (`SET LOCAL` scoping,
  `expire_on_commit=False` rationale, SQLite-aware pool
  defaults, no FastAPI leakage, consumer-side RLS-policy
  contract); build + release recipe; common pitfalls.
- **`docs/standards/README.md`** — pointer-only stub per
  **DOC-02** §2.0.4 Shape A. Conformance target: L2.

### Engineering

- No code changes. `src/mgf/sqlalchemy/` byte-identical to
  v0.1.1. `mgf-common>=0.33,<0.34` unchanged.

---

## [0.1.1] — 2026-05-11

PyPI: <https://pypi.org/project/mgf-sqlalchemy/0.1.1/> · Tag: `v0.1.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-sqlalchemy` 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.30,<0.31` → `mgf-common>=0.33,<0.34`.
  Lets consumers install `mgf-sqlalchemy` alongside the current
  `mgf-common` line.

---

## [0.1.0] — 2026-05-10

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

> **Maiden voyage** — extracted from `mgf-common` v0.29.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.sqlalchemy.create_engine`** — async SQLAlchemy engine
  factory wrapping `sqlalchemy.ext.asyncio.create_async_engine` with
  production-leaning defaults: `pool_pre_ping=True` (catches stale
  connections after DB restart), `pool_recycle=3600` (avoids MySQL
  `wait_timeout` surprise), conservative `pool_size`/`max_overflow`.
  SQLite-aware: detects `sqlite://` / `sqlite+` URLs and omits pool
  kwargs that `StaticPool` / `NullPool` reject. `**extra` forwarded
  to `create_async_engine` for rare options.
- **`mgf.sqlalchemy.create_sessionmaker`** — async sessionmaker
  factory wrapping `async_sessionmaker` with SQLAlchemy 2's
  recommended `expire_on_commit=False` default for async sessions.
  `expire_on_commit=True` opts back into the legacy synchronous
  semantics.
- **`mgf.sqlalchemy.tenant_session`** — async context manager that
  runs `SET LOCAL app.current_tenant = '<uuid>'` for Postgres
  RLS-based multi-tenancy. The `LOCAL` qualifier scopes to the
  current transaction (auto-resets on commit/rollback) so
  cross-tenant leakage between requests is impossible at the SQL
  layer. `tenant_id` is UUID-validated before interpolation
  (Postgres `SET LOCAL` doesn't accept bind params, so literal
  interpolation is the only path; UUID validation makes it safe).

### Removed (BREAKING vs the pre-extraction `mgf.common.db` shape)

- **`get_session` and `setup_db`** are NOT in this sibling. They
  were FastAPI-Depends and FastAPI-lifespan shaped respectively in
  mgf-common — pulling Starlette/FastAPI into a "framework-agnostic
  SQLAlchemy" sibling would violate the closed-box rule. They moved
  to `mgf.fastapi.db.get_session` / `mgf.fastapi.db.setup_db` in
  mgf-fastapi v0.2.0 (gated behind the `mgf-fastapi[sqlalchemy]`
  optional extra). Consumers needing them install both
  `mgf-sqlalchemy` and `mgf-fastapi[sqlalchemy]`.

### Engineering

- 11 tests carried over from `mgf-common/tests/unit/db/test_engine.py`
  + the non-FastAPI portions of `test_session.py` — every
  pre-extraction failure mode for the helpers in this sibling stays
  green at parity. Coverage threshold ≥80% (matches mgf-common's
  standard).
- Imports rewrite from `mgf.common.db.*` to `mgf.sqlalchemy.*`. No
  private-import leaks; the sibling reaches into mgf-common only
  through public names.
- `mgf-common>=0.30,<0.31` 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.sqlalchemy`, `mgf.alembic`,
  `mgf.fastapi`, `mgf.http`, and `mgf.common` coexist as siblings.
