diff --git a/CHANGELOG.md b/CHANGELOG.md index ad6d239..935d97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,102 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). --- +## v26.05.06 (2026-05-31) + +### Hardening pass — framework-wide bug fixes + +A deep audit of the whole framework surfaced a class of *silent wiring gaps* +and correctness bugs — features that existed but were never connected to the +runtime path, so the test suite passed while the behaviour was broken. This +release fixes them. The full test suite passes and CI (`ruff`, `ruff format`, +`mypy --strict`) is green. + +#### Admin dashboard + +- **HTTP traces are now recorded.** The `TraceCollectorFilter` was resolved + from the DI container at `create_app()` time — before beans are instantiated — + so it was always `None` and never joined the request filter chain. It is now + created and owned by `create_app` and wired into the chain; `/admin/api/traces` + and the SSE trace stream show real traffic. +- **Live updates (SSE) now stream.** `WebFilterChainMiddleware` buffered the + *entire* response body before returning, which hung every infinite SSE stream + (this broke **all** Server-Sent Events framework-wide, not just admin). The + filter chain now forwards streaming responses incrementally. +- **Server info** resolves lazily instead of showing `unknown`. + +#### Dependency injection & AOP + +- **Same-type beans no longer collapse.** Two `@bean` methods returning the same + concrete type overwrote each other in the type-keyed registry and vanished + from `list[T]` resolution. All registrations are now tracked and + `resolve_all`/`list[T]` returns every bean. +- **AOP advice is woven regardless of registration order.** Bean post-processing + is now two-pass (all `before_init`, then `post_construct`+`after_init`), so a + target initialised before its `@aspect` is still advised. +- **A side-effecting `@property` no longer aborts startup.** Weaving, scheduled- + task discovery and every context wiring/lifecycle scan now look attributes up + statically (`inspect.getattr_static`) instead of triggering property getters. +- **`RequestContextFilter` is wired by default**, so `REQUEST`-scoped beans and + `@pre_authorize`/`@post_authorize` work out of the box. + +#### Web + +- `RequestLoggingFilter`/middleware no longer crash on every request when + `structlog` is not installed (new `pyfly.logging.get_logger` shim). +- The FastAPI adapter now generates a correct OpenAPI document for controller + routes and honours `@controller_advice` global exception handlers. +- The health-indicator rescan hook now actually runs after startup, so + `/actuator/health` reflects `DOWN` subsystems instead of always reporting `UP`. +- `/actuator/prometheus` returns the Prometheus text exposition format (was JSON). + +#### Transactional engine + +- **Workflow `@compensation_step` now executes on failure** — completed + compensatable steps are rolled back in reverse order. +- **Transactional REST controllers** (`/api/orchestration`, `/dlq`, `/workflow`) + are now mounted as HTTP routes. +- The saga compensator records compensation outcomes, so + `SagaResult.compensated`/`compensation_result` are populated. +- Saga stale-recovery no longer raises `TypeError` (`started_at` is persisted as + a `datetime`; `get_stale` tolerates ISO strings). +- Workflow `@wait_for_all`/`@wait_for_any` timeouts are honoured (no unbounded waits). +- Fire-and-forget child workflows return the real child correlation id. + +#### CQRS + +- The query cache adapter now receives the `CacheAdapter`, so `@cacheable` + queries are actually cached. +- Domain-event publishing is wired when an EDA/messaging producer bean is + present (was a permanent no-op). + +#### EDA, messaging & scheduling + +- The EDA circuit breaker no longer gets permanently stuck `OPEN`. +- Kafka/RabbitMQ message-broker adapters handle `@message_listener` subscriptions + that arrive after `start()` (they previously never consumed). +- A scheduled `fixed_delay` task that raises no longer kills its loop. + +#### Data, config, event sourcing, notifications, callbacks + +- Derived-query stub detection no longer misclassifies documented repository + methods as real implementations (SQLAlchemy + MongoDB). +- MongoDB derived-query `LIKE` wildcards (`%`, `_`) are now translated to regex. +- `Config.bind()` resolves `${...}` placeholders and binds nested dataclass fields. +- `ConfigServer` filesystem backend writes back the file `fetch()` reads, so + saves are no longer silently shadowed by a stale `.yaml`. +- The event-sourcing `ProjectionRunner` no longer advances its cursor past a + failed event (at-least-once, in-order; was silent data loss). +- The SMTP notification provider no longer drops BCC recipients. +- Outbound callback/webhook HMAC signatures are computed over canonical JSON + (were computed over `str(dict)` — unverifiable). + +#### Internal + +- `pyfly.__version__` is back in sync with the packaged version (it was stale). +- Lint/format/type fixes across the EDA adapters and correlation surface. + +--- + ## v26.05.05 (2026-05-19) ### Fixed — `PostgresEventBus` is multi-worker safe diff --git a/docs/modules/README.md b/docs/modules/README.md index b3c789a..4536f03 100644 --- a/docs/modules/README.md +++ b/docs/modules/README.md @@ -29,6 +29,7 @@ The building blocks that every PyFly application relies on. | [Core & Lifecycle](core.md) | Application bootstrap with `@pyfly_application`, startup/shutdown sequence, configuration loading, profile overlays, banner rendering | | [Dependency Injection](dependency-injection.md) | `@service`, `@repository`, `@controller`, `@component` stereotypes, constructor injection, `Autowired()`, scopes (singleton, transient, request), `@bean` factories, `@primary`, `Qualifier`, conditional beans, lifecycle hooks | | [Configuration](configuration.md) | YAML/TOML config files, `Config` class, profile-specific overlays, `@config_properties` binding, environment variable overrides | +| [Config Server](config-server.md) | Centralized config server (`ConfigServer`, `ConfigClient`), `ConfigBackend` SPI, in-memory & filesystem backends, Spring-Cloud-Config-compatible responses | | [Error Handling](error-handling.md) | 25+ exception types, `ErrorResponse`, `ErrorCategory`, `ErrorSeverity`, HTTP status mapping, structured error responses, `@exception_handler` | --- @@ -152,6 +153,7 @@ Monitor, schedule, and observe your applications in production. | Guide | What You'll Learn | |-------|-------------------| | [Observability](observability.md) | `@timed`, `@counted`, `@span`, `MetricsRegistry`, `HealthChecker`, Prometheus metrics export, OpenTelemetry tracing, structured logging with correlation IDs | +| [Logging](logging.md) | `get_logger`, `LoggingPort`, structlog & stdlib adapters, structured `event + key=value` logging, runtime level control | | [Scheduling](scheduling.md) | `@scheduled`, `@async_method`, `CronExpression`, `TaskScheduler`, fixed-rate tasks, fixed-delay tasks, asyncio and thread pool executors | --- diff --git a/docs/modules/config-server.md b/docs/modules/config-server.md new file mode 100644 index 0000000..65356c2 --- /dev/null +++ b/docs/modules/config-server.md @@ -0,0 +1,105 @@ +# Config Server Guide + +A lightweight, Spring-Cloud-Config-style **centralized configuration server**: +serve versioned config bundles (keyed by application + profile + label) to many +client services over HTTP. + +--- + +## Table of Contents + +1. [Introduction](#introduction) +2. [ConfigSource](#configsource) +3. [Backends](#backends) +4. [ConfigServer](#configserver) +5. [ConfigClient](#configclient) + +--- + +## Introduction + +The module has three parts: + +- **`ConfigBackend`** — the storage SPI (a `Protocol`): `fetch`, `save`, `list`. +- **`ConfigServer`** — a framework-agnostic controller exposing config bundles + over HTTP (`base_path = "/config"`). +- **`ConfigClient`** — fetches a bundle from a remote `ConfigServer` on startup. + +--- + +## ConfigSource + +A bundle is identified by `application` + `profile` + `label`: + +```python +from pyfly.config_server import ConfigSource + +ConfigSource( + application="orders", + profile="prod", + label="main", # defaults to "main" + properties={"db.url": "..."}, +) +``` + +--- + +## Backends + +Two backends ship out of the box: + +```python +from pyfly.config_server import InMemoryConfigBackend, FilesystemConfigBackend + +backend = InMemoryConfigBackend() # great for tests +backend = FilesystemConfigBackend("/etc/pyfly") # reads/writes files +``` + +**`FilesystemConfigBackend`** stores each bundle as +`/