v26.09.4 — the native gaps, closed in the framework
Everything here began as a place where an application running on 26.09.3 had to write framework-shaped code of its own — a metric wrapper around a method, a retry loop, a publisher per broker, a security rule that silently matched nothing. Each one is now an attribute the compiled manifest carries and a firefly.* key that configures it.
Highlights
Micrometer's method attributes. #[Timed], #[Counted] and #[Observed] ride the same interceptor chain #[Transactional] does. The scanner refuses the shapes that would compile into nothing rather than dropping them in silence — a metric on a class no post-processor reaches, on a final class or method, and the two shapes PHP does not inherit: a class-level attribute on a base whose stereotyped child returns [] for it, and a method an unannotated override hides. The drop decision is per method and an intersection over subclasses, so one child that merely inherits an annotated method cannot vouch for a sibling that overrides it.
The six resilience patterns as attributes, on that same chain, instead of wrappers — with an idle TTL for breaker and limiter records.
The EDA brokers reach the tracing seam. RabbitMqEventPublisher, KafkaEventPublisher and PostgresEventPublisher route publish() through EdaTracing::tracePublish(), so a broker record carries a traceparent of the framework's own making — gated by firefly.eda.tracing.brokers.enabled. The tracing figure and its Known-latent caveat moved with the code.
Spring Data continues. A #[Projection] and a trailing Pageable combine, paging in the database. initialDelay is applied rather than carried.
Breaking
Each of these is migratable without guessing; CHANGELOG.md carries the full migration note for every one.
when-authorizedhealth details are real, instead of quietly degrading tonever. An application already running that value with security on starts disclosing what it used to withhold. Decide, do not inherit: setnever, list roles infirefly.management.endpoint.health.roles, or bind your ownHealthDetailsAuthorizer.HealthEndpointgained a required fourth constructor parameter.- problem+json's
traceIdis the W3C trace id, andcorrelationIdis its own member beside it; both travel as response headers. With tracing off, every byte is what it was. - A URL rule written
/api/*stops being a dead rule, so every/-prefixed rule now matches what its author meant. - The OpenAPI document publishes the security the server actually has, and what the dispatcher enforces rather than only what the URL rules say.
Documentation
The provenance guard that shipped in 26.09.3 audits every Markdown file this repository ships, and wave N branched before it existed. Eighteen listings quoting code the wave had changed were re-quoted verbatim from the files they name, five new listings gained a marker, and four claims that live outside any fenced block — where no guard reads them — were corrected, including the figure's alt text.
Verification
All 21 findings wave N's review loop left unconfirmed were verified against the tree before this tag: 21 already fixed, 0 still broken, each proven with git merge-base --is-ancestor.
Gate at 72db787: Pint, PHPStan max, 3779 tests (15,144 assertions), Deptrac 0 violations, 75 browser tests, and CI green on PHP 8.3, 8.4 and 8.5.
Installing
This release is not published to Packagist — the org's mirror repositories and release credentials are not configured, by design. Install from the monorepo or a git VCS repository entry; docs/publishing.md documents what a future Packagist release would need.
Full changelog: v26.09.3...v26.09.4