The Messenger `DataPersister` discards the `$context` array on `persist` — it dispatches the raw data to the bus without attaching the context. This breaks any handler that requires context-aware behaviour (e.g. reading API Platform context keys like `resource_class`, `collection_operation_name`, or custom context values that flow through `ContextAwareDataPersisterInterface`). The `remove` path already solves the analogous problem by using a `RemoveStamp`.

Extend this approach: the `persist` method should wrap the data in an envelope and attach the context via a dedicated stamp (analogous to `RemoveStamp`) so that message handlers can retrieve the context from the envelope. The stamp must store the full `$context` array and expose it. Update the bus dispatch call in `DataPersister::persist` accordingly.


## Description
When a developer defines a custom OpenAPI response for an operation (e.g., by specifying a `responses` map on an `ApiResource` attribute or via the `openapi` configuration), only the description is typically provided. The expectation is that the API Platform OpenAPI factory will still automatically generate the response content (media type schemas) and links as it normally does, **unless** the custom response explicitly includes its own content or links.

Currently, if *any* custom response is present for a given status code, the generated content and links are completely omitted. This means the custom description is used, but the resulting OpenAPI specification is missing the expected schema and link information unless the developer manually reproduces them in the custom response.

## Desired behavior
When building an OpenAPI operation response for a status code:

1. If a custom response exists, it should be used as the base (preserving its description and any other explicit fields).
2. Content should be generated and attached to the response **only if** the custom response does **not** already have content (even an empty content map is considered an explicit override and should be kept as-is).
3. Links should be generated and attached to the response **only if** the custom response does **not** already have links.

This allows developers to provide just a custom description while all normal media type schemas and resource links are still filled in automatically.

## Impact
- Custom OpenAPI responses with only a `description` will now include the same generated content and links as the automatically generated responses.
- If the custom response already contains a `content` or `links` entry (including a deliberately empty one), those take precedence and nothing is generated over them.

