# Changelog

All notable changes to `mgf-http` 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-http = ">=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.11] - 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.10] - 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]

(no entries)

---



## [0.4.9] — 2026-06-07 — error-handling (depth-audit tail)

### Changed
- `HttpTransportError` now appends a category-based actionable hint (EH-09).
- `RetryPolicy.max_attempts` documents the finite retry-amplification ceiling — the
  bound that lets mgf-http forgo a circuit breaker (EH-13).

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

### Added
- `mgf.http.__version__` (AP-13); CI `tag-coherence` asserts it.
- `PUBLIC_API.json` + generator + CI pin tests (DOC-08/DOC-01); `PUBLIC_API.md`
  now lists every public name + md↔`__all__` pin (AP-10/DOC-09).
- Intent+example docstrings on the 6 HTTP-verb convenience methods (DP-07).

### Changed
- README badges/links retargeted off pypi.org → Codeberg (PKG-12).
- AP-14 declined (CFM-04, conformance §3) — consumer-fixture import-linter
  enforcement needs an in-repo consumer harness.

## [0.4.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.4.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 (the
Codeberg package-distribution cutover — PKG-01..12). `mgf-*` resolve from the
`codeberg-mgf` index (PKG-06). No API change.

---

## [0.4.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.4.4] — 2026-05-28 — federation pin walk (mgf-common 0.42.x)

Federation cohort wave: mgf-http 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.3] — 2026-05-26 — federation pin walks (mgf-common 0.41.x + mgf-test-supervisor PyPI)

Federation cohort walk: mgf-http 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-http
and mgf-common. This release widens mgf-http'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 — no longer needed once
  the supervisor dep is plain PyPI form.

### Source-level changes

None — mgf-http's source has zero usage of mgf-common 0.41
deletions (the 4 observability shims, 12 N818 exception
aliases, vault dict-shape, build_parser). Verified by grep
before the bump.

---

## [0.4.2] — 2026-05-18

### Changed

