Skip to content

Releases: fireflyframework/fireflyframework-rust

v26.6.35

19 Jun 22:58
729f618

Choose a tag to compare

Spring Security parity — Tier 5c: ACL / domain-object security. The Rust
analog of spring-security-acl, answering hasPermission(object, permission)
from per-object access-control lists. Pure Rust — no new dependencies. All
additive. Adversarially reviewed before release.

Added

  • ACL core (spring-security-acl parity):
    • Permission — the BasePermission bitmask (READ=1, WRITE=2,
      CREATE=4, DELETE=8, ADMINISTRATION=16), with cumulative union,
      bit-contains, and case-insensitive name parsing.
    • Sid (Principal / Authority — Spring's PrincipalSid /
      GrantedAuthoritySid), ObjectIdentity (type + identifier),
      AccessControlEntry (sid + permission + granting), and Acl (owner
      • ordered ACEs + optional parent for inheritance).
    • AclService + InMemoryAclService (Spring's MutableAclService),
      and the free is_granted resolver.
  • AclPermissionEvaluator — bridges an AclService to the Tier 3
    PermissionEvaluator, resolving hasPermission(...) against per-object ACLs
    by object reference or (type, id). The principal and its roles/authorities
    map to PrincipalSid / GrantedAuthoritySid (each role matched both bare and
    ROLE_-prefixed).
  • PermissionEvaluator::has_permission_for_id + the free
    has_permission_for_id — Spring's id-based hasPermission overload
    (default-deny, backward compatible).

Security

  • ACL evaluation is default-deny: a permission is granted only when an
    applicable granting entry is found (locally or up the inheritance chain);
    the first entry matching a (sid, permission) wins, so a deny placed
    before a grant takes precedence (Spring's DefaultPermissionGrantingStrategy).
    The inheritance walk is bounded, so a cyclic or pathologically deep parent
    chain terminates and denies rather than looping.

v26.6.34

19 Jun 21:21
416312c

Choose a tag to compare

Spring Security parity — Tier 5a: LDAP / Active Directory authentication.
The first of the Tier 5 "big subsystems", delivered as an opt-in feature. All
additive (no behaviour change to existing code; the default build does not
compile the new module). Adversarially reviewed before release.

Added

  • ldap feature (opt-in, pulls in ldap3) — Spring's
    ldapAuthentication():
    • LdapAuthenticationProvider — bind authentication as an
      AuthenticationProvider (plugs into ProviderManager): search the user DN
      under a base+filter ((uid={0}), username RFC 4515-escaped), bind as that
      DN with the password (the directory verifies it), then map group membership
      ((member={0})) to ROLE_<GROUP> authorities — Spring's
      BindAuthenticator + DefaultLdapAuthoritiesPopulator.
    • ActiveDirectoryLdapAuthenticationProvider — binds as the
      userPrincipalName (user@domain) and maps the user's memberOf groups to
      roles.
    • LdapOperations port (+ escape_filter_value, cn_from_dn,
      LdapEntry) with the production Ldap3Operations adapter over ldap3.
      The port makes the provider logic unit-testable without a live directory.
  • Security defaults: an empty password is rejected before binding (a simple
    bind with an empty password is an anonymous bind that most directories accept
    — an authentication bypass); the username/DN are RFC 4515-escaped in search
    filters (LDAP-injection safe); unknown-user and wrong-password fail with the
    same error value; a non-zero LDAP bind result code is an error (never a silent
    success).
  • Hardened from the pre-release adversarial review: an ambiguous user search
    (more than one matching entry) is rejected rather than binding against an
    arbitrary first match (Spring's IncorrectResultSizeDataAccessException); a
    directory error while populating authorities propagates and fails the
    login instead of silently authenticating with no roles (Spring's
    DefaultLdapAuthoritiesPopulator semantics); and a malformed directory
    entry
    is caught and turned into a clean error rather than aborting the
    authentication task.

Notes

  • The live Ldap3Operations adapter is exercised by an integration test gated
    on FIREFLY_TEST_LDAP_URL (skipped when unset); the provider logic is fully
    covered by mock-LdapOperations unit tests.

v26.6.33

19 Jun 19:54
80260cf

Choose a tag to compare

Spring Security parity — Tier 4: the OAuth2 ecosystem. The wider OAuth2
surface beyond the browser login flow. All additive (no behaviour change to
existing code). Adversarially reviewed before release.

Added

  • Opaque-token introspection (RFC 7662)RemoteTokenIntrospector
    (Spring's OpaqueTokenIntrospector): POSTs a non-JWT bearer token to the
    authorization server's introspection endpoint (HTTP Basic client auth) and,
    on active: true, maps the response to an Authentication. Implements
    Verifier, so it drops into a BearerLayer as an alternative to local JWT
    verification. Fails closed (transport error / non-2xx / non-JSON /
    active: false/absent all reject).
  • Outbound OAuth2 client (AuthorizedClientManager)
    OAuth2AuthorizedClientManager + OAuth2AuthorizedClientService (+
    InMemoryOAuth2AuthorizedClientService) obtain, cache, and auto-refresh the
    access tokens the app needs to call downstream services: the client-credentials
    grant (service-to-service) and the refresh-token grant, reusing a cached
    OAuth2AuthorizedClient until it is within the clock-skew window of expiry.
  • RP-initiated logout (OIDC)oidc_logout_url + ClientRegistration's new
    end_session_endpoint / post_logout_redirect_uri: POST /logout invalidates
    the local session and, when the provider advertises an end_session_endpoint,
    redirects to it with id_token_hint + post_logout_redirect_uri (Spring's
    OidcClientInitiatedLogoutSuccessHandler). The login callback now stores the
    registration_id + id_token for the hint.
  • Authorization-server HTTP endpointsAuthorizationServerRouter mounts
    the previously callable-only AuthorizationServer as POST /oauth2/token
    (RFC 6749; client-credentials + refresh-token, client_secret_post; RFC 6749
    §5.2 error envelope) and GET /.well-known/oauth-authorization-server (RFC 8414
    metadata).

Security notes & known limitations (roadmap)

  • The OAuth2 HTTP clients (introspection, outbound token, JWKS, login) now apply
    connect/read timeouts and cap the response body, so a slow or hostile endpoint
    cannot hang the bearer-verification path or force unbounded allocation; a token
    response with no expires_in is assumed short-lived (bounded fallback), never
    immortal. ClientRegistration and OAuth2AuthorizedClient redact their
    secrets/tokens in Debug.
  • The authorization server signs HS256 (symmetric), so no jwks_uri is
    published; the server-side authorization_code grant + PKCE, an /authorize
    endpoint, and a client-authenticated /oauth2/revoke (RFC 7009) remain a
    follow-up.
  • OAuth2AuthorizedClientManager does not single-flight concurrent
    authorizations for the same registration: concurrent callers may each hit the
    token endpoint, and against an authorization server that rotates refresh
    tokens, concurrent refreshes can lose a rotated token (last-writer-wins).
    Serialize refreshes for the same client if your AS rotates. Token-endpoint
    failures surface as the HTTP status (the structured RFC 6749 §5.2 error body is
    not yet parsed back).

v26.6.32

19 Jun 18:51
dea877e

Choose a tag to compare

Spring Security parity — Tier 3: method-security depth. Expression-based
method security and domain-object permissions, the SpEL-equivalent layer over
the existing #[pre_authorize] / #[post_authorize] macros. All additive (no
behaviour change to existing code). Adversarially reviewed before release.

Added

  • Expression-based #[pre_authorize] — a non-keyword argument is now a
    boolean Rust expression evaluated before the body with the method's
    parameters and auth (a &Authentication) in scope (Spring's
    @PreAuthorize("#id == authentication.name")), e.g.
    #[pre_authorize(auth.has_role("ADMIN") || auth.principal == owner)]. The
    keyword rules (authenticated, role, any_role, authority,
    any_authority) are unchanged and fully backward-compatible. Fail-closed: no
    ambient context denies with Unauthenticated, a false expression with
    Forbidden.
  • PermissionEvaluator + has_permission — the Rust analog of Spring's
    PermissionEvaluator / hasPermission(target, permission). Register one
    process-wide with set_permission_evaluator; call
    has_permission(auth, target, permission) inside any pre/post expression. The
    target is erased to Any so one evaluator serves every domain type by
    downcasting. Secure default: with no evaluator registered, every permission
    is denied.
  • #[pre_filter] / #[post_filter] — collection filtering (Spring's
    @PreFilter / @PostFilter). #[post_filter(element.owner == auth.principal)]
    retains only the elements of the returned collection the predicate accepts;
    #[pre_filter(items, …)] filters a named owned mut collection argument
    before the body. element is the per-element &T (Spring's filterObject);
    no ambient context denies the call with Unauthenticated.

Known limitations (roadmap)

  • PermissionEvaluator is a process-global set-once registry (one evaluator per
    process, like Spring's single bean); there is no per-scope override.
  • #[pre_filter] requires the targeted parameter to be an owned mut
    collection with retain (e.g. mut items: Vec<T>).

v26.6.31

19 Jun 16:58
ce1311a

Choose a tag to compare

Spring Security parity — Tier 2: the web authentication mechanisms. The
classic browser/login surface from Spring's HttpSecurity, built on the Tier 1
authentication spine. All additive (no behaviour change to existing code).
Adversarially reviewed before release; the review's six confirmed findings are
fixed in this release.

Added

  • HTTP Basic (httpBasic())HttpBasicLayer reads
    Authorization: Basic … and authenticates through the AuthenticationManager
    spine. An absent header passes through (so a session/bearer layer can take
    over); an invalid or malformed one is rejected with 401 and a
    WWW-Authenticate: Basic realm="…" challenge (configurable realm, pluggable
    BasicAuthenticationEntryPoint) — Spring's BasicAuthenticationFilter.
  • Form login (formLogin())form_login_routes mounts POST /login
    (url-encoded username + password), rotates the session id on success
    (anti-fixation) before persisting the context through a
    SecurityContextRepository, then redirects. Success/failure responses are
    swappable (FormLoginSuccessHandler / FormLoginFailureHandler), and the
    success path is saved-request-aware.
  • Remember-me (rememberMe())TokenBasedRememberMeServices mints a
    signed, expiring cookie token whose signature is an HMAC-SHA256 keyed by a
    server secret over the username, expiry, and the user's stored password hash:
    a password change, an expired clock, a tampered token, or the wrong key all
    reject. New trust-level methods on Authentication
    is_remembered() / is_fully_authenticated() (+ REMEMBERED_CLAIM) — so a
    remembered context is authenticated but not fully authenticated (Spring's
    isFullyAuthenticated()), and a sensitive route can demand a fresh login.
  • RequestCache / SavedRequestHttpSessionRequestCache remembers the
    page an unauthenticated user wanted; form login returns them there after
    login instead of the default target (Spring's
    SavedRequestAwareAuthenticationSuccessHandler). Only same-origin targets
    are honoured (SavedRequest::is_safe_redirect): a protocol-relative,
    backslash-tricked, absolute, or control-char target falls back to the
    configured success URL, so the login flow can't be turned into an open
    redirect. NullRequestCache for stateless surfaces.
  • SessionCreationPolicyAlways / IfRequired (default) / Never /
    Stateless (Spring's sessionManagement().sessionCreationPolicy(...)).
    SessionAuthenticationLayer::session_creation_policy(...) installs the implied
    SecurityContextRepository; Stateless uses the null repository (no session
    context) for token-only APIs.
  • Multiple filter chainsSecurityFilterChains routes each request to the
    first chain whose RequestMatcher (AnyRequestMatcher /
    PathRequestMatcher, segment-aware, optional method) matches, so a
    locked-down /api/** and a permissive web surface coexist (Spring's
    FilterChainProxy); an unmatched request passes through. The dispatcher
    honours tower's readiness contract for a backpressure-bearing inner service.

Known limitations (roadmap)

  • TokenBasedRememberMeServices is the stateless of Spring's two
    remember-me strategies: a captured cookie replays for the full validity window
    (default 14 days) until the embedded expiry passes or the user's password hash
    changes — there is no per-token series/rotation theft detection (Spring's
    PersistentTokenBasedRememberMeServices) and no server-side revocation list.
    Use a short token_validity_seconds and serve the cookie HttpOnly + Secure
    • SameSite. A persistent/series variant is a follow-up.
  • RequestCache::save_request is provided for an authentication entry point to
    call before redirecting to login; wiring an entry point that auto-saves the
    request is left to the application (the consume side is wired into form login).

v26.6.30

19 Jun 11:43
d9394ed

Choose a tag to compare

Spring Security parity — Tier 1: the authentication spine. The core of
Spring Security's authentication architecture, the foundation later tiers build
on. All additive (no behaviour change to existing code).

Added

  • Authentication manager spineAuthenticationManager / ProviderManager
    / AuthenticationProvider (Spring's authentication architecture). An
    AuthenticationRequest (UsernamePassword / BearerToken, #[non_exhaustive])
    is resolved by the first supporting provider; BearerTokenAuthenticationProvider
    adapts the existing Verifier into the spine.
  • UserDetails + DAO authenticationUserDetails (with the four Spring
    account-status flags), UserDetailsService, UserDetailsChecker /
    AccountStatusUserDetailsChecker, InMemoryUserDetailsService, and
    DaoAuthenticationProvider (an enumeration-safe username/password provider:
    unknown user and wrong password both fail as Bad credentials with comparable
    bcrypt work).
  • DelegatingPasswordEncoder — Spring's recommended {id}-prefixed password
    storage ({bcrypt}/{argon2}/{noop}), upgrade_encoding for re-hash-on-login,
    and seamless migration of legacy bare hashes; plus NoOpPasswordEncoder.
  • SecurityContextRepository — the pluggable between-request context store
    (HttpSessionSecurityContextRepository default, NullSecurityContextRepository
    for stateless surfaces). SessionAuthenticationLayer now loads the context
    through a swappable repository instead of a hardcoded session key; added
    Authentication::is_authenticated().
  • AuthenticationEventPublisherAuthenticationEvent::{Success,Failure}
    published by ProviderManager for every outcome (LoggingAuthenticationEvent- Publisher default).
  • Pluggable AuthenticationEntryPoint / AccessDeniedHandler (Spring's
    ExceptionTranslationFilter seam) — FilterChain renders its 401/403
    through them, defaulting to the canonical problem+json and overridable via
    with_authentication_entry_point / with_access_denied_handler.

Known limitations (roadmap)

  • ProviderManager continues to the next supporting provider after a failure
    (Spring rethrows an AccountStatusException immediately). With one provider
    per credential kind — the norm — the outcome is identical; a terminal/continue
    error taxonomy is a follow-up.
  • DelegatingPasswordEncoder::upgrade_encoding compares the stored {id} only
    (algorithm migration); it does not yet flag a within-algorithm work-factor
    increase for re-hash.
  • with_defaults() registers {noop} (plaintext — dev only, as Spring does)
    and verifies legacy unprefixed hashes as bcrypt to ease migration; disable
    the latter with with_unprefixed(None) for Spring's stricter reject-on-bare
    behaviour.

v26.6.29

18 Jun 22:24
f64c998

Choose a tag to compare

A Spring Security 6 parity increment (Tier 0): an adversarially-verified
audit of the security tier against Spring Security 6 / Spring Boot 3, followed
by the hardening pass that closes the silent semantic divergences in shipping
code, plus the two Spring Security 6.4 passwordless mechanisms. See the new
Spring Security Parity book appendix for the full coverage matrix.

Added

  • One-time-token (magic-link) login — Spring 6.4 oneTimeTokenLogin():
    OneTimeTokenService (single-use, expiring; in-memory impl) +
    OneTimeTokenGenerationSuccessHandler for out-of-band delivery +
    ott_login_routes (POST /ott/generate, GET /login/ott) that redeems a
    token, rotates the session id, and establishes the security context.
  • WebAuthn / passkeys — Spring 6.4 webAuthn(): a feature-gated webauthn
    module with the registration and authentication ceremonies over webauthn-rs
    and a pluggable credential repository (opt-in; off by default).
  • EC + EdDSA JWKS keysJwksVerifier now verifies ES256/ES384 and
    EdDSA tokens in addition to RSA (RS*/PS*).
  • FilterChain::try_layer / CorsLayer::try_new — fallible builders
    that surface invalid glob patterns / unsafe CORS config as a recoverable
    error instead of panicking at startup.
  • Configurable clock-skew (clock_skew_seconds, default 60s) and nbf
    validation
    on JwksVerifier and JwtService.
  • A Spring Security Parity appendix in the book (EN + ES).

Changed (Spring-faithful defaults — each with an escape hatch)

  • Method security works behind every authentication mechanism.
    SessionAuthenticationLayer now scopes the task-local security context, so
    #[pre_authorize] / current_authentication() work for session- and
    OAuth2-login-authenticated callers (previously bearer-only).
  • hasRole('X') matches the ROLE_X authority (Spring's prefix) as well as
    a bare role name.
  • HSTS is sent only over secure requests by default
    (hsts_include_insecure to force it).
  • The CSRF cookie is Secure only when the request is secure
    (CookieSecure::{Auto,Always,Never}, default Auto).
  • A wildcard CORS origin with allow_credentials is rejected at
    construction.
  • JWT/JWKS validation tolerates 60s clock skew (was zero).
  • Path-prefix authorization is segment-awarepermit("/api") no longer
    matches /api-internal.

Fixed (security)

  • OIDC id_token is never trusted without validation — the login fails if
    it cannot be verified, instead of silently falling through to userinfo.
  • Bearer rejections carry an RFC 6750 WWW-Authenticate: Bearer challenge
    (error="invalid_token" when a token was supplied).
  • No user-enumeration timing oracle — an unknown username runs comparable
    bcrypt work to a wrong password (internal-db IdP).
  • Postgres SessionRegistry rows expire (opt-in absolute TTL + pruning, via
    with_ttl) so an orphaned session can no longer inflate the per-principal
    concurrency count. Pruning is off by default — a fixed TTL would wrongly
    evict still-active sliding sessions, so enable it only with an absolute
    session lifetime.

Known limitations (roadmap)

  • request_is_secure trusts X-Forwarded-Proto from any caller; deploy behind a
    trusted proxy (or terminate TLS in-process, which Firefly marks automatically).
    A trusted-proxy allowlist is planned.
  • WebAuthn authenticate/options reveals whether a username has registered
    passkeys; use discoverable (usernameless) credentials to avoid enumeration.
  • Sliding-session expiry isn't synced into the distributed SessionRegistry
    (no HttpSessionEventPublisher analog yet) — deregister on logout or set an
    absolute TTL.
  • One-time-token magic links are redeemed via GET (token in the URL);
    single-use + short expiry mitigate referer leakage.

v26.6.28

16 Jun 18:03
ec2ef67

Choose a tag to compare

A Spring Boot parity increment: the declarative HTTP-interface client — the
highest single value lever from the parity-gap analysis (it lifts the REST/HTTP
clients area off the floor).

Added

  • #[http_client] — a declarative HTTP-interface client, the analog of
    Spring 6's @HttpExchange (the modern OpenFeign replacement). Annotate a
    trait of methods with the same verb attributes a #[rest_controller]
    uses and the macro generates a <Trait>Impl that issues the requests over a
    WebClient — the mirror image of a controller.
    • Verbs: #[get("/path")] / #[post] / #[put] / #[delete] /
      #[patch] + generic #[request(method = "…")]. Path variables use the
      framework's :id syntax (same as the server macro); {id} is a compile
      error pointing at :id.
    • Argument binding needs no attributes in the common case: a name-matched
      :var arg is the path variable, the lone non-scalar arg on a body verb is
      the JSON body, the rest are query params (Option omits when None,
      Vec/&[_] repeat). Override with #[path] / #[query("k")] /
      #[header("X")] / #[body]. Every :var must bind exactly once or it is a
      compile error; an Option/Vec/slice path variable is rejected.
    • Return shapes: async fn -> Result<T, ClientError> (the ergonomic
      default), Result<T, E: From<ClientError>>, non-async Mono<T> / Flux<T>
      (returned directly; a Flux defaults Accept: application/x-ndjson), and
      WebClientResponse (the .exchange() escape hatch).
    • Construction: <Trait>Impl::new(base_url) or ::with_client(WebClient);
      the type is Clone. With #[http_client(... bean)] it is registered as a
      @Service and bound to dyn Trait, so #[autowired] Arc<dyn Trait>
      resolves (pulling a shared WebClient bean, named via client = "…").
    • Error fidelity (documented): an awaited Result<T, ClientError>
      surfaces every failure as ClientError::Problem (carrying a FireflyError
      with the original status/code, so the classifiers still work); the
      structured Transport/Decode/Encode/InvalidUrl variants survive only
      on the Mono/Flux return forms.
  • firefly_client::encode_path_segment — RFC 3986 path-segment
    percent-encoding (used by generated clients; also public).

The macro reuses the server #[rest_controller]'s verb-attribute grammar
(MappingAttr/VERBS/join_path), so client and server can't drift. Designed
via a scored 3-proposal panel and adversarially reviewed (the review caught a
runtime footgun — an Option path variable producing …/Some(x) URLs — now a
compile error). The firefly::prelude now also re-exports WebClient /
ClientError / new_web_client.

v26.6.27

16 Jun 18:03
b746625

Choose a tag to compare

A Spring Boot parity increment: declarative rollback rules on
#[transactional]. Chosen from a 16-area parity-gap analysis as the best
value-to-effort gap (the transaction runtime already supported it).

Added

  • #[transactional(no_rollback_for = "<pat>", rollback_only_for = "<pat>")]
    — declarative transaction rollback rules. Spring names exception types;
    because Rust's Result already separates failure from success, the Firefly
    analog names an error pattern. By default every Err rolls back; then:

    • no_rollback_for = "P"Spring's @Transactional(noRollbackFor = …):
      an Err matching pattern P commits instead of rolling back;
    • rollback_only_for = "P": roll back only when the Err matches P,
      committing the rest;
    • with both, no_rollback_for wins on overlap.

    The pattern is any Rust match pattern valid for the fn's error type (no if
    guard), alternatives included ("Error::A | Error::B"). The macro lowers to
    the already-present transactional_with / transactional_with_on runtime
    entry points (which take a should_rollback(&E) -> bool predicate), composes
    with manager = "…", and the generated predicate is matches!-based, so a
    pattern that does not fit the error type is a compile error.

    rollback_only_for is not named rollback_for: Spring's rollbackFor is
    additive (it widens the set of exceptions that roll back), but Rust has no
    checked/unchecked split — every Err already rolls back — so the faithful
    rule here is restrictive. Writing rollback_for is a friendly compile error
    pointing at the two rules above, so a Spring port can't be silently inverted.
    No runtime or API changes elsewhere.

v26.6.26

16 Jun 18:03
c835604

Choose a tag to compare

A correctness release. Every one of the 74 per-crate README.md files was
audited against that crate's actual shipped public API (43 confirmed fixes
across 28 crates), and the audit surfaced a real framework bug: the per-crate
VERSION constant was a hardcoded literal frozen at "26.6.24" instead of
tracking the crate version.

Fixed

  • VERSION no longer drifts from the crate version. Every crate's
    pub const VERSION was a hardcoded "26.6.24" string that nothing kept in
    sync with the workspace version — so firefly_kernel::VERSION, the actuator
    /actuator/version payload, and the startup banner all reported a stale
    release number, and the version_matches_crate_version guard tests only
    passed while the workspace happened to sit at 26.6.24. All 52 hardcoded
    constants now derive from env!("CARGO_PKG_VERSION") (the re-exporting
    crates already chained to firefly_kernel::VERSION), so VERSION is now
    always exactly the crate version and can never drift again. The cli
    FRAMEWORK_VERSION constant got the same treatment, and the handful of
    unit/integration tests that asserted VERSION == "26.6.24" against a frozen
    literal now assert against env!("CARGO_PKG_VERSION"), so the guard holds for
    every release instead of only when the workspace happened to sit at that
    number. (The CLI's render_for / SBOM-parser fixtures keep their literal
    sample versions — that string is arbitrary test data, not the build version.)

  • Phantom / incomplete public-surface docs. Documented APIs now match the
    source: admin's AdminDeps gained its environment field; openapi's
    RouteDef (4 missing fields: request_schema / response_schema /
    query_schema / pageable), Parameter::{query,header}, Builder::{add_schema, add_schema_descriptors, from_inventory, docs_router}, and the DocsConfig
    struct are now listed; orchestration's CompensationPolicy (now all six
    variants incl. GroupedParallel) and SagaError (all four variants);
    starter-web's WebStack::{set_security, set_exception_advice}; testkit's
    BuiltSlice::web_client; idp's Error enum + change_password signature;
    security, webhooks, kernel, transactional, plugins surface fixes.

  • Wrong signatures / variant names. The notifications-* READMEs used the
    wire spellings EmailStatus::SENT / FAILED; the Rust variants are Sent /
    Failed (#[serde(rename = "SENT")] only affects the JSON). plugins showed
    a Vec<Arc<dyn Any>> annotation that does not type-check against the real
    Extension = Arc<dyn Any + Send + Sync>. notifications-twilio,
    session-redis parameter names corrected.

  • Wrong facts. admin: the bean graph does ship dependency edges
    (one per autowired dependency), not "nodes-only". backoffice: the middleware
    order includes TraceContext (Problem → TraceContext → Correlation → Idempotency → BackOffice). resilience, starter-core, eda-kafka,
    session-postgres, session-mongodb, container (a warmform typo) fixes.

  • Stale version pins. Crate-README dependency examples that still pinned the
    long-stale 26.6.7 now use the self-maintaining minor pin version = "26.6"
    (the convention firefly / testkit / webhooks already used); example
    VERSION outputs updated to the release version.

  • firefly-cache doc comment. Removed the stale "once the Redis adapter
    ships in the next minor" note — firefly-cache-redis (RedisAdapter) has
    shipped and is a published workspace member.