fix(openapi): ability to override description in response (#7412)


**Title:** ApiProperty attribute values not applied when parent property metadata exists

**Description:**
When a resource property has an `ApiProperty` attribute and also inherits property metadata from a parent factory (e.g., through a decorated `PropertyMetadataFactoryInterface`), the attribute’s values (description, readability, writability, etc.) are ignored. The `AttributePropertyMetadataFactory::createMetadata` method returns the parent metadata unchanged.

**Observed behavior:**
- Calling `create` on a property that has both an `ApiProperty` attribute and a parent metadata entry returns the parent `ApiProperty` instance *unchanged*.
- Assertions that check for the attribute’s values (e.g., `getDescription()`, `isReadable()`) fail because the parent metadata is returned as-is.

**Expected behavior:**
The factory should merge the attribute’s non‑null values into the parent metadata and return a **new** `ApiProperty` instance that reflects those merged values.

**Additional context:**
`ApiProperty` is immutable – every `with*()` method returns a new instance. The current merging logic calls the `with*` method on the parent metadata object but discards the returned value, so the changes are lost. The fix must reassign the result of the `with*` call back to the variable that holds the evolving metadata.

**Note on types merging:**
The `types` property may require a separate fix (related to an upstream PR); it is acceptable to skip merging types in this change.

fix(metadata): property metadata merging (#4428)


**Bug Report: MongoDB ODM Partial Pagination Does Not Limit Documents Entering $facet**

**Observed Behavior**
When using API Platform's MongoDB ODM support with `pagination_partial: true`, the aggregation pipeline constructed by the pagination logic processes *all* documents that match the query before the `$facet` stage. This causes the database to buffer the complete result set, which defeats the memory and performance benefits that partial pagination is intended to provide.

**Expected Behavior**
When partial pagination is enabled (i.e., when the total item count is not needed), the pipeline should restrict the number of documents passed to the `$facet` stage. Specifically, a `$limit` stage should be added **before** `$facet` with a value of `offset + limit`, so that only the documents required to fulfill the current page are fed into the facet. This matches the conceptual flow where the skip/limit applied *inside* the facet only operates on an already-bounded set, and it avoids the overhead of processing unnecessary documents.

**Impact**
- Memory usage scales with the total collection size instead of the page size.
- Query latency increases unnecessarily, especially on large collections.
- The feature is effectively broken for its primary use case: efficient pagination without counting.

**Additional Constraints**
- The early `$limit` must *not* be applied when full pagination (counting) is in effect, because the count facet needs access to all matching documents.
- If the effective limit is zero or negative, no extra limitation should be introduced.
- The behavior should be symmetrical with how the ORM/ODM non-partial pagination already restricts results—this is purely about avoiding unnecessary work when counting is skipped.

fix(odm): partial pagination limit the documents entering $facet (#7822)


**Title:** Hydra JSON‑Schema collection items missing required `@id`/`@type` properties

**Background**
When API Platform Core generates JSON‑Schema (and OpenAPI) representations of Hydra resources, the root resource definition receives the three base Hydra properties: `@context`, `@id`, and `@type`. For collection schemas (i.e. when the schema’s top-level type is `array` and items are described by a `$ref` to a separate definition), the items definition currently receives none of these properties.

**Expected behavior**
- The definition used for **collection items** (`items.$ref`) **must** include `@id` and `@type` (with their usual `{ "type": "string" }` schema) as in the root case.
- The `@context` property **must not** appear in the items definition; it belongs only at the root level.

**Why this matters**
Downstream tooling (API documentation generators, client code generators, validator builders) that inspects the per‑item schema should see these core Hydra members. Their absence can cause incorrect assumptions about the item structure and missing documentation fields.

**Implementation notes**
- The work likely belongs in the Hydra‑specific `SchemaFactory` (the decorator that adds Hydra metadata).
- Obtaining the items definition key requires extracting the definition name from an `items.$ref` path, analogous to how `getRootDefinitionKey()` already works for `$ref`. The extraction logic currently only exists inline in `getRootDefinitionKey()` and would need to be reusable.
- The existing constant `BASE_PROPS` currently includes `@context`; a new “root‑only” set is needed so that items and root can be decorated differently.
- Keep schema version handling (JSON Schema `#/definitions/...` vs. OpenAPI `#/components/schemas/...`) consistent.

**Acceptance criteria**
- For a Hydra collection schema, the items definition’s `properties` contain exactly `@id` and `@type` (both `{"type": "string"}`), and **not** `@context`.
- The root definition still contains `@context`, `@id`, and `@type`.
- Non‑collection schemas are unchanged.
- Existing behaviour for other formats and non‑Hydra schemas is unaffected.

fix: add base Hydra properties to collection items definition in JSON Schema (#3803)


**Fix: SearchFilter must explicitly join inverse side of OneToOne associations**

When a SearchFilter targets a property that is the inverse side of a OneToOne relationship, Doctrine DQL rejects the query:

> "A single-valued association path expression to an inverse side is not supported in DQL queries. Use an explicit join."

The SearchFilter should automatically add an explicit join for such associations, just as ExistsFilter already does. Currently, an explicit join is only added for collection‑valued associations; the inverse‑side case for single‑valued associations (owning or owned) is missing.

Acceptance criteria:
- Filtering on the inverse side of a OneToOne association works without a Doctrine error.
- Filtering on the owning side of a OneToOne association is not affected (no unnecessary join is added – the existing path expression is sufficient).
- The fix must be consistent with the analogous handling already present in ExistsFilter.

fix(doctrine): handle inverse side of OneToOne association in Doctrine search filter (#4366)


In the OpenAPI specification generated by API Platform, the `summary` field of each operation always defaults to an empty string, while the `description` field defaults to a meaningful, autogenerated string (e.g., “Retrieves the collection of Dummy resources.”, “Creates a Dummy resource.”, “Replaces the Dummy resource.”). This discrepancy harms clients and tools (like Swagger UI) that rely on summaries.

**Desired behavior**
- For every operation (collection/item GET, POST, PUT, PATCH, DELETE, and sub-resource operations), when **no explicit `summary`** is specified in the operation’s `openapi_context`, the generated `summary` should use the **same generated string** currently used for the default `description` (e.g., “Retrieves a Dummy resource.”).
- If the developer explicitly provides a `summary` via `openapi_context` (including an empty string), that value must be used as-is and must override the default.
- The default `description` behavior must remain unchanged.

This ensures consistent, usable summaries out of the box while preserving full override control.

Make sure both, description and summary are generated (#3939)


```markdown
### Title
JSON:API flat pagination drops custom query parameters

### Description
When using the JSON:API format with **flat pagination** enabled (`pagination_enabled: true` on the resource and query parameter `page=N` instead of bracket-style `page[number]=N`), any additional query parameters that are not part of the JSON:API structure (e.g., a filter like `?city_id=3152`) are silently dropped from the `_api_filters` request attribute. As a result the underlying filter system never sees those parameters and the request produces unfiltered results.

### Expected behaviour
- All query parameters that are not JSON:API transforms (order, pagination, fieldsets, includes, etc.) must still appear in the `_api_filters` attribute and be available for filtering.
- Transformed JSON:API values must **take precedence** over any flat parameter with the same key.
- If the raw query string has already been parsed and stored in `_api_query_parameters` by an earlier listener (e.g., the `ParameterProvider`), that already-parsed set must be reused rather than re-parsed, to preserve any modifications done by that listener.

### Notes
- The problem occurs specifically when flat pagination is active and the request uses `page=N` without brackets.
- The JSON:API provider already transforms known JSON:API query parts into `_api_filters`; the missing piece is merging back the original (or pre-parsed) flat parameters that were not consumed by those transforms.
- A downstream consumer (`ReadProvider`) currently skips its raw-query fallback whenever `_api_filters` is set, so the JSON:API layer must ensure the attribute is complete.
```

fix(jsonapi): keep flat custom params with flat page (#8216) (#8217)


```
Issue: Symfony DI injects a `RewindableGenerator` (which implements `iterable`) when tagging multiple HTTP client services, but our HTTP cache purgers require a plain `array` for the `clients` constructor argument. This leads to a type error at runtime.

Classes affected:
- `ApiPlatform\HttpCache\VarnishPurger`
- `ApiPlatform\HttpCache\VarnishXKeyPurger`
- `ApiPlatform\HttpCache\SouinPurger`
- `ApiPlatform\HttpCache\SurrogateKeysPurger`

All of these classes currently type-hint the `clients` parameter as `array` (and the `@param` PHPDoc says `HttpClientInterface[]`). Because they iterate over the clients with `foreach` or `yield from`, they already work with any `Traversable`. We just need to widen the accepted type from `array` to `iterable` everywhere the clients collection is injected, including in the parent class `SurrogateKeysPurger`.

Acceptance criteria:
- The constructors of the four named classes accept `iterable` instead of `array` for the `clients` parameter.
- The `clients` property in `SurrogateKeysPurger` and `VarnishPurger` is typed as `iterable`.
- Existing PHPDoc annotations are updated to `HttpClientInterface[]|iterable` (or simply reflect the widened contract).
- No change in runtime behavior when an array is passed; passing a `RewindableGenerator` must work without errors.
```

fix(symfony): wrong purger clients type (#5373)


**Title:** Remove legacy `collection_operation_name` context key usage

---

**Background**
Earlier versions of API Platform set a `collection_operation_name` key in the serialization/pagination context for collection operations. This has been superseded by the generic `operation_name` key (and `graphql_operation_name` for GraphQL). Most of the framework has already been updated, but a few spots still reference the old key, leading to unnecessary double‑checking and potential misbehavior when `operation_name` is present but `collection_operation_name` is not.

**Problem**
The `PaginationExtension` (Doctrine ORM) currently inspects `$context['collection_operation_name']` as well as `$context['operation_name']` when deciding whether to use the collection‑specific pagination fetch join and output walker settings. Because `collection_operation_name` is no longer set anywhere, this dead check can cause the extension to miss the presence of an operation and fall back to a generic decision even when a collection operation is active. The same legacy key may appear in serialization‑related context code or its tests, creating confusing noise and potential inconsistency for developers reading or extending the serialization pipeline.

**Expected Outcome**
1. The `PaginationExtension`’s private methods for “fetch join collection” and “use output walkers” should **only** react to `$context['operation_name']` (and `$context['graphql_operation_name']` for GraphQL). The `collection_operation_name` check should be dropped entirely.
2. Any remaining place that populates or expects `collection_operation_name` in the serialization context (e.g., `SerializeListener` or the serializer context builder) should be cleaned up so that the legacy key is never produced or required. The context should consistently use `operation_name` for all operation‑aware consumers.

**Constraints & Edge Cases**
- The change must not affect the behavior of GraphQL operations: `graphql_operation_name` should keep working exactly as before.
- If no operation name is present in the context, the fallback behavior (reading the setting from the entity metadata or query hints) must remain unchanged.
- Any internal invariants that rely on the existence of *some* operation key in the context to apply per‑operation settings must still trigger correctly for both REST and GraphQL operations.
- The fix should be a clean removal of the legacy key; no new abstractions or configuration options are needed.

fix: remove collection_operation_name legacy key


**Title:** JSON Schema factory leaks operation-level deprecation into nested resource schemas

**Background**
When a resource declares a deprecated operation (e.g., a `PUT` with a `deprecationReason`), the JSON Schema generation code currently sets `deprecated: true` unconditionally on the resource's schema definition when an operation is available. This is appropriate when the schema is used directly for that operation's request/response, but it incorrectly leaks onto the resource *shape* when the same resource class is embedded as a property in another resource's schema.

**Reproduction scenario**
1. A resource `DeprecatedPutUser` defines several operations, including a PUT with `deprecationReason: 'Use PATCH instead'`.
2. Another resource `DeprecatedPutUserAction` has a property whose type is `DeprecatedPutUser`.
3. When the OpenAPI documentation (`/docs?format=openapi`) is requested, the JSON Schema for `DeprecatedPutUser` that is referenced from within the `DeprecatedPutUserAction` property contains `deprecated: true`.

**Expected behavior**
- The nested resource schema (`DeprecatedPutUser` as a property) must **not** be marked as deprecated.
- Only the operation/path entry corresponding to the actual deprecated PUT operation (in the OpenAPI paths) should carry the deprecation annotation.

**Current behavior**
- The JSON Schema factory assigns `deprecated: true` to the sub-schema because an operation object is present. However, when building a sub-schema for a nested object, the operation is typically *guessed* by the factory (e.g., via `findOperation`) rather than being explicitly provided by the caller, and the operation-level deprecation should not be transferred onto the resource's shape.

**Constraints / Safety**
- The fix must not prevent the deprecated flag from appearing on schemas that are genuinely requested for a specific, explicitly provided operation (e.g., a direct request/response schema for that operation).
- The fix should only affect the JSON Schema generation path, not the OpenAPI path-level deprecation.
- The `FORCE_SUBSCHEMA` serializer context flag (already used by the factory to denote that a schema is being built as a nested sub-schema) is the right signal to distinguish the two cases. If an operation was not explicitly provided **and** the `FORCE_SUBSCHEMA` context is active, the deprecation flag must not be set.
- All other output formats (JSON-LD/Hydra, JSON:API) that delegate to this factory should automatically benefit from the correction.

**Acceptance criteria**
- The OpenAPI specification's `components.schemas` for `DeprecatedPutUser` (when accessed as a nested property) does not contain the `deprecated` property.
- The deprecated PUT operation still appears with the `deprecated: true` marker in the `paths` section of the spec.
- Existing tests for operation-level deprecation in OpenAPI documents remain green.

fix(jsonschema): don't leak operation deprecation onto sub-schemas (#8289)


Bug: `ResourceMetadataCollection::getOperation()` returns wrong operation due to cache not respecting `$forceCollection` and `$httpOperation`

When calling `ResourceMetadataCollection::getOperation()` with different values for `$forceCollection` or `$httpOperation`, the second call may return the operation cached by the first call instead of the correct one. For example:

```php
$collection = new ResourceMetadataCollection(MyResource::class, [...]);
$item = $collection->getOperation(null, false);       // expecting non-collection operation
$coll = $collection->getOperation(null, true);         // expecting collection operation
// $coll may actually return the same as $item
```

The same problem occurs when toggling the `$httpOperation` flag, and when mixing HTTP and GraphQL lookups. The issue is that the internal operation cache uses only the operation name and the HTTP/GraphQL prefix, ignoring the `$forceCollection` and `$httpOperation` flags. This causes the cache to alias calls with different flag combinations, leading to incorrect operation objects being returned.

Additionally, the `Operations` constructor unconditionally sets an operation's priority from the array key even when the operation already has a custom priority set. This can silently override an explicitly assigned priority.

Expected behavior:
- `getOperation()` should correctly return the appropriate operation for every distinct combination of `$operationName`, `$forceCollection`, and `$httpOperation`, without cache pollution between different argument sets.
- The `Operations` constructor should preserve an existing priority on an operation; it should only auto-assign a priority from the integer key if the operation does not already have one.

fix(metadata): getOperation cache matches arguments (#5215)


```
Bug: Hydra documentation endpoint produces non-compliant JSON‑LD context

Details
-------

When consuming the Hydra API documentation endpoint (e.g. a GET to `/docs.jsonld`), the generated JSON‑LD `@context` does not include the standard Hydra context (`http://www.w3.org/ns/hydra/context.jsonld`). Instead it emits only a custom context that redundantly defines Hydra terms such as `expects` and `returns`, and uses `#`‑prefixed compact IRIs for operation targets (e.g. `"returns": "#Dummy"`).

According to the Hydra Core Vocabulary specification the standard context must be present. The standard Hydra context already defines `hydra:expects` and `hydra:returns`, so they should not be redefined in the local context. When the standard context is present, the values of `returns` and `expects` should be plain short‑names (e.g. `"Dummy"`) rather than `#`‑prefixed names (e.g. `"#Dummy"`), because they expand relative to the base defined by `@vocab`.

Impact
------
Hydra clients that rely on the standard context may fail to parse the documentation vocabulary correctly, and the `#`‑prefixed values lead to incorrect term expansion.

Expected behaviour
------------------
- The documentation JSON‑LD `@context` should start with the standard Hydra context URI as the first element of an array.
- The local context must not redefine terms that already exist in the standard Hydra context (in particular `expects` and `returns`).
- All `returns` and `expects` values inside operation descriptions should use the unprefixed short‑name (e.g. `"Dummy"` instead of `"#Dummy"`).
- The entry‑point’s `returns` value should change from `"#EntryPoint"` to `"EntryPoint"`.

The existing `ContextBuilder` in the JSON‑LD layer already knows how to use the standard Hydra context in resource contexts. It would make sense for the documentation normaliser to follow the same approach and reuse the same constant so the documentation stays aligned with how other parts of the API handle Hydra contexts.
```

fix(hydra): hydra context changed (#6710)


**Title:** Cursor pagination on `DateTime` fields throws "Object of class DateTime could not be converted to string"

**Description**
When using API Platform’s cursor-based pagination (`paginationViaCursor`) with a field of type `DateTime` or `DateTimeImmutable` (or any `\DateTimeInterface` implementation), the API returns a 500 error. The underlying PHP error is: `Object of class DateTime could not be converted to string`. The error occurs when the Hydra `hydra:view` links (`hydra:next` / `hydra:previous`) are being built, because the cursor value for the date field is not properly formatted before being embedded in the URL.

**Expected behavior**
- The API should not throw an error.
- The generated `hydra:next` and `hydra:previous` URLs should contain the date-time value formatted as an ISO 8601 string (e.g., `2024-01-03T12:00:00+00:00` or `2024-01-03T12:00:00Z`). This is the standard, unambiguous representation for date-time values in HTTP APIs and aligns with the existing `DateFilter` serialization expectations.
- The fix must cover any field value that implements `\DateTimeInterface`, including both mutable `\DateTime` and immutable `\DateTimeImmutable`.

**Example scenario**
1. Define a resource with a `DateTimeImmutable` property (e.g., `createdAt`).
2. Configure cursor pagination on that field, e.g.:
   `#[GetCollection(paginationViaCursor: [['field' => 'createdAt', 'direction' => 'DESC']])]`
3. Send a `GET` request with `Accept: application/ld+json` to the collection endpoint.
4. Observe: the response should be a 200 with a valid `hydra:view` containing a `hydra:next` link, and the link’s query parameter should carry the date in ISO 8601 format.

**Acceptance criteria**
- No PHP `Object of class DateTime could not be converted to string` error when a cursor field is a `DateTimeInterface` object.
- Cursor values in pagination URLs are formatted as ISO 8601 (e.g., using `DateTimeInterface::ATOM` or an equivalent format with timezone offset/UTC designator).
- Existing cursor pagination behaviour for non-date fields remains unchanged.

fix(hydra): format datetime fields as iso 8601 in cursor pagination urls (#8241)


```
When generating JSON Schema for an API resource property typed as a
`BackedEnum` with integer backing (e.g. `enum Foo: int`), API Platform
currently emits `"type": "int"`.  JSON Schema expects `"integer"` for
integer values, so the generated schema is invalid.  Please fix the
enum schema generation so that int‑backed enums produce
`"type": "integer"` instead of `"int"`.  String‑backed enums should
continue to use `"string"` as before.
```

fix(jsonschema): fix invalid "int" type to "integer" (#6049)


The `FilterEagerLoadingExtension` corrupts DQL when a property name suffix matches a query-builder alias. For example, if an entity has a property named `secondaryColors` and the query contains a join with alias `colors`, the alias‑rewriting step inside `getQueryBuilderWithNewAliases` uses a naive string replacement that sees `colors.` inside `secondaryColors.` and replaces it with the remapped alias. This produces an invalid DQL path.

Make alias replacement token‑aware: it must replace an alias (including its trailing dot) only when the alias appears as a standalone identifier—i.e., when it is not part of a larger property name. A property name that merely *ends* with the same characters as an alias must be left intact.

The behavior should be consistent across join conditions, join strings, and the `WHERE` clause of the cloned query builder.

fix(doctrine): FilterEagerLoadingExtension breaks DQL if property name endings matches with query builder aliases (#4592)


### Bug: Doctrine persists new related entities instead of referencing existing ones when using DTOs with ObjectMapper

When an API resource is configured with a DTO mapped to a Doctrine entity (via `stateOptions` pointing to an `entityClass` and `#[Map]` annotations), any relation field that receives an IRI pointing to an existing entity causes the `PersistProcessor` to try to persist the related entity as a new object. This happens even when the related entity already exists in the database and should simply be assigned by reference.

**Observed behavior**:
- POSTing a new resource with a relation field containing an IRI (e.g., `"category": "/categories/42"`) results in either a duplicate entity (if unique constraints fail) or an unintended INSERT of the related entity.
- The related entity appears as a lazy proxy object on the owning side, but the `EntityManager` does not treat it as a reference; it tries to cascade-persist it.

**Expected behavior**:
- The related entity should be recognized by its identifier (extracted from the IRI or the object after mapping) and replaced with a proper Doctrine reference (`EntityManager::getReference`) before the owning entity is persisted.
- This should work for all write operations that can map (POST, PATCH, etc.) when the ObjectMapper is used.

**Context**:
- API Platform Core’s `PersistProcessor` handles the persistence step.
- The ObjectMapper integration maps DTOs to entities, but the resulting relation properties may be unmanaged lazy objects that are not yet part of the `EntityManager`’s unit of work.
- The same replacement logic may already exist when the data has been newly mapped by the ObjectMapper in one code path, but it is not applied consistently after the mapping step.

Please ensure that the fix does **not** break plain-entity POSTs (without DTOs) where cascade persist should still function normally. The relation cleanup should only be applied when the operation is mapped by the ObjectMapper (`Operation::canMap` semantics).

fix(doctrine): post with mapped relation


We need to produce a short, realistic implementation request that describes the bug and expected fix without revealing the exact patch or test details. The request should naturally point to the MainController's __invoke method and the Error operation handling, as these are well-known public surfaces in API Platform's Symfony integration. The description should be concise and behavioral, focusing on the observable crash and the invariant that Error operations should not require URI variable extraction.

**Bug report / implementation request:**

**Title:** MainController crashes when a provider changes the operation to an Error without URI variables

**Description:**
When a state provider modifies the `_api_operation` attribute to an `Error` instance (for example to signal a 404), the main controller's `__invoke` method still tries to extract URI variables from the request for that error operation. This extraction step can fail because Error operations often do not have URI variables defined, and the thrown exception is incorrectly caught and logged instead of being ignored. That can lead to a crash or broken response.

Additionally, if `initializeOperation()` returns `null` (meaning the request does not map to any API operation), the controller continues without a valid operation, which causes a type error further down.

**Expected behavior:**
- Skip URI variable extraction entirely when the operation is an `Error`, both before the provider is called and after the provider possibly replaces the operation.
- If `initializeOperation()` returns `null`, throw a clear `RuntimeException` early instead of proceeding with an invalid operation.

This ensures Error operations can be used safely in providers without requiring URI variables on the error operation.

fix(symfony): error in provider without uri variables (#6005)


**Title:** Eager loading with `fetchPartial` ignores per-property serializer group overrides

**Problem:**
When `fetchPartial` is enabled, the Doctrine eager loading extension uses the root resource’s serializer groups to determine the partial SELECT columns for every eagerly loaded relation. If a relation defines its own serialization context—for example, via `normalizationContext` or `denormalizationContext` with a different `groups` value on the association’s attribute metadata—that override is ignored. This causes partial fetches to include the wrong columns (or miss required ones) for relations that switch groups.

**Expected behavior:**
For each eager-loaded association, the partial SELECT should respect the effective serializer groups after resolving property-level serialization contexts (when a serializer `ClassMetadataFactory` is available). If an association’s metadata specifies a custom normalization/denormalization group mapping for the active context groups, the partial select fields should be computed from those overridden groups.

**Constraints:**
- The behavior must work recursively: when a relation’s target entity itself has eager-loaded relations, the group resolution continues to propagate correctly.
- Only relationships that actually have a group override should be affected; relations without a property-level serialization context should continue to use the caller’s groups unchanged.
- The `serializer_groups` shorthand must be expanded into explicit `normalization_groups` / `denormalization_groups` as needed, since a per-property context may differentiate between normalization and denormalization.

**Relevant area:**
The eager loading extension in `src/Doctrine/Orm/Extension/` (the `joinRelations` method and the partial select path).

fix(doctrine): partial fetch when relation switches context (#7645)


When a collection property is typed with multiple key types (for example `Collection<int|float, RelatedDummy>`), the serializer rejects some valid keys during denormalization. A key is compared only against the first key type in the union, so a `float` key like `1.0` causes a `NotNormalizableValueException` even though the collection advertises `int|float`. The type check should accept a key if it matches *any* of the declared key types, not just the first. The fix belongs in the collection-denormalization path and must still throw when the key matches none of the declared types, as well as preserve the existing behavior when no key-type constraint is specified.

fix(serializer): fix union types on collection denormalization (#6192)


When `enable_swagger_ui` is set to `false` in the API Platform configuration, the Swagger UI HTML documentation endpoint should be disabled while the OpenAPI JSON documentation remains accessible. Currently the flag is ignored — the Swagger UI pages are still served and all Swagger‑UI‑related services are loaded.

Fix the behavior so that:
- With `enable_swagger_ui: false`, any request that would normally be handled as the Swagger UI HTML docs (e.g., requests with `Accept: text/html` to the documentation URL) results in a 404 response with a clear message.
- The OpenAPI JSON endpoint continues to work normally when Swagger UI is disabled.
- The Symfony dependency‑injection container does not register Swagger‑UI‑only services (e.g., the UI processor, provider, context, and related wiring) when `enable_swagger_ui` is `false`. Complete removal of those services is expected.

fix(symfony): disable Swagger UI and keep openapi.json (#7549)


Bug report:

**Title**: QueryParameterValidateListener ignores `_api_platform_disable_listeners` and misses item GET operations

**Description**
When the `QueryParameterValidateListener` uses a validation provider (any `ProviderInterface` for query parameter validation), it does not respect the `_api_platform_disable_listeners` request attribute. Even when that attribute is set to `true`, the listener still invokes the provider and validation runs.

Furthermore, only collection GET operations are validated. Single-item GET operations (e.g., fetching one resource) are skipped entirely, even when the operation has query parameter validation enabled and the request is a safe GET.

**Expected behavior**
- If `_api_platform_disable_listeners` is `true`, the listener must return immediately without performing any validation.
- Query parameter validation should be performed for *any* safe HTTP GET operation, not only for operations implementing `CollectionOperationInterface`. An individual item GET (an `HttpOperation`) with validation enabled should be validated as well.

**Steps to reproduce**
1. Configure a resource with a single-item GET operation that has query parameter validation enabled, and ensure the listener is backed by a `ProviderInterface` that performs validation.
2. Send a GET request with query parameters to that endpoint – the validation is not triggered.
3. Set the request attribute `_api_platform_disable_listeners` to `true` on a collection GET request – validation still occurs (the listener’s early provider path bypasses the check).

fix: reorder early return QueryParameterValidateListener (#6300)


# Bug: multiple `#[ApiResource]` declarations on one class are not exposed in Hydra outputs

When a PHP class carries more than one `#[ApiResource]` attribute (for example, to publish the same entity under different URI templates or with different operation sets), **only the first declaration** appears in:

- the Hydra entrypoint (`/index.jsonld`)
- the Hydra documentation (`/docs.jsonld`)
- the JSON‑LD entrypoint context (`/contexts/Entrypoint`)

The remaining declarations are silently omitted. This breaks a supported pattern: the ability to define multiple, independently reachable resource representations for the same underlying class.

### Desired behavior

All `#[ApiResource]` declarations should be reflected in the three Hydra outputs. Every declaration’s collection operations (those implementing `CollectionOperationInterface` or `POST`) must show up as entrypoint links and as a separate Hydra supported class (with its own `hydra:title` and operations) in the documentation.

Because the Hydra format requires unique short names to distinguish entries, duplicates must be handled. **Duplicate short names** (two declarations with the same `shortName`) should be deduplicated automatically by appending a numeric suffix to the second and subsequent duplicates (e.g., `Book`, `Book2`, `Book3`). The operations inside those declarations must also inherit the deduplicated short name.

### Compatibility / deprecation

The automatic deduplication is a breaking behavior change. It should be **opt‑in** for now, controlled by a configuration flag. When the flag is not set, the framework should emit a deprecation warning that:

- mentions the resource class and the duplicated short name,
- states that automatic deduplication will become the default in API Platform 5.x,
- and points to the configuration option `defaults.extra_properties.deduplicate_resource_short_names` to enable it immediately.

When the flag **is** set to `true`, the deduplication must take effect and no deprecation should be triggered.

fix!(hydra): expose all ApiResource declarations in documentation and entrypoint (#7746)


```
Title: LegacyOpenApiNormalizer produces invalid 3.0 schema when downgrading from 3.1

The `LegacyOpenApiNormalizer` is responsible for converting an internal OpenAPI 3.1 document into a spec-compliant 3.0 document when `spec_version` is set to `3.0.0`. The current implementation only touches the direct properties of schemas listed under `components/schemas`, and even there it does not recurse into nested schema locations.

This results in invalid OpenAPI 3.0 output in several ways:

- **Path-level schemas are never downgraded.** Schemas within `requestBody`, `responses`, and `parameters` inside path items retain 3.1 syntax (e.g., array-valued `type`), producing an invalid 3.0 document.
- **Nested schema locations are ignored.** Inside a schema, only the top-level `properties` map is visited. Keywords that carry sub-schemas — such as `items`, `additionalProperties`, `allOf`, `oneOf`, `anyOf`, `not`, `if`/`then`/`else`, and nested object properties — are never recursed into. For example, `items: { type: ['string', 'null'] }` remains as an illegal array-typed declaration.
- **Nullable types are always rewritten to `anyOf`.** When a schema declares `type: ['string', 'null']`, the current code writes `anyOf: [{type:'string'}, {type:'null'}]` even when there is only one non-null type. OpenAPI 3.0 supports `type: 'string', nullable: true`, which many tooling ecosystems expect. The `anyOf` representation should only be used as a fallback when there are multiple non-null types.
- **`examples` → `example` conversion is not recursive.** The `examples` key is replaced with `example` only at the top level of direct properties; nested schemas that contain `examples` remain unconverted.

The required behavior: when running the normalizer with `spec_version` set to `3.0.0`, every schema appearing anywhere in the document (components, paths, and all nested locations) must be recursively downgraded to valid OpenAPI 3.0 form, using `nullable: true` for single-type nullable schemas and `anyOf` only for genuinely multiple non-null types.

Acceptance criteria / invariants:
- The final document contains no array-valued `type` in any schema.
- `type: ['<type>', 'null']` becomes `type: '<type>', nullable: true`.
- `type: ['<type1>', '<type2>', 'null']` becomes `anyOf: [{type:'<type1>'}, {type:'<type2>'}], nullable: true` (the `type` key is removed).
- A `type` array consisting only of `['null']` results in the `type` key being dropped and no `anyOf` added; `nullable` may be omitted or set to `true`.
- Every occurrence of `examples` (at any nesting depth) is replaced with `example`, copying the same value.
- Schemas embedded in `requestBody`, `responses`, and `parameters` under path items are also downgraded.
- Downgrade logic recurses through `properties`, `patternProperties`, `items`, `additionalProperties`, `allOf`, `oneOf`, `anyOf`, `not`, `contains`, `propertyNames`, `if`, `then`, and `else`.
```

fix(openapi): emit valid 3.0 schemas when downgrading from 3.1 (#8225)


Title: GraphQL item resolver rejects subclass instances of the declared resource class

Description:
When a GraphQL item query is configured on a parent resource class, and the data provider / read stage returns an instance of a subclass of that resource class, the resolver currently throws an `UnexpectedValueException` with the message “Resolver only handles items of class %s but retrieved item is of class %s”. This prevents the use of resource inheritance with GraphQL items, even though API Platform supports resource hierarchies elsewhere.

The resolver should accept items whose class is the declared resource class **or a subclass thereof**, in line with typical polymorphic handling in the API Platform stack. The error should only be thrown when the retrieved item is truly incompatible (neither the exact class nor a subtype).

fix(graphql): item resolver inheritance error  (#5910)


**Bug: `ContextStamp` is not serializable when context includes a request with an active session**

When API Platform’s Messenger integration dispatches a message asynchronously, the `ContextStamp` may include the current HTTP request in its context array. If that request has an active session (e.g. because the request touched session state), the `ContextStamp` becomes non-serializable and any attempt to send the envelope to an async transport fails with a serialization error.

**Expected behavior:**
The `ContextStamp` must always be serializable. When the provided context contains a Symfony `Request` object that has an active session, that request should be excluded from the stored context so the stamp can be safely serialized for async transport. If the request does not have a session, the current behavior (keeping the request) is acceptable.

**Impact:**
Async processing breaks entirely whenever a message is dispatched from a request that has started a session. This affects any setup using an async Messenger transport together with API Platform operations that require or interact with sessions.

fix(symfony): context not serializable when session (#6302)


The `PropertyInfoPropertyNameCollectionFactory` currently throws a `RuntimeException` when a resource class has no public properties (or no properties at all). This breaks API Platform for entities or objects that are valid but simply don't expose any properties through the Symfony PropertyInfo component. Instead of an exception, the factory should return an empty `PropertyNameCollection`.

The `getProperties()` call from Symfony's PropertyInfo component can return `null` for such cases. Treat that null as an empty array so the collection is created with zero items rather than throwing. The exception message "There is no PropertyInfo extractor supporting the class …" is misleading—the extractor supports the class, there are just no properties to extract.

Change the factory so that when `getProperties()` returns `null`, it produces an empty `PropertyNameCollection` instead of throwing.

Fix: Allow objects without properties (#3544)


We should support `\BcMath\Number` (introduced in PHP 8.4) as a property type in API resources. Currently, when an API resource has a property of type `\BcMath\Number`, the JSON-LD / JSON:API output renders it as an object rather than a scalar string, and the OpenAPI schema incorrectly describes it as an object. Input via `POST`/`PATCH` also fails or does not produce a `\BcMath\Number` instance.

**Acceptance Criteria**

1. **Serialization**: When an API resource property is typed as `\BcMath\Number`, the API must represent it as a plain string in all supported response formats (JSON-LD, JSON:API, HAL, etc.). For example, a `\BcMath\Number(300.55)` should appear as `"300.55"` in the JSON body.
2. **Deserialization**: The API must accept a string value for a `\BcMath\Number` property in request bodies (`POST`, `PATCH`, etc.) and properly convert it to a `\BcMath\Number` object internally.
3. **JSON Schema / OpenAPI**: The generated JSON Schema (and thus OpenAPI docs) for a `\BcMath\Number` property must declare `type: string` (with a suitable format, such as `string` or `number-string`), not an object or any other type.
4. **Symfony Serializer Integration**: The feature must work when using the Symfony Serializer. If the Symfony Serializer provides a `NumberNormalizer` (from symfony/serializer ≥ 7.3) but the FrameworkBundle does not automatically register it (e.g., FrameworkBundle < 7.3), API Platform should ensure the normalizer is registered so that serialization and deserialization behave correctly out of the box.
5. **Compatibility**: The implementation must be guarded so that it only activates when `\BcMath\Number` exists (PHP ≥ 8.4 with the bcmath extension). On unsupported PHP versions or without bcmath, there must be no effect and no runtime errors. The feature must not interfere with existing type handling or serialization of other custom types.

**Edge Cases & Safety**

- **Nullable properties**: A property typed `?\BcMath\Number` should correctly handle `null` values (omitted or `null` in JSON) and not emit a null string or cause errors.
- **Property identity**: The schema mapping should not accidentally alter the serialization of other classes that might have a similar name or relate to BCMath.
- **Backward compatibility**: Existing APIs using other numeric or custom types must remain unaffected.
- **Dependency boundaries**: The normalizer registration must check both the existence of the class and the absence of the service to avoid overriding an explicitly configured service.

The goal is that working with `\BcMath\Number` in API Platform feels just like working with scalar integer/float fields, without any extra configuration from the developer.

feat(serializer): render BCMath\Number (PHP 8.4+) as string instead of object (#7555)


**Summary:** Two related bugs cause incorrect behavior when using default metadata attributes and route prefixes in API Platform.

---

**Bug 1: Provided default attributes are silently ignored or cause crashes**

When I configure an `AttributesResourceMetadataCollectionFactory` with an explicit `$defaults` array that contains an `'attributes'` key (for example, to set default `cache_headers` on all operations across my application), those attribute defaults are not applied to the resulting `Operation` and `ApiResource` metadata. Instead the operations keep their own defaults, and my provided values are lost.

Further, if I include an attribute key that doesn't correspond to a real getter on `Operation` or `ApiResource` (e.g., a typo or an experimental key), I get a fatal error because the code blindly calls a `get` method without checking whether it exists.

**Expected behavior:**
- The caller’s `attributes` defaults should be applied when the corresponding value is not already set on the operation/resource.
- Unknown attribute keys should be gracefully skipped rather than throwing an error.

---

**Bug 2: Route prefix is ignored in OpenAPI path generation when no explicit route name is defined**

When I define an API resource (or operation) that sets a `routePrefix` but does **not** have an explicit `routeName`, the generated OpenAPI documentation completely omits the prefix from the path. For example, an operation declared with `uriTemplate: '/dummies'` and `routePrefix: '/prefix'` ends up with the path `/dummies` in the OpenAPI spec, while I clearly expect the path to be `/prefix/dummies`.

If an explicit route name is present, the route’s path (which already includes the prefix) is used, so the bug only surfaces when the route name is missing and the factory falls back to assembling the path from `uriTemplate`.

**Expected behavior:**
When a `routePrefix` is set and no `routeName` is available, the OpenAPI factory should construct the path as the concatenation of the prefix and the `uriTemplate`.

fix: use route prefix in OpenApiFactory and correctly manage defaults attributes (#4489)


The signature algorithm validation in s2n currently rejects client-side signature schemes based on the negotiated cipher suite's authentication method (e.g., restricting to RSA-only signatures when the cipher suite specifies RSA auth). This is incorrect: per the TLS protocol, the cipher suite's authentication method constrains the *server's* certificate and signature selection, but the client's signature algorithm does not need to match it. The restriction causes handshake failures when a client's certificate uses a signature type different from the cipher suite's auth method.

Adjust the validation so that the check against the cipher suite's auth method is only enforced when the connection is acting as a server. The client side should skip that check entirely—the only remaining requirement for the client is that at least one compatible certificate is configured.

Only check the auth method for signatures server side (#2434)


```
Add missing null pointer checks (defense-in-depth) to the TLS 1.3 secret derivation and key schedule code. Several functions in tls/s2n_tls13_secrets.c and tls/s2n_tls13_key_schedule.c dereference `conn->secure` and `conn->secure->cipher_suite` without first ensuring they are non-NULL. Although these pointers are not expected to be NULL during normal operation, the lack of checks could lead to crashes in unexpected edge cases.

Update these functions to validate that `conn->secure` and `conn->secure->cipher_suite` are non-NULL before use, returning an appropriate error (e.g., S2N_ERR_NULL) instead of dereferencing a NULL pointer.

Additionally, update the unit test for `s2n_tls13_secrets_get` to cover the cases where `conn->secure` or `conn->secure->cipher_suite` is NULL. The test should allocate a proper connection object, temporarily set these pointers to NULL, and verify that the function returns S2N_ERR_NULL.
```

Add some missing null ptr checks for defence in depth (#3596)


BUG: Server cipher suite negotiation does not check certificate compatibility, causing handshake failures.

Summary:
When the TLS server selects a cipher suite, it must only consider cipher suites whose authentication method matches the configured server certificate’s type (RSA or ECDSA). Currently, the server may pick an incompatible cipher suite, leading to an aborted handshake. For example, a server with an ECDSA certificate and a cipher preference that lists RSA-based ciphers ahead of ECDSA-based ones could select an RSA cipher, and vice‑versa. The server should skip any cipher suite whose authentication method is not compatible with the leaf certificate it holds.

Behavioral requirements:

1. Certificate‑type filtering in server cipher selection
   - When the server iterates over its cipher preference list in search of a cipher that the client also supports, it must additionally verify that the cipher suite’s authentication method (RSA vs ECDSA) matches the server’s certificate type (RSA_SIGN vs ECDSA_SIGN).
   - Only the currently configured certificate (the single leaf certificate) matters; the server supports one certificate chain.
   - If no compatible cipher can be negotiated, the handshake should fail normally (as it would for any other negotiation failure).

2. Example scenarios that must work after the fix:
   - Server has an RSA certificate. Its cipher preference is `test_ecdsa_priority` (ECDSA ciphers first, then RSA ciphers). The client offers both an ECDSA cipher and an RSA cipher. The server must select the RSA cipher, even though ECDSA ciphers appear first in its preference list.
   - Server has an ECDSA certificate. Its cipher preference is `test_all` (RSA ciphers first, then ECDSA). The client offers both. The server must select an ECDSA cipher.

3. Ancillary bug fix:
   The `test_all_ecdsa` built‑in cipher preference list has an incorrect `.count` calculation. It currently uses the size of `s2n_all_fips_cipher_suites[0]` instead of the ECDSA‑specific array element. Fix it to use the correct array so that the count accurately reflects the number of ECDSA cipher suites. This avoids potential memory errors when the list is used.

4. Non‑goals:
   - No changes to the public API or configuration formats.
   - No changes to client‑side cipher selection logic.
   - No new publicly visible cipher preference names are required (they may be added for testing convenience but are not part of the user‑facing fix).

The change should be made in the internal TLS/SSL state machine that manages server cipher suite negotiation, respecting the existing code conventions for error handling and boundary checks.

Add check for cert type when choosing cipher


**Issue: `s2n_recv_quic_post_handshake_message` cannot process multiple NewSessionTicket messages in QUIC**

When using s2n‑tls with QUIC support, calling `s2n_recv_quic_post_handshake_message` successfully processes the first incoming NewSessionTicket message, but any subsequent call for another post‑handshake message fails. The function does not properly reset its internal state after consuming a message, so future messages are never parsed, even when they are correctly delivered via the QUIC transport.

**Acceptance criteria / required observable behaviour**

1. After `s2n_recv_quic_post_handshake_message` returns `S2N_SUCCESS` for one NewSessionTicket, a later call (after more data is supplied on the QUIC stream) must be able to process another NewSessionTicket without tearing down the connection or forcing a re‑handshake.
2. The fix must not change the existing handshake‑message accumulation path. During the main QUIC handshake, `s2n_quic_read_handshake_message` relies on the handshake buffer retaining the parsed header so that the full handshake message can be assembled across multiple reads. That behaviour must be preserved as‑is.
3. After a post‑handshake message is successfully processed, any stale data from that message must be cleared from the connection’s internal buffers so that the next read starts in a clean state. No public API changes are required—only internal state cleanup.

**Non‑goal**
This change only addresses the QUIC post‑handshake path; the standard TLS receive path and the QUIC handshake read path should not be affected.

fix(quic support): Wipe buffers after reading post-handshake message (#5750)


**Title:** Align fingerprint digest API with s2n_blob and namespace the GREASE helper

**Summary:**
The common fingerprint module (`tls/s2n_fingerprint.c`) has a hash digest function that takes a raw `uint8_t*` buffer + length, and a grease-value check function that lacks the `fingerprint_` prefix. Both should be brought in line with the project’s conventions.

**Request:**

1. **Digest function signature**
   Change `s2n_fingerprint_hash_digest` to accept the output buffer as a `struct s2n_blob*` instead of `uint8_t* out` and `size_t out_size`. Internally, the function must use `out->data` and `out->size` when calling the underlying hash digest. Add a null-pointer guard for the blob parameter (in addition to the existing guards for `hash` and `hash->hash`).

2. **Update callers**
   All callers in `tls/s2n_fingerprint_ja3.c` and `tls/s2n_fingerprint_ja4.c` that currently pass a raw buffer to `s2n_fingerprint_hash_digest` must be adapted to prepare a properly initialized `s2n_blob` and pass its address. The digest output sizes (`MD5_DIGEST_LENGTH` / `SHA256_DIGEST_LENGTH`) remain unchanged.

3. **Rename the grease helper**
   Rename `s2n_is_grease_value` to `s2n_fingerprint_is_grease_value` so that all public fingerprint symbols consistently use the `s2n_fingerprint_` prefix, avoiding potential naming collisions. Update every reference to this function in the fingerprint source files to use the new name.

4. **Tests**
   The unit test for fingerprints should be updated to match the new API: use `s2n_blob_init` to create blobs, pass blob pointers to the digest function, and include an explicit null-blob safety check. The renamed grease function should be used consistently in its test assertions.

**Rationale:**
Using `s2n_blob` for buffer + length is the idiom chosen by the rest of s2n (e.g., stuffer operations), improving consistency and reducing the risk of size mismatches. The explicit null check makes the function’s contract clearer. The rename groups the grease predicate with the fingerprint module to which it logically belongs.

refactor: minor fixes for common fingerprint code (#4712)


```
Reduce allocations in s2n_ktls_sendv_with_offset when applying an offset to the iovec array.

Currently, whenever offset > 0, the entire iovec array is heap-allocated and copied, then modified. This allocation can be avoided in many common cases:
- If the offset aligns exactly with one or more iovec lengths, we can skip those entries entirely by advancing the pointer and reducing the count—no copy needed.
- If the number of remaining iovecs after skipping fully-offset entries is small (say 16 or fewer), use stack memory instead of heap allocation.
- When the offset equals the total data length, the call should send zero bytes without any allocation.

The existing validation contract must be preserved: offset beyond total data size still yields S2N_ERR_INVALID_ARGUMENT, etc.

Also fix the null-argument error codes in s2n_ktls_send_cb: passing NULL for io_context or buf should return S2N_ERR_NULL, not S2N_ERR_IO.
```

Reduce allocs in ktls app data send (#4181)


Per RFC 8446 Section 5.1, TLS 1.3 records must use TLS 1.2 (0x0303) as the `legacy_record_version` on the wire, regardless of the negotiated version, with the possible exception of the initial ClientHello (which may additionally use 0x0301). Make the following behavioral changes to s2n’s record layer:

1. **Outbound records**: When writing a record header, cap the protocol version at TLS 1.2 (33, `S2N_TLS12`) so that a TLS 1.3 connection never emits a record version field higher than 0x0303.

2. **Inbound record validation**: When `actual_protocol_version_established` is set and we validate the version from the incoming record header, compare against `MIN(actual_protocol_version, S2N_TLS12)` rather than the raw negotiated version. This ensures that a TLS 1.3 connection correctly accepts records carrying the legacy version 0x0303 and rejects a record carrying a version higher than TLS 1.2 (e.g., 0x0304).

The changes must keep existing version handling for older protocol versions intact (e.g., the cap to TLS 1.0 for certain cases elsewhere in the write path). Ensure the record layer still rejects records whose version does not match this adjusted expectation.

Freeze maximum record protocol version to 33 (TLS 1.2).


The project uses the deprecated `RAND_set_rand_method` to install a custom random generator (`s2n_openssl_rand_method`) in `s2n_init` and to restore the original in `s2n_cleanup`. Switch to the ENGINE API: register an ENGINE that provides the custom RAND method and set it as the default for RAND operations. Update the production override in `s2n_random.c` and the corresponding unit-test override to use ENGINE instead of the deprecated function. The custom generator must continue to supply bytes, pseudorand, and status callbacks; any unused callbacks can be removed. The engine must be properly initialised and, when the engine is installed as the default, the cleanup path should not attempt to manually restore the original random method.

Use the ENGINE API for overriding libcrypto rand


```
Title: TLS 1.3 handshake client key selection is hardcoded to the first offered curve

Summary:
In the TLS 1.3 handshake, the shared secret derivation step incorrectly always uses the client key corresponding to the first supported ECC curve, regardless of which curve the server actually negotiated. This causes handshake failures whenever the server chooses a curve other than the first one offered by the client. Additionally, the ECC private keys (both server and client) are not cleaned up after the handshake secrets have been derived.

Expected behavior:
- The shared secret must be computed using the client key that matches the server's negotiated curve. The code should search through the client’s offered key shares, find the one whose IANA curve ID matches the negotiated curve, and use that for the shared secret computation.
- After the TLS 1.3 handshake secrets (early/late, finished keys) have been successfully derived, the server and client ECC private key material should be securely freed (e.g., using the existing `s2n_ecc_params_free`). This prevents sensitive key material from lingering in memory longer than necessary.
- Error handling: if no matching client key is found (e.g., due to a protocol violation), the handshake should abort with an appropriate error (e.g., `S2N_ERR_BAD_KEY_SHARE`).

Observed behavior:
The current implementation hardcodes `selection = 0` in the shared secret computation function, always picking the first client key. For example, if the client offers both secp256r1 and secp384r1, and the server selects secp384r1, the handshake will still compute the shared secret using the client’s secp256r1 key. This results in mismatched shared secrets between client and server, causing the handshake to fail (e.g., integrity check failures, decryption errors).

Furthermore, the ECC private key structures (`server_ecc_params` and `client_ecc_params[]`) remain allocated after the handshake secrets are set up. That key material should be wiped at that point.

Impact:
- TLS 1.3 handshakes that negotiate any curve other than the first one in the client’s offer will fail.
- If the client offers multiple curves, server selection of a non-first curve is broken.
- Sensitive key material may persist in memory longer than necessary, increasing risk if a later memory disclosure vulnerability occurs.

Steps to reproduce (conceptual):
1. Configure s2n server to prefer/select a curve such as secp384r1 (IANA 24) over secp256r1 (IANA 23).
2. Initiate a TLS 1.3 handshake from a s2n client that offers both curves.
3. The handshake will fail during key derivation because the shared secrets do not match.
4. Diagnose by logging the `selection` variable in the shared-secret function – it will always be 0.

The fix should remove the hardcoded index, match based on the server’s negotiated curve, and add secure cleanup of the ECC parameters once they are no longer needed.
```

Remove TLS 1.3 client key selection hardcoding


**Title:** Integration tests cannot reliably send and verify data over the TLS connection

**Background**
The current Integration V2 test framework (under `tests/integrationv2/`) successfully negotiates TLS connections between s2n and other providers, and it checks for protocol version strings in the output. However, it does not exercise or verify actual data transfer — the tests only confirm that the handshake completed. This leaves a gap in coverage for the core echo functionality provided by `s2nc` and `s2nd` (reading from stdin, sending over TLS, receiving, and writing to stdout).

**Problem**
Adding data transfer to the existing tests is blocked by the subprocess I/O approach used in `ManagedProcess`. The test harness currently relies on `Popen.communicate()`, which sends a single chunk of data to stdin and then immediately closes the pipe. This premature stdin close causes some TLS implementations (notably OpenSSL’s `s_client`) to shut down the connection before the handshake is complete, making reliable data exchange impossible. There is also no mechanism to wait until the TLS handshake has finished before beginning data transfer; injecting data into stdin before the client/server is ready leads to protocol errors or dropped connections.

**Feature request**
Extend the integration test framework so that a test can:
1. Specify an arbitrary byte sequence to be sent from one side (typically the client) to the peer over the established TLS channel.
2. Have the framework wait until the process indicates that the handshake is complete (by printing a provider-specific “ready” marker to stdout) before starting to feed data into stdin.
3. Keep stdin open long enough for the data to be fully transmitted and for any echoed response to be collected, without the premature termination caused by `Popen.communicate()`.
4. Allow the test to assert that the sent data appears in the peer’s stdout (proving that the data was actually transmitted and echoed back).

**Additional considerations**
- **Echo mode** – The `s2nc` client must be invoked with the `-e` flag so it reads from stdin and echoes received data to stdout. `s2nd` should be configured to echo as well (this may require adjusting the flags passed to the server process).
- **Provider-specific readiness markers** – Each TLS provider outputs different text when the handshake completes (for example, S2N prints “Cipher negotiated:”, OpenSSL prints “Verify return code”, etc.). The mechanism must allow providers to supply their own marker string.
- **Data size and fragmentation** – To make the test more robust, the transmitted data should be large enough to span multiple TLS records / TCP segments (e.g., a few kilobytes), but still sized so that debug output from servers does not corrupt the expected data in stdout.
- **Non-ASCII bytes** – The data sent should purposely use bytes outside the ASCII printable range to avoid accidentally interacting with line‑based “connected commands” that some TLS clients may interpret from stdin.
- **Separation of concerns** – The handshake-readiness detection and non‑blocking I/O logic should be distinct from the provider setup, keeping the existing `ManagedProcess` threading and result collection intact while replacing the crude stdin drain with a more controlled communication loop.

**Acceptance criteria**
- The existing happy‑path integration tests (s2n as server, s2n as client) are updated to send data from the client and verify that the server’s stdout contains the exact same bytes.
- The new communication logic handles timeouts correctly and does not introduce deadlocks or flaky behavior, even under heavy load or with large data sizes.
- The same mechanism works across all supported providers (S2N, OpenSSL, BoringSSL) without special‑case branching in the core process manager.

Support sending data in one direction in integration tests. (#1827)


Title: TLS 1.3 early secret derivation ignores PSK

Currently `s2n_tls13_derive_early_secrets()` always computes the early secret as if no PSK were present (using a zero-filled PSK input to HKDF-Extract). This is incorrect when the handshake has selected a PSK (resumption or external), because the key schedule then diverges from the peer’s derivation and subsequent secrets (handshake, application traffic) will not match.

The fix:

- Modify `s2n_tls13_derive_early_secrets()` to accept an optional `struct s2n_psk *psk` argument. When `psk` is NULL, keep the existing behaviour (derive from zero-length PSK). When `psk` is non‑NULL, directly assign `keys->extract_secret` from `psk->early_secret` instead of running HKDF‑Extract with a zero IKM.
- Add a safety check: if a PSK is provided but `psk->early_secret.size == 0`, return an error (RFC 8446 §7.1 requires a non‑empty early secret).
- Update the caller in `s2n_tls13_handle_handshake_secrets()` to pass `conn->psk_params.chosen_psk`. This is the PSK that was negotiated during the handshake; `chosen_psk->early_secret` should already be set by the resumption logic.
- Update the function’s declaration in `s2n_tls13_keys.h`.
- Update every existing call site (including unit tests) to pass NULL for the zero-PSK case, preserving current behaviour when no PSK is used.

See `s2n_tls13_derive_binder_key()` for an analogous function that already takes a `struct s2n_psk*`. The pattern should be consistent.
```


```
Add a connection-level getter for the active sequence number, similar
to the recently-added `s2n_connection_get_secure_cipher`.  The same
pattern (branching on mode to pick either client_sequence_number or
server_sequence_number and then calling s2n_blob_init) is duplicated in
several places: key update sending, kTLS crypto-info init, the TLS 1.3
handshake zero-sequence helper, the key-schedule zero-sequence helper,
and a test helper.  Replace all of them with a single helper that takes
an `s2n_mode` and returns an `s2n_blob` pointing at the correct
sequence number, guarded by nullptr and invalid-mode checks.
```

cleanup: add getter for sequence number (#4317)


```markdown
# s2n_rand_set_callbacks should reject NULL callback arguments

The public API `s2n_rand_set_callbacks` currently stores the function pointers
provided by the caller without any null checks. If a caller passes `NULL` for any
of the four callbacks (init, cleanup, seed, mix), the library will later
dereference that null pointer during normal operation, causing a crash.

We should add argument validation to this function so that passing `NULL` for
any callback is treated as an error and causes the function to return
`S2N_FAILURE` (enforcing `s2n_errno` appropriately). This prevents accidental
misuse and follows the same defensive style used elsewhere in the codebase.

Use `POSIX_ENSURE_REF` to check each of the four callback arguments at the
entry of `s2n_rand_set_callbacks` (defined in `utils/s2n_random.c`). The
existing test infrastructure should be extended to verify that all such invalid
calls are rejected.
```

Suggestion: Prevent randomness callbacks being set to NULL (#2916)


In the server cipher-suite selection path, `s2n_kex_supported_and_configure` is used both to test whether a key exchange is viable and to apply configuration state to the connection (for example, picking a negotiated KEM parameter). Because the callback mutates connection state unconditionally, a cipher suite that is checked but ultimately rejected can leave the connection with a partially-configured `negotiated_kem` or other side effects. This leads to incorrect key-exchange configuration.

Split the mixed callback into two separate operations:

- A pure **support check** that returns whether the key exchange is possible for the connection **without** mutating any connection state.
- A **configure** function that sets up the connection for the selected key exchange after it has been definitively chosen.

Update all key-exchange implementations (RSA, DHE, ECDHE, KEM, hybrid ECDHE-KEM) to provide both callbacks. The KEM and hybrid callbacks are the main offenders — ensure that `negotiated_kem` is only set during configuration, not during support checking.

Adjust the server cipher-suite loop to call the support check first, and only invoke the configure callback once a cipher suite is actually selected. Ensure the public API exposes `s2n_kex_supported` and `s2n_configure_kex` (or equivalent names) in place of the old `s2n_kex_supported_and_configure`.

Update KEX api with new 's2n_kex_supported' and 's2n_configure_kex'


**Bug: Server Max Fragment Length negotiation incorrectly active without explicit opt-in**

When a server config is created with `s2n_config_new()` and used as‑is (without calling `s2n_config_enable_server_max_fragment_length()`), the server may still accept and process the `max_fragment_length` TLS extension from a client. This can cause the server to unexpectedly restrict its outgoing fragment length, violating the contract that MFL negotiation is an explicit server‑side opt‑in.

**Expected behavior**

* A fresh `s2n_config` (or any config where `s2n_config_enable_server_max_fragment_length()` has **never** been called) must **not** negotiate the max fragment length extension, and must not change its fragment length in response to a client’s MFL request.
* Calling `s2n_config_set_max_fragment_length()` without also enabling server MFL must have no effect on the handshake; the extension should remain disabled.
* The GnuTLS integration handshake tests (`s2n_handshake_test_gnutls`) should exercise **both** the no‑MFL case **and** the case where MFL is used, for **every** valid MFL size: 512, 1024, 2048, and 4096. Today those tests always run with `--enable-mfl --recordsize=512`, so the no‑MFL scenario and other sizes are never tested.

**Acceptance criteria**

1. A new `s2n_config` object must have its internal “server MFL enabled” flag initialised to *off*.
2. A handshake performed by the server using that config, without any additional MFL API calls, must succeed and must **not** result in an MFL‑negotiated fragment length.
3. When `s2n_config_enable_server_max_fragment_length()` is called and a specific MFL size is set, the negotiation must reflect that size.
4. The GnuTLS integration test must iterate over `[none, 512, 1024, 2048, 4096]`, running the full cipher/signature‑algorithm test matrix in each mode, with the appropriate server flags and GnuTLS `--recordsize` argument for the MFL cases and without them for the no‑MFL case.

make non-mfl and mfl test cases separate in s2n_handshake_test_gnutls & loop through all possible mfl value


Bug: TLS 1.3 records written with wrong outer content type and off-by-one payload length

After a TLS 1.3 handshake completes, s2n writes protected records (e.g., Handshake, Alert, or Certificate records) with several violations of RFC 8446 §5.1:

- The outer record header’s type byte is set to the inner `content_type` (e.g., 0x16 for Handshake) instead of always being `APPLICATION_DATA` (0x17). The spec requires the outer `TLSPlaintext.type` to be `application_data` for all TLS 1.3 protected records; the actual content type is carried as a trailing byte inside the encrypted payload.

- The 2‑byte payload length in the record header does not include the inner content type byte that is appended after the plaintext before encryption (the “opaque type” byte). This makes the announced record length one byte too short, which can cause compliant receivers to misread record boundaries.

- The AEAD additional data (AAD) used for encryption/decryption is also computed with the incorrect record length (plaintext length only, without the extra inner‑type byte). Since AAD must cover the exact record length as defined for TLS 1.3, this mismatch will cause decryption failures or authentication failures on receiving implementations.

The expected behaviour:
- The first byte of every TLS 1.3 write record’s header must be `TLS_APPLICATION_DATA` (23).
- The header payload length must equal the length of the plaintext *plus* the 1‑byte inner content type that is appended after the plaintext (before padding and encryption).
- The AAD construction (through `s2n_tls13_aead_aad_init`) must receive a record length that includes that 1‑byte inner content type.

Please locate the record‑writing path that handles TLS 1.3 records (the branch conditioned on `is_tls13_record`) and correct the outer content type, payload length, and AAD length to conform to the specification. The fix likely needs to:
- use `TLS_APPLICATION_DATA` for the record header type byte,
- adjust the fragment‑length calculation to account for the extra 1 byte, and
- ensure the AEAD AAD initialization uses the updated length.

The relevant constants and helpers are already available in the repository (e.g., `TLS_APPLICATION_DATA` in `tls/s2n_tls_parameters.h` and `s2n_tls13_aead_aad_init` in `tls/s2n_aead.c`).

Fix record write payload length and ensure opaque type in TLS 1.3


We need to harden the RSA-based pre-master secret handling against timing side channels.
Currently, when an RSA key exchange fails (bad decryption or version mismatch), the code branches to generate a random pre-master secret. That branch may leak timing information about whether the decryption succeeded. Similarly, copying the decrypted value into the output buffer is done in a way that may not be constant-time.

We should:

1. Introduce a constant-time “copy or don’t copy” utility. Given a source buffer, a destination buffer, a length, and a flag `dont`, the function must copy source to destination only if `dont` is zero — but it must always run in constant time regardless of the flag’s value. The implementation should use mask-based operations, not branches that depend on the flag.

2. In the RSA pre-master secret decryption path:
   - Always fill the pre-master secret buffer with random data *before* attempting decryption.
   - Set the protocol version bytes in the random buffer as expected.
   - Then try the RSA private key decryption. Use the new constant-time copy utility to conditionally place the decrypted result into the pre-master secret buffer only when the decryption succeeds (i.e., when the returned size matches the expected output size). If decryption fails, the buffer must remain the random data, but the operation must not reveal via timing whether the copy happened or not.
   - This replaces any branching that previously generated the random secret only on failure. The `rsa_failed` flag should still be set based on decrypt success and version comparison, but the random secret generation must not be inside a branch.

3. As an additional defense-in-depth measure, the client Finished message handler should also check the `rsa_failed` flag (alongside the existing constant-time Finished verification) and reject the handshake if the flag is set.

The goal is to eliminate observable timing differences between a successful RSA pre-master secret decryption and a failed one that falls back to a random secret. Every control flow that depends on the outcome must be replaced with constant-time logic, and the random secret must be set up unconditionally.

More paranoia for pre-master secret handling


**Title:** Support TLS 1.3 clients that do not send the `signature_algorithms` extension

**Background**
Per TLS 1.3 (RFC 8446, Section 4.4.2.2), a server may proceed using its own default signature algorithm if the client omits the `signature_algorithms` extension.  Some real-world TLS 1.3 clients do not include this extension, or include only pre‑TLS‑1.3 signature schemes that are not usable in a TLS 1.3 context.

**Observed problem**
Currently, s2n in TLS 1.3 server mode:

* Rejects the handshake when the client does **not** provide a `signature_algorithms` extension, failing with an “Empty signature scheme” error.
* Rejects the handshake when the client provides only signature schemes that are not valid for TLS 1.3 (e.g., RSA PKCS#1 v1.5 SHA‑224), failing with an “Invalid signature scheme” error.

This prevents interoperability with compliant clients that legitimately omit or lack TLS‑1.3‑compatible signature algorithms.

**Expected behavior**

1. When the `signature_algorithms` extension is missing or contains only unusable entries, the server must **not** fail the handshake solely because of the missing / incompatible extension.
   Instead, the server should fall back to a server‑preferred signature scheme that is:
   * Permitted for TLS 1.3,
   * Compatible with the chosen cipher suite,
   * Supported by the server’s certificate and authentication configuration.

2. The server should still respect the client’s preferences when the extension is present and contains at least one scheme that is usable for the connection.  The fallback should only be used when no mutually acceptable scheme can be discovered via the client’s list.

3. If the server is unable to locate **any** usable signature scheme from its own preferences (e.g., no certificate is configured, or no certificate matches the cipher suite’s authentication requirements), the handshake should continue to fail with an appropriate error (e.g., invalid signature scheme), as it does today.

4. The existing error code specific to an empty signature scheme (currently surfaced for missing extensions in TLS 1.3) should be removed, as it is no longer applicable.  The generic “invalid signature scheme” error should cover remaining truly pathological cases.

5. The fix must not alter the behavior for TLS 1.2 and earlier.  In those versions, a fallback to a server‑preferred default signature scheme in the absence of client input is already supported and should remain completely unchanged.

**Acceptance criteria**
* A TLS 1.3 client that omits the `signature_algorithms` extension can complete a handshake with an s2n server, given that the server has at least one usable signature scheme.
* A TLS 1.3 client that sends only signature schemes invalid for TLS 1.3 can still complete the handshake, with the server selecting a viable default.
* The handshake still fails if the server has no usable signature scheme (e.g., no matching certificate).
* TLS 1.2 (and lower) behavior is unaffected.
* The “Empty signature scheme” error is no longer raised in any scenario.

Support TLS 1.3 clients that do not specify signature algorithms (#2222)


**Bug report: Null-pointer dereferences in s2n connection accessor functions**

Several public functions that take a `struct s2n_connection *` pointer directly dereference it without checking for `NULL`, leading to undefined behavior (typically a segmentation fault) when a caller accidentally passes `NULL`. This is inconsistent with the library’s stated emphasis on C safety and systematic boundary-enforcing checks, and it prevents callers from relying on safe failure paths.

**Observed behavior**
Calling `s2n_connection_get_cipher(NULL)` crashes immediately because the implementation unconditionally accesses `conn->secure.cipher_suite->name`.

**Expected behavior**
All externally visible functions that accept a `struct s2n_connection *` should gracefully handle a `NULL` input:

- Integer-returning accessors and mutators should set `s2n_errno` to an appropriate error indicator and return `-1` (the existing `S2N_ERROR`/`S2N_FAILURE` convention).
- Pointer-returning accessors should return `NULL`.
- No function should dereference `conn` without first verifying that the pointer is non‑null.

Any internal sub‑pointers that are relied on for correctness (e.g., `conn->secure.cipher_suite` inside `s2n_connection_get_cipher`, or `server_name` inside `s2n_set_server_name`) should similarly be checked.

**Impact**
Null-pointer dereferences in a security library can at minimum crash the host application, and they break the defensive programming model that the library otherwise promotes throughout its codebase. This is visible end-user risk anytime a connection object becomes unavailable or is misused.

**Additional context**
The existing codebase already uses safety macros (`notnull_check` / `notnull_check_ptr`) in many places; these same patterns should be applied in all remaining functions that lack a null guard, such as (but not limited to) `s2n_connection_get_cipher`, the various `s2n_connection_get_*_version` accessors, `s2n_connection_client_cert_used`, `s2n_connection_get_ocsp_response`, `s2n_set_server_name`, `s2n_get_application_protocol`, `s2n_connection_kill`, and `s2n_connection_send_stuffer`.

Adds null pointer handling to multiple functions in s2n_connection.c.


**Title:** Complete Extended Master Secret (EMS) implementation and testing

**Description**
The Extended Master Secret (EMS) extension (RFC 7627) is partially implemented but currently only enabled in unit tests, and the master secret derivation only handles TLS 1.2 correctly. We need to finish the implementation so that it works for all supported TLS versions (1.0, 1.1, 1.2) and can be tested in a broader set of test environments.

**Expected behavior**
- The EMS extension should be negotiable in any test build (not only unit tests) by using the `S2N_IN_TEST` gate consistent with other in-progress features.
- When EMS is negotiated:
  - For TLS 1.2 connections: compute the session hash using the cipher suite’s PRF hash algorithm (e.g., SHA-256 or SHA-384) and feed that into the extended master secret PRF.
  - For TLS 1.0 and 1.1 connections: compute both MD5 and SHA-1 session hashes and feed them both into the extended master secret PRF, as required by the RFC.
  - For connections without EMS, the standard master secret derivation must continue to be used.
- The PRF helper that computes the EMS session hash should accept a caller-specified hash algorithm instead of internally deriving it from the cipher suite. This allows it to be called once per required hash algorithm (MD5 once, SHA-1 once for pre-1.2; the PRF hash once for 1.2).
- The session hash must include the full ClientKeyExchange message, as specified in RFC 7627 Section 5.1.

**Non‑obvious constraints & edge cases**
- The extended master secret PRF wrapper must handle an optional second hash blob (for SHA-1) that is only supplied for pre‑TLS‑1.2 connections; for TLS 1.2 it must be `NULL`.
- The hash algorithm parameter must be an explicit value from the existing `s2n_hash_algorithm` enum, not derived inside the computation function.
- The EMS negotiation flags on each connection (`ems_negotiated`) must still correctly reflect whether the extension was exchanged, and the test-only gating must not accidentally disable the extension in production builds (i.e., the `s2n_in_unit_test()` guard must be replaced with `S2N_IN_TEST`).

**Testing required**
Add end‑to‑end test scenarios for EMS negotiation covering:
1. Client supports EMS, server does not → EMS should not be negotiated.
2. Server supports EMS, client does not → EMS should not be negotiated.
3. Both support EMS → EMS should be negotiated successfully.
In each case validate that the `ems_negotiated` flag is set correctly on both sides and the handshake completes without errors.


**Bug: `s2n_connection_set_early_data_state` rejects no‑op (same‑state) transitions**

### Observed behavior
When a connection is already in a particular early data state (for example, `S2N_UNKNOWN_EARLY_DATA_STATE`), calling `s2n_connection_set_early_data_state` with the **same** state returns an error (`S2N_ERR_INVALID_EARLY_DATA_STATE`). This is inconsistent with state‑machine helpers found elsewhere in the codebase, which treat a transition to the current state as a harmless no‑op.

### Expected behavior
- Setting the early data state to the value it already holds must succeed and return `S2N_RESULT_OK`.
- All **other** transitions must continue to be validated using the existing state‑machine rules (e.g., transitions to `S2N_UNKNOWN_EARLY_DATA_STATE` from any *different* state remain illegal).

### Acceptance criteria
1. For any connection and any valid early data state `S`, `s2n_connection_set_early_data_state(conn, S)` returns success whenever `conn->early_data_state == S` (idempotent no‑op).
2. This includes `S2N_UNKNOWN_EARLY_DATA_STATE`: a connection already in that state can “transition” to it without error.
3. The existing state‑machine validation (checking the allowed previous state for each next state) is unchanged; only the no‑op case is exempted.
4. The internal test helper that validates all possible state sequences must be updated so that self‑transitions are not treated as test failures.

### Context
- The early data state enum is defined in `tls/s2n_early_data.h`, with states like `S2N_UNKNOWN_EARLY_DATA_STATE`, `S2N_EARLY_DATA_REQUESTED`, etc.
- The function signature is `S2N_RESULT s2n_connection_set_early_data_state(struct s2n_connection *conn, s2n_early_data_state next_state)`.

Allow no-op transitions in early data state machine (#2607)


When a peer closes its read-end of a TCP connection (half-close) and s2n subsequently tries to write application data, the write operation triggers a SIGPIPE signal that by default terminates the process. This is undesirable for libraries: applications should be able to handle a broken-pipe error gracefully without being killed.

**Acceptance criteria**
- Writing to a TLS connection whose remote read-end has been closed MUST return -1 with errno set to EPIPE – exactly the same error code as today – but NO SIGPIPE signal may be delivered to the calling process.
- The fix must not alter the behavior for any other write errors (connection reset, etc.).
- The change must be portable across all platforms s2n supports (Linux, macOS, …). The preferred mechanism is to use the `send()` system call with the `MSG_NOSIGNAL` flag where it is available (Linux, *BSD, macOS) to suppress the signal per-call, avoiding any global state change such as ignoring SIGPIPE globally.
- The underlying I/O write path (the function that actually pushes encrypted bytes onto the file descriptor) must be updated so that no raw `write()` call can generate SIGPIPE. The error EPIPE must still be reported to higher layers so that existing error handling continues to work.

**Non-goals**
- No change to the public API or error-reporting semantics is needed – exactly the same return values and errno settings as before, just without the signal.

Do not generate SIGPIPE but still return EPIPE


## Implementation Request: Handle error alerts correctly in s2n_shutdown

The `s2n_shutdown` function does not fully implement the TLS rule that error alerts close the connection without exchanging `close_notify` alerts (RFC 8446 §6.1). Several situations are mishandled, causing the function to send or wait for a `close_notify` when it should not, or to miss error alerts that arrive during the shutdown sequence.

**Acceptance criteria:**

1. **No-op when an error alert was already received.**
   If the peer has already sent a fatal-level (error) alert on the connection, any call to `s2n_shutdown` must return success immediately without sending a `close_notify` and without attempting to read further data. The connection’s read side is already closed, and the function must not treat the absence of a `close_notify` as something to wait for.

2. **Do not send `close_notify` after sending an error alert.**
   If an error alert has been sent by the local side (either queued earlier, or flushed during the current `s2n_shutdown` call), the function must not subsequently attempt to queue or send a `close_notify`. The existing mechanism that inspects `conn->write_closing` is not specific enough: both error alerts and `close_notify` alerts cause `write_closing` to be set. The decision to skip the `close_notify` must be based on whether the outstanding alert was actually an error, not on the `write_closing` flag alone.

3. **Surface error alerts that arrive while waiting for `close_notify`.**
   If `s2n_shutdown` has sent a `close_notify` and is waiting for the peer’s `close_notify` response, and the peer instead sends an error alert, `s2n_shutdown` should report that error (via the normal alert-reporting failure path) rather than ignoring it. After reporting the error, the connection is closed, and any further calls to `s2n_shutdown` must be no-ops (no additional alerts, no state changes).

4. **`close_notify` is not an error.**
   A received `close_notify` must never be mistaken for an error alert, and a queued/sent `close_notify` must not suppress normal shutdown behavior. The logic should use the existing `close_notify_received` and `close_notify_queued` flags to distinguish `close_notify` from other alerts.

**Safety / non-obvious constraints:**

- The existing blinding enforcement and `s2n_flush` call sequence inside `s2n_shutdown` must be preserved. The new error-alert checks should fit into the existing control flow without breaking timer or flush ordering.
- The `alert_in` stuffer may contain an alert even after it has been “consumed” by `s2n_connection_get_alert` (known issue #3933). The received-error check must not rely on whether the alert has been read by the application; it must detect the presence of an alert in the stuffer independently of consumption.
- The connection’s IO status (`S2N_IO_CLOSED` vs. `S2N_IO_FULL_DUPLEX` etc.) must correctly reflect the closure after an error is sent or received.
- The `read_closed` flag should be set when an error alert is sent, because the connection is fully terminated and the peer is not expected to send a `close_notify`.

The goal is to make `s2n_shutdown` behave correctly in all combinations of prior alerts, and avoid unnecessary blocking, duplicate alerts, or silent loss of error indications.

Cover more situations where no close_notify is sent/received (#3957)


```
Title: RAND_bytes() fails after s2n_cleanup()

s2n registers a custom RAND engine with libcrypto to provide entropy
during TLS operations. After a successful s2n_cleanup() call, OpenSSL’s
RAND_bytes() fails (returns 0) even though the default built-in RAND
implementation should be in effect. The expected behaviour is that
once s2n is fully shut down the process can again use libcrypto’s
standard random functions independently, as if s2n had never been
initialized.

The problem prevents applications from performing any OpenSSL operation
that needs randomness after tearing s2n down (including a later
re-initialisation of s2n, because s2n_init() itself depends on
RAND_bytes). The cleanup path currently calls RAND_set_rand_engine(NULL)
and destroys the s2n engine object, but libcrypto still considers the
old engine registered and does not properly fall back to its built-in
RAND.

The s2n cleanup should completely unregister the custom RAND engine from
libcrypto’s internal engine table so that the process returns to a clean
state where RAND_bytes() succeeds using the platform’s default RAND
implementation.
```

s2n_rand_cleanup: be sure to unregister s2n RAND engine from libcrypto (#3966)


```
Title: PeerManager should attempt to sync on every peer initialization

Background:
When a peer connects (or reconnects), PeerManager.onInitialization handles the event.
Currently it calls syncHelper only for new peers. For a peer that was already tracked
(reconnection or duplicate initialization), it does nothing. This means a node may fail
to resume syncing after a disconnection, leaving it without an up-to-date view.

Request:
Change PeerManager.onInitialization so that syncHelper is invoked whenever a peer is
initialized, regardless of whether the peer is new or already known. The syncHelper
should handle the case where a sync is already in progress – it is expected to be
safe to call multiple times.

Constraints / expected behavior:
- syncHelper must be called for every onInitialization invocation (both new and known
  peers).
- The change must not break the existing flow for new peers (which already calls
  syncHelper after managePeerF).
- The node should not end up in a state where it silently forgets to sync after a
  peer reconnects.
```

Defensively try to sync everytime a peer is a initialized (#5154)


# Refactor `Future[Wallet]` parameter to `Wallet` in `buildBitcoindNodeApi`

`BitcoindRpcBackendUtil.buildBitcoindNodeApi` currently takes a `Future[WalletApi]` argument. All callers already have the wallet instance available at the call site, so they are forced to create a `Promise`, pass its future, and later complete the promise with the same wallet. This indirection makes the code harder to follow, introduces a potential hang if the promise is never completed, and provides no actual benefit.

**Requested change:**
- Change the signature of `buildBitcoindNodeApi` to accept a concrete `WalletApi` instead of `Future[WalletApi]`.
- Propagate this change through any internal helpers or callers that forward the same future, adjusting them to take a wallet directly.
- Remove the `Promise`-based plumbing from the callers in `BitcoindRpcBackendUtil` and from the test utility in `BitcoinSWalletTest` that sets up a wallet with bitcoind callbacks.

**Acceptance criteria:**
- No `Promise` is used to wire a wallet into `buildBitcoindNodeApi`.
- The wallet is passed directly when constructing the node API.
- The existing behavior of syncing blocks from bitcoind, processing them through the wallet, and invoking chain callbacks is unchanged.
- No other modules (especially `DLCWalletLoaderApi`, `BitcoinSServerMain`, wallet/DLC wallet internals) require any changes; only the immediate wiring around `buildBitcoindNodeApi` and its test helper should be affected.

Replace Future[Wallet] -> Wallet parameter in {BitcoinSWalletTest, BitcoindRpcBackendUtil} (#5796)


We need to add observable helpers for LND chain notifications inside the LND RPC client. Currently the client offers streaming wrappers for invoices but nothing for on-chain confirmation events. The goal is to give downstream code a straightforward way to wait for a transaction to reach a specific number of confirmations without manually dealing with LND’s raw chain-notifier stream.

**Acceptance criteria:**

1. **Raw stream wrapper**
   - Expose a method that wraps the underlying LND chain client’s `registerConfirmationsNtfn` RPC and returns a reactive stream (`Source[ConfEvent, NotUsed]`). This stream must deliver both confirmation and reorg events for a given `ConfRequest`.
   - The method should be usable as a building block for higher-level helpers.

2. **Future-based subscription helpers**
   - Provide two overloads of a method that subscribes to transaction confirmation and returns a `Future[ConfDetails]`.
     - One overload accepts: a `txId: DoubleSha256Digest`, a `script: ScriptPubKey`, `requiredConfs: Int`, and `heightHint: Int`.
     - The other overload accepts only a `script: ScriptPubKey`, `requiredConfs: Int`, and `heightHint: Int` (it should act as if `txId` is all zero bytes, allowing detection of any transaction matching the script).
   - Each overload must:
     - Immediately reject with an `IllegalArgumentException` if `heightHint` is not greater than 0.
     - Subscribe to the underlying confirmation stream, **filter out reorg events** (only keep events where `.event.isConf` is true), take the first such event, and extract the `ConfDetails`.
     - Complete the future only when LND reports that the transaction has reached the required number of confirmations.

3. **Correctness of the returned ConfDetails**
   - The `ConfDetails` returned by the future must contain the raw transaction bytes (`rawTx`). When a specific `txId` was provided, those bytes must correspond to the original transaction (i.e., the raw transaction bytes should match the original transaction’s serialized form). This ensures the caller receives exactly the transaction they expected.

4. **Non-functional**
   - The helpers must be callable from the `LndRpcClient` instance used by tests and any external consumer.
   - They should be structured so that a caller can apply a timeout to the future (via `Await.result` or composition) without blocking indefinitely if the confirmation never arrives.

The work should be limited to the LND RPC client module; no changes to other services or external APIs are required.

Add helper functions for lnd chain notifications (#4846)