- **`mgf-common` pin window**: widened from `>=0.36,<0.37` to
  `>=0.36,<0.39` so consumers can install mgf-http alongside
  mgf-common 0.37 + 0.38. No source changes — mgf-http's API
  surface against mgf-common is stable across 0.36 / 0.37 /
  0.38 (the changes in those releases were docs + federation-
  contract artifacts, no API touched on mgf-http's import paths).
  Unblocks the federation catch-up sweep for figrecovery,
  mgf-usb, and vm-vmanager-app which need mgf-common 0.38 to track
  the v2.10 contract.

---

## [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` upper bound widened: `>=0.36,<0.37` → `>=0.36,<0.38` (accommodates 0.38 federation contract).

### Federation
- Aligned with mgf-common v0.38.0 contract (DOC-13/14/15/16/17). Federation `catch-up` clean.

---

## [0.4.0] — 2026-05-18

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

> **Wave 2 sibling release — `AsyncHttpClient.download()` ships
> resumable + integrity-verified downloads (H-NEW-1, two-consumer
> signal: iosRecovery IPSW restoration + vm-vmanager-app VM-image
> downloads). Also bundles the `max_response_size` + `.stream()`
> work from the [Unreleased] section (PAPER-36 + PAPER-37 — moved
> to this sibling's FEEDBACK per v2.8 DOC-13).** Pin bump:
> mgf-common ≥0.34 → ≥0.36.

### Added — `AsyncHttpClient.download()` resumable + verified (H-NEW-1)

The federation finally has the two-consumer evidence to ship
this primitive (iosRecovery's IPSW restoration flow +
vm-vmanager-app's VM-image fetch path both need the same shape).

```python
async with AsyncHttpClient() as client:
    result = await client.download(
        "https://updates.example.com/firmware-9.0.ipsw",
        Path("/var/cache/firmware/9.0.ipsw"),
        expected_sha1="abc123...",     # Apple ships SHA-1 for IPSWs
        expected_size=1_500_000_000,   # ~1.5 GB
        resume=True,                    # default — pick up after a blip
        on_progress=lambda done, total: ...
    )
    # result.dest, result.bytes_downloaded, result.sha256,
    # result.sha1 (None if not requested), result.resumed_from
```

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

- **`AsyncHttpClient.download(url, dest, *, expected_sha256, expected_sha1, expected_size, resume, chunk_size, on_progress, headers)`** —
  streams to `<dest>.partial`, atomic-renames to `dest` on
  verified completion. Honours `Range` headers for resume
  (server returning 200 instead of 206 is detected and treated
  as "server replayed from zero" — library resets transparently).
  Computes SHA-256 always; SHA-1 only when `expected_sha1` is
  supplied (saves the hashlib cost). Per SC-13 atomic write.
- **`DownloadResult(dest, bytes_downloaded, sha256, sha1, resumed_from)`** —
  frozen dataclass returned on verified success.
- **`IntegrityError(url, algorithm, expected, actual)`** —
  subclass of `HttpTransportError` (so `except HttpTransportError`
  catches it). `algorithm` ∈ `{"sha256", "sha1", "size"}`.

15 new unit tests cover happy path (fresh download + sha256 +
sha1 + size + on_progress), integrity failures (sha256
mismatch + size mismatch + IntegrityError IS HttpTransportError),
resume paths (continues from partial + resume=False truncates +
server 200-instead-of-206 reset), and preconditions (missing
parent / invalid hash lengths / explicit Range header rejected).

### Added — `max_response_size` cap and public `.stream()` method

Closes [PAPER-36](https://codeberg.org/magogi-admin/mgf-common/src/branch/main/FEEDBACK.md)
filed by inthewords during its citation-fetcher adoption.

- **`AsyncHttpClient(max_response_size: int | None = None)`** —
  a hard cap on the response body size that `request()` will
  buffer. When set to a positive int and the response exceeds
  it, the wrapper streams the body under the hood and raises
  `HttpTransportError(cause_summary="response-too-large")`
  instead of letting httpx buffer unbounded memory. Default
  `None` preserves historical buffer-fully behaviour. Intended
  audience: SSRF-conscious consumers fetching user-submitted
  URLs (link previews, OG-metadata scrapers, citation
  enrichers) who today have to drop to the `.client` escape
  hatch to enforce a body cap — bypassing retry + redaction +
  typed-error translation in the process.

- **`AsyncHttpClient.stream(method, url, ...)`** — a public
  async context manager that yields a still-streaming
  `httpx.Response`. Retry on transport failures, identity-aware
  User-Agent, secret-header redaction, and typed-error
  translation all apply — same posture as `request()`. The
  caller drives the body read via `response.aiter_bytes()` and
  decides how much to consume. Once the response is yielded,
  retries are off the table. Primary use cases: large-download
  patterns where buffering wastes memory, server-sent-events /
  NDJSON streams, and any consumer that wants more control
  than the new `max_response_size` cap.

`request()` with `max_response_size` set routes through
`stream()` internally — single implementation of the streaming
+ retry loop. The existing `_send_with_retries` (the buffered
path) is unchanged; the shared cookie header-injection guard
(SH-01 / RA-03) is extracted into a new private helper
`_assemble_request_headers` to keep validation in one place
across both surfaces.

16 new unit tests in `tests/unit/test_client.py`:
`TestMaxResponseSizeConstruction` (4 tests) +
`TestMaxResponseSize` (4 tests) + `TestStream` (8 tests).
Total 71 → 87 tests; all existing tests pass unchanged.

---

## [0.3.0] — 2026-05-17

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

> **Quality release** — closes every P0/P1/P2 reliability,
> security, and performance tracker item to v1.0-ready
> state. One new optional kwarg on `AsyncHttpClient`
> (`follow_redirects=`). One behaviour-tightening for security
> (RA-04 / SH-04 — see "Changed — security" below).

### Added — new optional kwargs on `AsyncHttpClient`

- **`follow_redirects: bool = True`** (RA-05). Pass-through
  to `httpx.AsyncClient(follow_redirects=...)`. Default
  preserves the previous behaviour; consumers needing manual
  30x handling pass `follow_redirects=False`.

### Added — regression-protection tests

- **PC-04** — `_redact_headers` perf pin (≤ 2 µs/call over
  20 headers) + correctness pin
  (`tests/perf/test_header_redaction.py`).
- **PC-05** — `RetryPolicy` lookup perf pins (≤ 1 µs/call
  each — `tests/perf/test_retry_policy_lookup.py`).
- 4 new unit tests asserting `HttpTransportError.cause_summary`
  carries category-only strings (no leaky `str(exc)` from the
  underlying httpx exception).
- New `TestAcloseIdempotency` class — 4 cases pinning the
  idempotent-close contract.

### Changed — security (RA-04 / SH-04 twins)

- **`_categorise_exc`** reworked to return category-only
  strings (`"timeout"` / `"connect-error"` / `"network-error"`
  / `type(exc).__name__`). NO `str(exc)` interpolation. Same
  fix on the catch-all `httpx.HTTPError` branch. Original
  exception preserved as `__cause__`. **Closes the info-
  disclosure window where a leaky `__str__` on the underlying
  httpx exception could surface in
  `HttpTransportError.cause_summary` or `str(HttpTransportError)`.**

### Changed — internal-only

- **RA-01** — `X-Request-Id` reflection sanitised
  (CR/LF/control-char rejection).
- **RA-02** — signature header tokenisation made resilient
  (single space / tab / multi-space whitespace tolerated).
- **RA-03** — cookie value sanitisation (CR/LF/NUL rejection).
- **RA-06** — `aclose()` idempotency pinned by tests.
- **SH-01..SH-03** — DB-URL redaction in error paths;
  Authorization / Cookie header redaction in structured logs;
  TLS-disable warning + structured audit log at client
  construction.
- **PC-01..PC-03** — per-request cost pinned; UA-string
  memoisation; redact-cookie perf pin.

### Changed — pin

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

### Notes for consumers

- `HttpTransportError.cause_summary` now returns shorter,
  category-only strings. If you parse these for diagnostics,
  switch to reading the exception type via
  `type(exc.__cause__).__name__`.

### Deferred to v1.1+ (decision-of-record)

- **SH-05** (cert pinning recipe / SPKI kwarg).
- **SH-06** (federation-wide SBOM + pip-audit CI).
- **PC-06** (sustained-throughput 60s test) — PC-02 already
  covers the resource-discipline shape.

---

## [0.2.0] — 2026-05-15

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

> **v2.6 + v2.7 standards-compliance release.** Brings
> `mgf-http` 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.2.x` is the supported MINOR),
  private disclosure path, 90-day embargo default, scope
  (header redaction, retry policy, transport-error translation,
  TLS-default-on warning), out-of-scope (`httpx` upstream,
  self-inflicted `verify_tls=False` in production).
- **`docs/claude/CLAUDE.md`** — AI-agent dense-lookup entry
  point per **DOC-07**. File map; cross-cutting patterns
  (transport-vs-application error split, idempotency
  boundary in `retryable_methods`, TLS-default-on warning,
  User-Agent identity, full-jitter backoff per EH-11);
  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-http is a federation
  sibling and inherits them. Conformance target: L2.

### Engineering

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

---

## [0.1.1] — 2026-05-11

PyPI: <https://pypi.org/project/mgf-http/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-http` 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.29,<0.30` → `mgf-common>=0.33,<0.34`.
  Lets consumers install `mgf-http` alongside the current
  `mgf-common` line.

---

## [0.1.0] — 2026-05-09

PyPI: <https://pypi.org/project/mgf-http/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.http.AsyncHttpClient`** — thin wrapper over
  `httpx.AsyncClient` baking in identity (User-Agent header derived
  from `mgf.common.app_name()` / `mgf.common.app_version()` plus
  `mgf-http/<libver>`), retry, secret-header redaction in logs +
  OTel spans, typed error translation, and TLS-default-on with a
  one-shot warning when `verify_tls=False`. Async context manager;
  `.client` escape hatch for httpx-specific features (streaming,
  multipart).
- **`mgf.http.RetryPolicy`** — exponential-with-jitter retry policy.
  Default zero retries (deterministic single-attempt). Idempotent
  methods (GET/HEAD/OPTIONS/PUT/DELETE) retryable by default; POST
  intentionally NOT retryable. Retryable statuses default to
  `{429, 503, 504}`; configurable via `retryable_methods` /
  `retryable_statuses` frozensets.
- **`mgf.http.exceptions.HttpTransportError`** —
  `OperationError`-rooted concrete leaf for transport-layer
  failures (timeout, connect-refused, network unreachable). Carries
  `url`, `cause_summary`, `attempts` for log + OTel attribution.
  Original `httpx` exception preserved via `__cause__`.

### Renamed (BREAKING vs the pre-extraction `mgf.common.http` shape)

- **`HttpClientError` → `HttpTransportError`** — the transport-layer
  concrete moves from `mgf.common.http.HttpClientError` to
  `mgf.http.exceptions.HttpTransportError`. The old name collided
  with `mgf.common.exceptions.HttpClientError` (the HTTP-01 4xx-class
  hierarchy parent, which **stays** in mgf-common). Renaming at the
  v0.29 cutover is the natural moment to fix the collision; the new
  name is also more descriptive — "transport failure" is exactly its
  role. **No deprecation alias.** Consumers rename in lock-step (the
  cutover guide ships a sed-friendly rewrite).

### Engineering

- 46 tests carried over from `mgf-common/tests/unit/http/` — every
  pre-extraction failure mode stays green at parity. Coverage
  threshold ≥80% (matches mgf-common's standard).
- Imports rewrite from `mgf.common.http.*` to `mgf.http.*`. The
  private `mgf.common._identity` import was replaced with the
  public `mgf.common.app_name()` / `mgf.common.app_version()`
  accessors — closes a closed-box leak that pre-existed in
  mgf-common v0.27.
- `mgf-common>=0.29,<0.30` 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.http`, `mgf.fastapi`, and
  `mgf.common` coexist as siblings.
- User-Agent suffix changed from `mgf-common/<libver>` to
  `mgf-http/<libver>`. Consumers reading the User-Agent for
  attribution should update their match patterns.
