From 85974a4d88086d9554b9d8e128cf6e6d24ae8f47 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Fri, 17 Jul 2026 15:22:24 +0300 Subject: [PATCH] docs(releases): 0.12.0 release notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture-deepening pass: six internal behavior-preserving refactors (schema validator split, dead to_delete flag removed, table ops behind the client seam, envelope header keys named, lease-token guard named, terminal path deepened to an Outcome sum type) plus two design records (conn union deliberate; two-seam overlap doc). One test-broker-only behavior change (session now required on cancel_timer/fetch_unprocessed) makes it a minor bump per the release README. Notes only — no tag/publish (that is a separate explicit step). Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/releases/0.12.0.md | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 planning/releases/0.12.0.md diff --git a/planning/releases/0.12.0.md b/planning/releases/0.12.0.md new file mode 100644 index 0000000..9745422 --- /dev/null +++ b/planning/releases/0.12.0.md @@ -0,0 +1,98 @@ +# faststream-outbox 0.12.0 — architecture-deepening pass (internal refactors, no production behavior change) + +**Minor release.** Every change to the installed package's **production** runtime is an +**internal refactor** — the producer, subscriber, lease/terminal-write, timer, DLQ, and +validation behavior are identical to 0.11.0, verified by the unchanged suite at 100% +coverage across Python 3.11–3.14. The batch came out of an architecture review that +deepened shallow modules, named duplicated rules, and removed a dead field. The **one** +behavior change is test-broker-only (a tightened `session` argument, below), which is why +this is a minor rather than a patch. Drop-in for production code. + +## Breaking changes + +- **`TestOutboxBroker`: `cancel_timer` / `fetch_unprocessed` now require `session`.** These + two methods moved behind the `AbstractOutboxClient` seam (see refactors below). Under the + **test broker** they previously accepted an *optional* `session` (an internal patch + defaulted it to `None`); they now require `session=...`, matching the real broker's + signature and the documented public contract (the argument is still ignored under the + test broker). **Production is unaffected** — `broker.cancel_timer` / `broker.fetch_unprocessed` + always required `session`. Only test code that called these on a `TestOutboxBroker` + *without* `session` needs a one-token edit. ([#141](https://github.com/modern-python/faststream-outbox/pull/141)) + +## Internal refactors (no behavior change) + +Each deepens the codebase — more behavior behind smaller, single-owner seams — without +changing observable production behavior. + +- **Schema-drift validator split out of `client.py`.** The ~285 LOC of Alembic-diff + + `pg_catalog` probes moved to a new `schema_validation.py`; the transport client roughly + halved (796 → 471 LOC) and no longer imports the optional `alembic` dependency. + `broker.validate_schema()` / `client.validate_schema()` are unchanged. + ([#139](https://github.com/modern-python/faststream-outbox/pull/139)) +- **Dead `to_delete` intent flag removed.** `OutboxInnerMessage.to_delete` was set on every + ack/terminal path but read by zero production code (dispatch branched on other fields). + Removed; tests now assert on the fields the dispatcher actually reads. + ([#140](https://github.com/modern-python/faststream-outbox/pull/140)) +- **`cancel_timer` / `fetch_unprocessed` routed through the client seam.** Both built inline + SQL on the broker's table handle, bypassing the `OutboxClient` that already owns the + table. They are now methods on `AbstractOutboxClient` (real + fake); the broker delegates, + and the test-broker patch machinery for them is deleted. (Carries the test-broker `session` + change above.) ([#141](https://github.com/modern-python/faststream-outbox/pull/141)) +- **Envelope-managed header keys named once.** `content-type` and `correlation_id` were bare + string literals across `envelope.py` (write), `parser.py` (read), and the relay + header-strip. Defined once in `message.py` as `CONTENT_TYPE_HEADER` / + `CORRELATION_ID_HEADER` / `ENVELOPE_MANAGED_HEADERS`. + ([#143](https://github.com/modern-python/faststream-outbox/pull/143)) +- **Lease-token guard named; contract coverage filled.** The `NULL = NULL` token guard the + fake repeated inline is now one `_lease_token_matches` helper. The cross-adapter contract + suite gained the missing `mark_pending` unleased-row scenario (the delete path already had + it) and an empty-queues symmetry check. + ([#144](https://github.com/modern-python/faststream-outbox/pull/144)) +- **Terminal intent deepened to a canonical `Outcome` sum type.** `OutboxInnerMessage` now + records one `Outcome` — `Ack` | `Retry(delay_seconds)` | `Terminal(reason)` — instead of + three loosely-coupled fields. Success is an explicit `Ack()`; "reason and delay both set" + is now unrepresentable. `terminal_failure_reason` / `pending_delay_seconds` / `state_set` + remain as **read-only property views**, so readers and the `DLQFailureReason` public + contract are unchanged. ([#145](https://github.com/modern-python/faststream-outbox/pull/145)) + +## Design records (no code change) + +- **The `conn: AsyncConnection | None` union on the client seam is deliberate.** The review + flagged it as a "type lie"; on inspection it is a documented widening (the fake genuinely + has no connection, the subscriber genuinely produces `None` on the fake path). Recorded as + an accepted decision with a revisit trigger. + ([#142](https://github.com/modern-python/faststream-outbox/pull/142)) +- **The two observability seams overlap on the consume/publish series.** `architecture/metrics.md` + now documents that the recorder adapters and the native middleware emit the same-named + consume/publish metrics — one Prometheus registry raises `Duplicated timeseries`, live OTel + meters on both double-count — and why the library can't guard it at construction (separate + registries / middleware-as-source-of-truth stays an operator concern). + ([#146](https://github.com/modern-python/faststream-outbox/pull/146)) + +## Migration + +- **Production code:** none. No public-API change, no schema change, no config change. +- **Test suites using `TestOutboxBroker`:** if you call `.cancel_timer(...)` or + `.fetch_unprocessed(...)` on the test broker without a `session`, add `session=...` (any + session — it is ignored under the test broker). Everywhere else, no change. + +## Touched surface + +- **Package code (internal only):** `faststream_outbox/schema_validation.py` (new), + `client.py`, `broker.py`, `message.py`, `envelope.py`, `parser/parser.py`, + `subscriber/usecase.py`, `testing.py`. +- **Tests:** `tests/test_client_contract.py` (+2 scenarios), `tests/test_unit.py`, + `tests/test_fake.py`. +- **Living docs / decisions:** `CLAUDE.md`, `architecture/dlq.md`, `architecture/test-broker.md`, + `architecture/metrics.md`, `docs/usage/testing.md`, `planning/decisions/2026-07-17-conn-union-is-deliberate.md`. + +## See also + +- [#139](https://github.com/modern-python/faststream-outbox/pull/139) — split schema-drift validator. +- [#140](https://github.com/modern-python/faststream-outbox/pull/140) — remove dead `to_delete` flag. +- [#141](https://github.com/modern-python/faststream-outbox/pull/141) — table ops behind the client seam. +- [#142](https://github.com/modern-python/faststream-outbox/pull/142) — `conn` union deliberate (decision). +- [#143](https://github.com/modern-python/faststream-outbox/pull/143) — name envelope header keys. +- [#144](https://github.com/modern-python/faststream-outbox/pull/144) — name lease-token guard + contract coverage. +- [#145](https://github.com/modern-python/faststream-outbox/pull/145) — canonical `Outcome` sum type. +- [#146](https://github.com/modern-python/faststream-outbox/pull/146) — two-seam overlap hazard (docs).