Add a symplectic integrator (velocity Verlet) for Hamiltonian Monte Carlo to `pyro.ops.integrator`. This is needed as a reusable building block for HMC kernel proposals.

**Requirements:**

- Implement a function `velocity_verlet(z, r, potential_fn, step_size, num_steps)` where:
  - `z` is a dict mapping sample site names to `torch.autograd.Variable` values representing the current position.
  - `r` is a dict mapping sample site names to `Variable` momenta conjugate to `z`.
  - `potential_fn` is a callable that takes a dict `z` (like `z` but with `Variable`s that require gradients) and returns a scalar potential energy. Its negative gradient with respect to each site gives the force on that site.
  - `step_size` (float) and `num_steps` (int) control the integration.
  - The function returns `(z_next, r_next)` with the same structure as `(z, r)`, all values being `Variable`s.
- The integrator must be **second-order symplectic**, **energy-conserving** (up to numerical error from discretization), and **time-reversible**.
- The implementation must handle an arbitrary number of sample sites (dict with multiple keys). Compute gradients efficiently using `torch.autograd.grad` only once per full time step, keeping the gradient results in `data` form to avoid graph accumulation across steps.
- Ensure the integrator works with PyTorch’s autograd mechanics (e.g., no gradient leaks, no unnecessary history tracking inside the loop).
- Place the integrator in the new module `pyro/ops/integrator.py` (create the `pyro/ops` package if needed).
- Include unit tests that validate trajectory end-points, energy conservation, and time-reversibility on at least three simple physical models: a harmonic oscillator, a circular planetary motion (Kepler), and a quartic oscillator. The tests should confirm that the integrator matches analytically computed final positions and momenta within a tight tolerance, and that forward+backward integration recovers initial positions.

[Reinstating #611] Verlet integrator for HMC (#614)


Title: InverseAutoregressiveFlow breaks on batch shapes with more than one dimension

### Problem

`InverseAutoregressiveFlow` (IAF) currently errors or silently produces incorrect results when applied to a base distribution that has a multi-dimensional batch shape (e.g. shape `(N, M, D)` or even `(N, D)` where `D` is the event dimension). The forward call (`_call`) slices the output of the autoregressive network with `hidden[:, 0:input_dim]` and `hidden[:, input_dim:]`, which hardcodes the assumption of exactly one leading batch axis. When the input has additional or zero leading dimensions, this indexing mismatches the shape of the input, causing runtime errors or broadcasting mistakes.

### Desired behavior

The IAF bijector should work transparently for any leading batch dimensions, matching the contract expected by `TransformedDistribution`. The forward pass should:

- Preserve all batch dimensions of the input `x` when computing `scale` and `mean` from the ARN output.
- Not assume a particular rank for the batch shape.

### Where the fix belongs

The slicing in `InverseAutoregressiveFlow._call()` in `pyro/distributions/iaf.py` must be changed from `:`-only to `...` ellipsis indexing so that any leading dimensions are retained. The rest of the logic (scale/mean computation, combination, caching) already handles arbitrary shapes if the slices are correct.

### Edge cases / safety

- The fix should handle the 1D case (no batch dim) correctly.
- It must be consistent with the other parts of `TransformedDistribution` that pass inputs with arbitrary batch shapes to bijectors.
- The caching mechanism uses `_add_intermediate_to_cache` and should continue to work unmodified once the slicing is fixed.


The `ZeroInflatedDistribution` class does not currently broadcast the gate and base distribution batch shapes, and uses only `gate.shape` as the batch shape. It also lacks a `support` property and does not guard against base distributions with non-empty event shapes. Update the constructor to compute the batch shape via `broadcast_shape(gate.shape, base_dist.batch_shape)`, expand both `gate` and `base_dist` accordingly, expose `support` as `base_dist.support`, and raise a `ValueError` if `base_dist.event_shape` is non-empty.


We need two utility modules, `ClippedSoftmax` and `ClippedSigmoid`, that behave like `torch.nn.Softmax` and `torch.nn.Sigmoid` respectively, but rescale the output so that the resulting probabilities never become exactly 0 or 1.

Requirements:
- `ClippedSoftmax(epsilon, dim=None, ...)` should compute softmax over `dim` (default last dimension) and then linearly map outputs from `[0,1]` to `[epsilon, 1-(n-1)*epsilon]` along that dimension, where `n` is the size of the dimension. The sum of outputs along `dim` must remain exactly 1.
- `ClippedSigmoid(epsilon)` should compute sigmoid and then map outputs from `[0,1]` to `[epsilon, 1-epsilon]`.
- Both classes must accept the same optional keyword arguments as their `nn.Softmax`/`nn.Sigmoid` parents (e.g., `dim` for softmax) and delegate to the parent’s forward, only adding the epsilon rescaling.
- The new modules should live in `pyro.nn` and be importable as `from pyro.nn import ClippedSoftmax, ClippedSigmoid`.

The main use case is to avoid numerical issues and degenerate densities by ensuring probabilities are bounded away from zero and one during training.

Epsilon-scaled clipped versions of Softmax and Sigmoid as utilities (#444)


```
AutoGuideList currently raises NotImplementedError when .median() is called,
even though the individual guide parts (like AutoDelta, AutoDiagonalNormal)
already implement median. AutoGuideList should combine medians from all its
parts into a single dict, matching the contract of AutoGuide.median.

Additionally, AutoGuide should declare a base median method (raising
NotImplementedError) so that any custom guide that fails to implement it
is caught early rather than at the call site.
```


**Subject:** Numerical instability and performance regression in `precision_matrix` computation for `MultivariateStudentT` and missing `precision_matrix` in `MultivariateNormal`

**Description**
When using `MultivariateStudentT` with ill-conditioned or high-dimensional `scale_tril`, the current `precision_matrix` property is numerically unstable and slow. The implementation uses `triangular_solve` followed by a matrix multiplication, which can propagate large errors and produce `NaN` gradients during backprop. This affects log-probability computations and downstream inference.

Additionally, `MultivariateNormal` (in `pyro.distributions.torch`) does not expose a `precision_matrix` property, forcing users to manually compute the inverse, often with equally unstable methods.

**Observed behavior**
- `MultivariateStudentT.precision_matrix` returns values that diverge from the true inverse of `covariance_matrix` when `scale_tril` is nearly singular or has large condition number.
- The computation is an order of magnitude slower than necessary because of the unnecessary explicit inversion.
- The `covariance_matrix` and `scale_tril` properties are broadcast to full batch shape eagerly, wasting memory when only a single batch element is needed later.
- Variable names in `GammaGaussian.compound()` (e.g., `alpha` and `beta`) are misleading; they actually correspond to **concentration** and **rate** of the latent Gamma factor, not to the resulting Student-t parameters.

**Expected behavior**
1. **Numerical stability and speed**: `precision_matrix` should be computed via `torch.cholesky_solve` using the identity matrix and the Cholesky factor (`scale_tril`). This avoids explicit inversion and is backward-stable.
2. **Lazy, memory-friendly broadcasting**:
   - Store the user-provided `scale_tril` as an **unbroadcasted** attribute (`_unbroadcasted_scale_tril`).
   - Provide `scale_tril`, `covariance_matrix`, and `precision_matrix` as `lazy_property`s that expand to the full batch shape only when accessed.
   - The `expand()` method must correctly handle already-materialized lazy properties.
3. **Consistent API**: Add a `precision_matrix` `lazy_property` to `MultivariateNormal` using the same Cholesky-solve approach, so both distributions behave identically.
4. **Code clarity**: Rename `alpha` and `beta` in `GammaGaussian.compound()` to `concentration` and `rate`, reflecting their actual semantics and avoiding confusion with the Student-t degrees-of-freedom.

**Impact**
- Prevents `NaN` gradients and inaccurate log-probabilities in models using large or ill-conditioned covariances.
- Reduces memory footprint for widely-broadcast distributions.
- Removes a common user pain point around missing `precision_matrix` for `MultivariateNormal`.

improve speed and numerical stability of scale_tril to precision (#2264)


We need to enhance the MADE-style autoregressive neural network in `pyro.nn.auto_reg_nn` to be more flexible and correct a subtle issue in the mask structure. Currently, `AutoRegressiveNN` only supports a single hidden layer, hardcodes ReLU activation, uses a fragile random mask encoding, and has no skip connections. For more powerful normalizing flows (e.g., multi-layer IAF), we should extend it.

**Desired changes:**

1. **Multiple hidden layers**: Add a `num_layers` parameter to `AutoRegressiveNN.__init__` that stacks `MaskedLinear` layers with appropriate autoregressive masks, keeping the final output layer.
2. **Skip connections**: Add a `skip_connections` boolean parameter. When `True`, a parallel `MaskedLinear` from the input directly to the output (with appropriate skip-connection mask) is added, and its result is summed with the main pathway’s output.
3. **Configurable nonlinearity**: Add a `nonlinearity` parameter (default `nn.ReLU()`) to replace the hardcoded ReLU between hidden layers.
4. **Drop explicit `mask_encoding`**: The old constructor accepted an optional `mask_encoding` tensor and exposed `get_mask_encoding()`. This is an internal detail that should be eliminated—masks should be constructed automatically via a new helper (e.g., `create_mask`) based on input/hidden dimensions, permutation, and number of layers. The `permutation` argument should still be accepted (and default to a random permutation), but it must be stored as `torch.LongTensor`.
5. **Correct mask structure**: The old mask left the output corresponding to the first variable in the order disconnected from any hidden units. The new mask should instead connect that output to hidden units while still preserving the autoregressive invariant (i.e., the Jacobian remains lower triangular after applying the permutation). This is achieved by comparing indices with `>=` at the last hidden-to-output mask and using `>` for the skip mask and earlier layers, as well as sampling hidden-indices via `sample_mask_indices`.
6. **Hidden dimension constraint**: Raise a `ValueError` if `hidden_dim < input_dim`, because otherwise it is impossible to build a valid autoregressive mask for all outputs (some outputs could become disconnected).
7. **Backward compatibility**: Ensure that callers using only the `input_dim`, `hidden_dim`, and optional `permutation` (e.g., `InverseAutoregressiveFlow`, `AutoIAFNormal`) continue to work without changes. The default number of layers is 1, skip connections False, nonlinearity ReLU, so behavior for existing single-layer, no-skip cases should be equivalent—except for the corrected mask connectivity, which should only affect capacity, not the autoregressive property (Jacobian remains triangular).

The new implementation should keep the `MaskedLinear` module unchanged. Provide a standalone `create_mask` function that can be reused and tested separately. This function should accept `input_dim`, `observed_dim`, `hidden_dim`, `num_layers`, `permutation`, and `output_dim_multiplier`, returning a list of per-layer masks and a skip-connection mask. The mask generation must handle `observed_dim > 0` (for conditional densities) even if that feature isn’t exposed yet in `AutoRegressiveNN`.

Please implement this refactoring in `pyro.nn.auto_reg_nn` and adjust any internal usage.


**Feature request: LogNormalNegativeBinomial distribution**

We need a new distribution that represents a continuous mixture of `NegativeBinomial` distributions where Normally-distributed noise is added to the logit parameter. This distribution should be accessible as `pyro.distributions.LogNormalNegativeBinomial`.

**Acceptance Criteria**

- The distribution is defined over non-negative integers (`support = constraints.nonnegative_integer`).
- Constructor parameters:
  - `total_count` (float or Tensor): non-negative, controls variance (concentration-like).
  - `logits` (Tensor): event log-odds for the underlying Negative Binomial.
  - `multiplicative_noise_scale` (Tensor): standard deviation of the Normal noise injected into the logits; must be positive.
  - `num_quad_points` (int, default 8): number of quadrature points used to approximate `log_prob`.
- All three tensor parameters broadcast together.

- **`log_prob(value)`**: Computed by numerically integrating out the Normal noise using Gauss-Hermite quadrature. Specifically, the log-probability is `logsumexp(log_weights + log_prob_NB(quad_logits))`, where `quad_logits = logits + multiplicative_noise_scale * quad_points` and `quad_points`/`log_weights` are obtained from a standard Normal quadrature rule. The computation must be numerically stable (using log-space addition).

- **`sample`**: Not implemented (raise `NotImplementedError`).

- **`expand`**: Must work correctly, preserving all parameters and the chosen number of quadrature points.

- **Analytic moments** (lazy properties):
  - Mean: `exp(logits + log(total_count) + 0.5 * multiplicative_noise_scale^2)`.
  - Variance: `mean + (exp(multiplicative_noise_scale^2) * (1 + 1/total_count) - 1) * mean^2`.

- The distribution must be registered in the public API (`pyro/distributions/__init__.py`) and appear in the Sphinx documentation (`docs/source/distributions.rst`).

**Supporting utility: Gauss-Hermite quadrature in log-space**

Add a function `get_quad_rule(num_quad, prototype_tensor)` to `pyro.ops.special` that:
- Returns a tuple `(quad_points, log_weights)` as Tensors matching the dtype/device of `prototype_tensor`.
- `quad_points` are the nodes for a standard Normal (mean 0, variance 1), scaled by `sqrt(2)` to correspond to the “probabilist” Hermite rule.
- `log_weights` are the log of the normalized weights, so that `logsumexp(f(quad_points) + log_weights)` approximates an expectation under `N(0,1)`.

**Non-obvious edge cases**

- `total_count` can be zero (yields a distribution with weight only at zero in the NB mixture). The quadrature approximation should remain well-defined.
- `multiplicative_noise_scale` approaching zero should recover a standard Negative Binomial distribution (logits unchanged). The quadrature with a very small scale should still produce valid log-probabilities.
- The number of quadrature points must be at least 1; invalid values should raise a `ValueError`.
- `expand` should not lose the quadrature properties (i.e., the internal quadrature points must be recomputed accordingly after expansion).

add log normal negative binomial distributions (#3010)


In `CompartmentalModel.fit_svi()`, allow using the simpler `AutoNormal` (diagonal) guide and the more expressive `AutoMultivariateNormal` (full-covariance) guide, in addition to the existing `AutoLowRankMultivariateNormal`. Use the existing `guide_rank` parameter to select the guide class, with the following mapping:

- `guide_rank=0` → `AutoNormal`
- `guide_rank="full"` → `AutoMultivariateNormal`
- any positive integer or `None` → `AutoLowRankMultivariateNormal` (current behavior)

Change the default from `guide_rank=None` to `guide_rank=0` to make cheap iteration the default when exploring models. If an illegal value is passed, raise a `ValueError`.

Update the `fit_svi` docstring to describe the new options and include a suggested workflow (e.g., start with `guide_rank=1`, then move to low-rank or full). Also ensure that the heuristic initialization log prints the init values in a consistent sorted order so that output is deterministic across runs.

Enable AutoNormal and AutoMultivariateNormal guides (#2538)


**Issue:** `ProvenanceTensor` does not preserve provenance through `dict` and nested container structures
**Reported by:** model developer using Pyro’s tracing and effect handling
**Affected component:** `pyro.ops.provenance`

### Problem Description

When I track provenance on tensors that are inside a `dict` (or any nested structure containing `dict`), the provenance is lost after a forward pass. For example:

```python
x = torch.tensor(1.0)
y = {"a": track_provenance(torch.tensor(2.0), frozenset({"param1"}))}
# Later, after some operations that involve y, provenance of the tensor inside the dict is empty.
```

The same issue occurs if a `dict` is passed through a `ProvenanceTensor.__torch_function__` dispatch (e.g., as keyword arguments to a torch function). The provenance attached to keyword arguments is not merged correctly because the loop over `kwargs` incorrectly used `provenance |= provenance` instead of the per-value provenance. Moreover, `dict` was never handled by any of the `singledispatch` registrations (`track_provenance` and `extract_provenance` were only registered for `list`, `tuple`, `frozenset`, and `set`).

### Expected Behavior

- `track_provenance(x, prov)` should recursively update provenance on all `ProvenanceTensor` leaves in **any** standard container type, including `dict`, and any nesting thereof (e.g., lists of dicts, dicts with list values, etc.). It should be consistent with the PyTorch `pytree` mechanism.
- `extract_provenance(x)` and `get_provenance(x)` should recursively collect provenance from all `ProvenanceTensor` leaves in arbitrary nestable containers, returning the union of all provenance sets.
- `ProvenanceTensor.__torch_function__` must correctly collect provenance from both positional and keyword arguments when dispatching to PyTorch functions, ensuring none are silently dropped.

### Reproducible Failure Modes

1. **Dicts ignored:** Running `get_provenance({"a": tracked_tensor})` returns an empty frozenset instead of the tracked tensor’s provenance.
2. **Nested containers lost:** `track_provenance( [{"a": tensor}], new_provenance )` applies provenance to the list but not to the tensor inside the dict.
3. **`__torch_function__` bug:** When calling `torch.add(tensor, alpha=tracked_tensor)`, the provenance of `tracked_tensor` is omitted because of the copy-paste error `provenance |= provenance` in the kwargs loop.

### Impact

These issues silently corrupt provenance chains, breaking any code that relies on provenance-based conditional logic (e.g., effectful interpretations, tracing, or custom reparameterizations). Developers cannot safely use untracked/tracked tensor mixtures inside dictionaries or arbitrary PyTrees.

### Desired Fix

Refactor `track_provenance`, `extract_provenance`, and `__torch_function__` to use PyTorch’s `pytree` utilities (`tree_map`, `tree_flatten`, `tree_unflatten`) for all `list`, `tuple`, and `dict` inputs. This would automatically handle arbitrary nesting, eliminate duplicated container logic, and fix the missing `dict` registration. Also correct the keyword argument aggregation bug in `__torch_function__`.

Refactor `ProvenanceTensor` to use `pytree` (#3223)


The `Empirical` distribution’s `sample` method produces malformed samples whenever the distribution has a non-empty `batch_shape` and the aggregation dimension (the dimension over which the sample population varies) is not the first axis.  The resulting tensors have incorrect shapes and values that do not correspond to independent draws per batch element, making it impossible to use `Empirical` for vectorised operations or downstream batched applications.

### Acceptance criteria

1. **Sample shape contract**
   For an `Empirical` distribution with `batch_shape = B` and `event_shape = E`, calling `sample()` must return a tensor of shape `B + E`.
   Calling `sample(sample_shape=S)` must return a tensor of shape `S + B + E`.

2. **Independence across batch dimensions**
   Each element of the batch index must be sampled independently from its own weighted categorical over the aggregation dimension.

3. **Consistency with distribution moments**
   When drawing a large number of samples (via a leading sample shape), the empirical mean of the samples along the sample dimension must converge to the distribution’s `mean` for every batch element, and similarly the empirical variance must converge to the distribution’s `variance`.  This must hold for both uniformly and non-uniformly weighted `Empirical` instances.

4. **Compatibility with `log_prob`**
   The `sample` output for single samples (no extra sample dimension) must be directly passable to `log_prob`, i.e., the resulting tensor must have shape `B + E`.  Vectorised (multi-sample) tensors are *not* required to be scoreable via `log_prob`—the existing restriction that `log_prob` only accepts a single unexpanded sample per batch element is preserved.

5. **Documentation of shape requirements**
   The class docstring must clearly state the shape relationship between `samples`, `log_weights`, `batch_shape`, `event_shape`, and the aggregation dimension, including a small example illustrating `sample` output shapes for single and batched sampling.

Fix bug in vectorized empirical.sample() (#1721)


Add systematic resampling to `SMCFilter` and make epidemic model initialization robust to SMC failures.

- In `pyro.infer.smcfilter`, replace the existing multinomial resampling step with systematic resampling. The new resampling should preserve particle diversity better than `Categorical(probs).sample()`.
- When the filter fails to find any feasible hypothesis (i.e., when no particle obtains a non-zero weight), raise a dedicated exception `SMCFailed` (subclass `ValueError`) instead of a plain `ValueError`. This allows callers to distinguish recoverable filtering failures.
- In `pyro.contrib.epidemiology.compartmental.CompartmentalModel.heuristic`, add a `retries` keyword argument (default `10`). If an `SMCFailed` exception is raised during the SMC initialization, retry the entire initialization up to `retries` times, logging a short message on each retry. Only re-raise the last failure if all attempts fail.
- After a successful heuristic, log the resulting initial values (scalar components) to aid debugging.

Use systematic resampling in SMCFilter (#2488)


Improve HMC's log-probability computation for models with discrete enumeration by replacing the existing `einsum` path with a `contract_to_tensor`-based evaluator.

- Add a `TraceEinsumEvaluator` that aggregates log-prob factors per independence context and uses `contract_to_tensor` (from `pyro.infer.contract`) to efficiently marginalize out enumerated dims.
- Rename the old `EnumTraceProbEvaluator` to `TraceTreeEvaluator` and remove its `use_einsum` branch, leaving only the tree-structured reduction logic.
- In `HMC._validate_trace`, select `TraceEinsumEvaluator` when `use_einsum=True`; otherwise select `TraceTreeEvaluator`.
- Ensure the evaluators handle batched enumeration correctly and respect `max_iarange_nesting` constraints.
- The `TraceEinsumEvaluator` must raise an error if `max_iarange_nesting` is not finite when the model contains enumerable sites.

Use contract_to_tensor in HMC for discrete site enumeration (#1403)


We need a way to obtain a loss tensor from `Trace_ELBO` that supports automatic differentiation, while still reporting the true (negative) ELBO value.
Currently, `Trace_ELBO.loss()` returns a detached scalar and `loss_and_grads()` calls `.backward()` internally, making it impossible to use the ELBO gradient in custom training loops that rely on calling `.backward()` themselves.

**Desired behavior**
Add a `differentiable_loss(model, guide, *args, **kwargs)` method to `Trace_ELBO` that returns a scalar tensor with the following properties:

- The **value** of the returned scalar equals the standard negative ELBO estimate (as returned by `loss()`).
- The **gradient** of this scalar with respect to model and guide parameters matches the gradient used by `loss_and_grads()` – that is, the surrogate ELBO gradient (including both reparameterized and score‑function terms).
- The caller can simply call `.backward()` on the result to compute parameter gradients without the loss object needing to know about Pyro optimizers or performing backward internally.

Additionally, the internal computation for a single particle should be shared between `loss_and_grads()` and `differentiable_loss()` to avoid duplication and ensure consistency.

**Impact / reason**
This would allow `Trace_ELBO` to be used directly with third‑party optimizers, custom gradient manipulations, and multi‑loss training schemes where the ELBO gradient is combined with other losses. The existing `loss_and_grads` remains useful but is too rigid when the user needs control over the backward pass.

Add differentiable_loss to Trace_ELBO (#1252)


Add a `finalize` hook to `CompartmentalModel` to support non-factorizable likelihoods that couple states across time.

**Motivation**
Some likelihoods cannot be added to the per-timestep `transition` method because they require the full time series (e.g., a coalescent likelihood or a Gaussian process over the entire incidence curve). Forcing them into `transition` would break factorizable inference and prevent use in heuristic initialization.

**Request**
Add an optional method `finalize(self, params, state)` to `CompartmentalModel`. It should:
- Be called once after the last time step in both the quantized (`_quantized_model`) and relaxed (`_relaxed_model`) internal models.
- Receive `params` from `global_model` and `state`: a dictionary mapping compartment names to tensors of the **entire time series**. For quantized inference the state tensors must be the approximate point estimates (i.e., the `..._approx` versions) for compartments listed in `self.approximate`.
- Not introduce any latent random variables; any attempt to do so must raise a clear error (e.g., `NotImplementedError`).
- Have a default no-op implementation so existing models are unaffected.
- **Not** be used during heuristic initialization (since it is only called after the last time step).

**Non-goals / safety**
- This hook must **not** affect the factorized structure of `transition`; it is purely a final, joint likelihood.
- Users who need state compartments in `finalize` must ensure those compartments are requested as approximate, e.g., via `super().__init__(..., approximate=("I","E"))`.
- No support for latent variables in `finalize`; the error should guide users to put factorizable terms in `transition`.

Add an optional .finalize() method to CompartmentalModel (#2646)


**Title:** TraceGraph_ELBO ignores score parts decomposition, causing high-variance gradients and poor inference for non-reparameterizable models

**Background**
Pyro’s `Trace` objects can precompute *score parts* – an entropy term and a score function term – via `Trace.compute_score_parts()`. The standard `Trace_ELBO` already uses these decomposed terms when building the surrogate loss, which reduces gradient variance for non-reparameterizable sites and correctly handles the reparameterization case. The `TraceGraph_ELBO` class, used when `trace_graph=True` in `SVI`, does not yet respect the score parts decomposition. It treats the full guide log-density as the score-function term and does not use the entropy term at all, leading to incorrect gradient estimation for many models with non-reparameterizable latent variables, and sub-optimal performance even when reparameterization is available.

**Desired behaviour**
1. **Reparameterized sites** – The surrogate ELBO should incorporate the `entropy_term` from `guide_site["score_parts"]` instead of subtracting the full `log_pdf`. When the entropy term is identical to the log-density (or the score parts are not present) the behaviour should fall back to what we have today, preserving backward compatibility. The actual ELBO (non-differentiable estimate) should still use the full `log_pdf`.
2. **Non-reparameterized sites** – The score-function gradient estimator should use `score_function` from `guide_site["score_parts"]` instead of the raw `log_pdf` (possibly divided by `site["scale"]`). This ensures that any stabilising transformations applied via score parts are honoured, just as they already are in `Trace_ELBO`.
3. The `is_identically_zero` helper currently defined inside `trace_elbo.py` is needed in both ELBO implementations; it should be moved to a shared utility location (`pyro.infer.util`) and reused, without duplication.
4. In the `TraceGraph_ELBO` loss computation, the guide trace must call `compute_score_parts()` so that the required fields are available. The model trace still needs `compute_batch_log_pdf()`. The callers of `_compute_elbo_reparam` and `_compute_elbo_non_reparam` should ensure the traces have these fields populated before entering the per-particle loss calculation.

**Affected area**
`pyro.infer.tracegraph_elbo` (class `TraceGraph_ELBO` and its helper functions `_compute_elbo_reparam`, `_compute_elbo_non_reparam`, and `_loss_and_grads_particle`). The `Trace_ELBO` implementation can serve as a reference for the correct score-parts handling.

**Non-negotiable constraints**
- Must not change the behaviour for models where `compute_score_parts()` has never been called (the trace should still work with only `log_pdf` available – though the new code path can assume `compute_score_parts` has been called by the time the surrogates are computed).
- Ensure that the differentiable `surrogate_elbo` only includes terms with non-zero expectation under the reparameterization trick; use `is_identically_zero` to guard against adding zero tensors that would break autograd.
- Keep the existing baseline estimation logic (weighted averages, neural baselines, etc.) working exactly as before; only the per-site gradient term should change.

add score parts logic to trace_graph (#760)


**Title:** Support monotonic rational quadratic splines in `Spline` / `ConditionalSpline`

**Background**
The `Spline` and `ConditionalSpline` transform classes (defined in `pyro/distributions/transforms/spline.py`) currently only support the monotonic rational **linear** spline (`order='linear'`). Requesting `order='quadratic'` raises a `ValueError` with the message “Monotonic rational quadratic splines not yet implemented!”. We need to fill this gap so users can leverage quadratic spline-based normalizing flows as described in *Neural Spline Flows* (Durkan et al., 2019).

**Desired behavior**
1. The `Spline` class (and its conditional counterpart) should accept `order='quadratic'` and construct a valid monotonic rational quadratic spline transform.
2. The quadratic spline must compute a monotonic, differentiable mapping from the input domain to the output domain, along with an exact log-absolute-determinant of the Jacobian (`log_abs_det_jacobian`). Both forward and inverse directions must be supported.
3. The quadratic spline **does not** use the extra parameter `lambdas` (the weight splitting each bin into two sub-regions) that is present in the linear case. Consequently:
   - The neural network output for the quadratic spline should produce only three parameter arrays per input bin: widths, heights, derivatives (no lambdas output).
   - The internal `_monotonic_rational_spline` function must branch on the presence/absence of `lambdas` to select the correct arithmetic.
4. All public helper functions (`spline`, `conditional_spline`) must expose the `order` argument and wire it through correctly, adjusting the dimensionality of the conditioning network (`DenseNN`) when `order='quadratic'` (three parameter sets of the appropriate size).

**Mathematical constraints & edge cases**
- The quadratic spline formulas must be implemented exactly as given in the reference implementation (the NSF codebase cited in the module header). In the inverse direction, the solution requires solving a quadratic equation; the implementation must select the correct root (the one within [0,1]) and handle the discriminant robustly (e.g., use `-b - sqrt(discriminant)` as per the reference to avoid cancellation).
- Numerical stability should be maintained similarly to the existing linear code: clipping/projection of bin widths, heights, and derivatives to minimum positive values; identity mapping outside the bounding box `[-bound, bound]`.
- The Jacobian determinant must be computed accurately for both forward and inverse calls.

**Scope**
- The change is entirely within `pyro/distributions/transforms/spline.py` and its associated helper API. No external interface changes are required beyond adding `order` support.
- The existing linear spline path must remain unaltered and continue to pass all current tests. The quadratic path should be internally testable via the same `_test` / `_test_conditional` harnesses by simply passing `order='quadratic'`.

**Acceptance criteria**
- Calling `spline(input_dim, order='quadratic')` and `conditional_spline(input_dim, context_dim, order='quadratic')` returns a working transform without raising any error.
- Forward, inverse, and Jacobian computations are consistent (inverse recovers original input, log-det matches the inverse log-det up to sign).


Allow users to supply an explicit initial trace for HMC/NUTS kernels before running MCMC, while preserving the existing automatic trace generation when no trace is provided.

Currently `initial_trace` is a no-argument method on `TraceKernel` subclasses (HMC, NUTS), so callers cannot inject a precomputed trace. This makes it impossible to initialize the chain from a known-good state for models with discrete latents or tricky support—the kernel always samples a fresh trace from the prior during setup.

- Change `initial_trace` from a method to a Python property with getter and setter, reflected in the abstract `TraceKernel` class.
- The getter should return the trace that the sampler will use as the chain's starting point and as the prototype for dict/trace interconversion. If no trace has been set, the kernel should lazily find a valid trace from the model (as currently done) and cache it, raising an error if a valid trace cannot be found after a reasonable number of attempts.
- The setter must accept a trace object and store it so that subsequent reads and the `setup()` method use it without replacement.
- `setup()` must respect any pre-assigned initial trace. It should not sample a new trace that overwrites the user-supplied one. The adaptation and model-inspection steps that currently operate on the prototype trace should use the stored trace (whether user-supplied or auto-generated).
- Remove the internal “prototype trace” storage (`_prototype_trace`) and consolidate its semantics into the new property so there is a single source of truth for the initial trace.
- The HMC/NUTS kernels should still handle the case where no trace is provided: the getter should generate and cache a valid trace with `NaN`/`Inf` checking as before, and `setup` should trigger that generation eagerly enough that adaptation sees the trace.

Refactoring HMC class to set initial trace (#1538)


The QUIC client session ticket cache currently uses `encoding/asn1` to serialize extra metadata (smoothed RTT, application data, and nonce) into the TLS session ticket. This ASN.1 dependency is undesirable because it introduces unnecessary complexity and a non-obvious serialization surface.

Replace the ASN.1-based serialization with a simple binary encoding that uses the project’s existing variable-integer utilities. The new encoding must:

1. Start with a version number (use `1`; the old version was `0`). Tickets carrying the old version must be rejected cleanly.
2. Serialize the smoothed RTT **in microseconds** (not nanoseconds).
3. Store the application-data bytes as a length‑prefixed blob (varint length, then the data).
4. Store the nonce bytes as a length‑prefixed blob.
5. On deserialization, if any read fails due to truncated or corrupt data, the entire ticket must be treated as invalid (the cache lookup should report no ticket and no error).
6. Do not import or rely on `encoding/asn1` for this marshalling any more.
7. The serialization/deserialization logic must stay inside the functions that put session tickets into the cache and retrieve them; no other component should be affected except the change in the on‑disk/wire format.

The desired observable behaviour is that the client still caches and restores QUIC session tickets correctly, the code no longer depends on `encoding/asn1`, and any tickets produced by an old version of the library (which used the ASN.1 format) are silently discarded as invalid by the new code.

don't use ASN.1 for marshalling data for the session ticket


**Title:** Use 0‑RTT to open the HTTP/3 client control stream

**Description**
The HTTP/3 client currently establishes connections with `quic.DialAddr`, which returns a `quic.Session` only after the QUIC handshake finishes. This means the control stream (carrying SETTINGS and other framing) is opened after the handshake, adding a round trip to every new connection.

We already have the `quic.DialAddrEarly` function and the `quic.EarlySession` interface (which provides `HandshakeComplete()`) in the QUIC layer. The H3 client should take advantage of 0‑RTT by switching to `DialAddrEarly` and opening the control stream immediately, while it’s still early data.

**Expected behavior**
- The client uses 0‑RTT to open the HTTP/3 control stream.
- Request streams (client‑initiated bidirectional streams) must **not** be opened until the handshake completes—only the control stream is allowed during 0‑RTT.
- The public `RoundTripper.Dial` callback type should be updated to return `quic.EarlySession` so custom dialers can also support this.

**Acceptance criteria**
1. The internal dial path calls `quic.DialAddrEarly` (or its equivalent) instead of `quic.DialAddr`.
2. Control stream setup is launched immediately after the early session is obtained (using the same goroutine pattern already present for `setupSession`).
3. The session’s `HandshakeComplete()` context is waited on before the `dial()` method returns, ensuring that subsequent `RoundTrip` calls see a fully established transport before they open request streams.
4. All existing tests pass with the updated signatures (mock expectations should expect `HandshakeComplete()` calls where needed).

**Impact**
Reduces latency for HTTP/3 clients by one RTT for SETTINGS exchange when 0‑RTT is accepted by the server, while maintaining correctness for request streams.

use 0-RTT to open the H3 client's control stream


**Title:** Pass request context to `RoundTripper.Dial` for cancellation-aware QUIC handshakes

**Background**
When an `http3.RoundTripper` is used with a custom `Dial` function, the dialing (QUIC handshake) currently proceeds without any connection to the lifecycle of the `http.Request`. If the caller cancels the request or its deadline expires, the QUIC connection setup continues to completion, wasting resources and ignoring the caller’s intent.

**Desired behavior**
The custom dialer should receive the request’s `context.Context` as a first parameter, allowing the dial to be cancelled or to timeout alongside the request. When no custom dialer is set (`Dial` is nil), the existing default dialing behavior can remain unchanged for now.

**Breaking change note**
Adding `context.Context` to `RoundTripper.Dial` will break existing custom dialers. Users will need to update their function signatures to accept the new parameter. The change should be clearly documented.

**Constraints**
- If the request context is already cancelled when dialing begins, dial should fail immediately with an appropriate error.
- The context is intended only for the initial connection setup; downstream stream operations should not be affected.
- The internal client’s `dial` method should be updated to pass the context through.
- Tests must be adjusted to verify that the correct context is propagated to the custom dialer (for example, by wrapping the request with a context that carries a known value or cancellation).

**Acceptance criteria**
- `RoundTripper.Dial` signature includes `ctx context.Context` as its first parameter.
- The request’s context is passed to the dial function when `RoundTrip` is called.
- Existing non-custom-dialer behavior is preserved.
- Unit tests confirm context propagation for the custom dialer case.

respect the request context when dialing


```
Add support for the QUIC HANDSHAKE_DONE frame (type byte 0x1e). This frame has no additional payload; the entire frame consists of its type byte. It is used at the 1‑RTT encryption level.

Acceptance criteria:

- The frame must be a new type that satisfies the existing `Frame` interface.
- Writing the frame outputs exactly one byte: `0x1e`.
- Parsing from a `bytes.Reader` that begins with `0x1e` must consume that byte and return a valid frame object.
- The wire frame parser, when asked to parse a 1‑RTT packet starting with byte `0x1e`, must dispatch to the new frame rather than returning an "unknown frame type" error.
- The implementation should follow the patterns established for other no‑payload frames (e.g., PING).

Note: QUIC specification defines HANDSHAKE_DONE as a server‑to‑client frame that confirms the handshake is complete. The frame carries no data beyond the type, so its length is always 1 byte.
```

implement parsing and writing of the HANDSHAKE_DONE frame


When 0-RTT is rejected by the server, the packet handler's `DropPackets` logic for the 0-RTT encryption level is missing, causing a panic. Additionally, any 0-RTT data that was already sent is not queued for retransmission over the 1-RTT encryption level, leading to lost application data.

Expected behavior:
1. Dropping keys for the 0-RTT encryption level should not panic. On the receive side this should be a no‑op (if 0-RTT is rejected, no 0-RTT packets will have been decrypted). On the send side the handler must iterate over the application‑data packet history (since 0‑RTT and 1‑RTT share the same packet number space), select only the 0‑RTT packets, mark their frames for retransmission, subtract their contribution from `bytes_in_flight`, and remove them from the history. 1‑RTT packets in the same history must be preserved.
2. After dropping 0‑RTT keys the bytes‑in‑flight count must reflect only the 1‑RTT packets that remain outstanding.

Impact:
- A QUIC connection that attempts 0‑RTT will crash the sender (client) at the point where 0‑RTT keys are dropped.
- Even if the crash is avoided, data sent in 0‑RTT is never retransmitted, silently breaking reliability.

queue 0-RTT packets for retransmission when 0-RTT is rejected


**Title:** Send INVALID_TOKEN error when server rejects an invalid retry token

**Description:**

Currently, when a client sends an Initial packet with a token and the server’s `AcceptToken` callback returns `false`, the server always responds with a Retry packet. Per the QUIC transport specification, this is incorrect when the rejected token is a Retry token. In that case the server must send an `INVALID_TOKEN` error (transport error code `0xb`) as a `CONNECTION_CLOSE` frame in an Initial packet, rather than a Retry. The existing Retry behavior should continue for other rejected tokens (nil token or non‑Retry token).

**Safety constraint – amplification protection:**

To prevent an attacker from using the server as a denial‑of‑service reflector, the server must **not** send an `INVALID_TOKEN` error if the received packet is corrupted or cannot be decrypted with the Initial AEAD keys. Only packets that pass the Initial AEAD integrity check may trigger the error response; corrupted packets must be silently dropped.

**Acceptance criteria:**

1. The server adds a new transport error code `0xb` with the string representation `"INVALID_TOKEN"`.
2. When `AcceptToken` returns `false` for a token where `token != nil && token.IsRetryToken` is true, the server sends a `CONNECTION_CLOSE` frame with error code `INVALID_TOKEN` in an Initial packet protected under the Initial AEAD keys for the destination connection ID.
3. When `AcceptToken` returns `false` for a nil token or a token whose `IsRetryToken` field is false, the server still sends a Retry (existing behavior).
4. If the received Initial packet containing the rejected Retry token cannot be unprotected (AEAD decryption fails), the server silently drops the packet and does **not** send any response.
5. No other error‑handling paths are affected; `ServerBusy` and other error responses remain unchanged.

send an INVALID_TOKEN error when receiving an invalid token


We need a utility to quickly tell whether a raw QUIC packet is a 0‑RTT packet, analogous to the existing `wire.IsVersionNegotiationPacket`. This will be used in fuzzing and other contexts where we inspect bytes without a full parse.

## Requirements
- The function should be named `Is0RTTPacket` and reside in the `internal/wire` package.
- It must accept a raw byte slice (the whole packet) and return a boolean.
- Safety / edge cases:
  - If the slice is shorter than the minimum amount of data needed to identify a long‑header packet (5 bytes), return `false`.
  - If the first byte signals a short header (bit 0x80 not set), return `false`.
  - If the QUIC version encoded in bytes 1–4 is not one of the versions currently supported by the protocol (i.e. `protocol.IsSupportedVersion` against `protocol.SupportedVersions`), return `false`. A packet with an unrecognised version cannot be classified, so it’s safest to treat it as not 0‑RTT.
  - Otherwise, examine bits 4‑5 of the first byte. The 0‑RTT packet type in IETF QUIC is `0x1` in those bits. Return `true` if those bits match, `false` otherwise.
- The function must not panic on any input; it will be called from fuzzers.

## Additional integration
- Once added, the header fuzzer (`fuzzing/header/fuzz.go`) should call this new function on the raw input *before* parsing the packet, and verify that its result matches the classification obtained later via `ParsePacket` / `hdr.Type` (i.e. `PacketType0RTT` vs. anything else). This cross‑check caught bugs in earlier QUIC drafts and should be enforced in fuzz runs.
- Please add table‑driven or Ginkgo‑style unit tests in the existing header test file covering the edge cases above (too‑short slice, unknown version, short‑header packet, positive match, and a longer slice that starts with a valid 0‑RTT header).

add a function to tell if a packet is a 0-RTT packet


```
Request: Eliminate unnecessary heap allocations when adding packets to the sent packet history.

The sentPacketHistory currently stores Packet values directly in its two linked lists
(outstandingPacketList, etcPacketList). This copies the Packet struct on insertion and then
requires taking the address of the stored copy (e.g. &el.Value) when returning a *Packet from
methods like FirstOutstanding, Iterate, and DeclareLost, which causes a fresh allocation each
time. Switch the lists and the associated packetMap to store *Packet, so the original pointer
is stored without copying and retrieved without additional address-taking. Ensure all methods
that push, iterate, or return packets use the pointer type consistently. Any code that
previously took the address of a list element’s Value must be updated to use the Value
directly as a *Packet.
```

avoid allocations when adding packets to the sent packet history


We are seeing spurious test failures caused by the quicproxy used in integration tests delivering delayed packets out of order, even when the configured delay is constant. For example, with a delay callback that always returns `100ms`, the receiving side sometimes sees `Packet 2` arrive before `Packet 1`. This reordering can cause false negatives in tests that rely on the proxy to simulate network latency without changing packet sequence.

Specifically, the proxy applies per-packet delays using individual `time.AfterFunc` timers. When several packets are delayed by the same amount and their timers fire close together, the actual delivery order depends on goroutine scheduling rather than the original send order. This makes it impossible to write reliable tests that expect packets to be delivered in order after a uniform delay.

The required behavior is straightforward: the proxy should deliver delayed packets in the order of their *scheduled delivery time* (the arrival time plus the returned delay). If packet A’s scheduled delivery time is before or equal to packet B’s, A must be delivered first. In particular, when the delay callback returns the same duration for every packet, the delivery order must match the send order.

The fix must cover both directions (`DirectionIncoming` and `DirectionOutgoing`). Existing semantics for immediate forwarding (delay = 0) and for drop callbacks must not regress, and the proxy must continue to support varying per-packet delays (i.e., a callback that returns different durations for different packets should still reorder those packets correctly according to their scheduled times). Clean shutdown must also be preserved, ensuring that no goroutines or timers leak after `Close()` is called.

rewrite the proxy to avoid packet reordering


When a 0‑RTT queue entry expires after `Max0RTTQueueingDuration`, the timer callback removes the entry from the map but does not release the packet buffers held in that entry’s queued packets. This causes a memory leak – each unreleased `receivedPacket.buffer` keeps a pool buffer from ever being returned.
Fix this by ensuring that, whenever a 0‑RTT queue entry is deleted (normally due to the timeout), all `receivedPacket` buffers that were still queued are released via `buffer.Release()` before removing the entry. The existing locking and queue-creation logic should be kept as is; only the cleanup path needs to be corrected.

release packet buffers when deleting a 0-RTT queue


ECN validation currently detects congestion events (e.g., an increase in ECN‑CE marks), but the result is discarded and never passed to the congestion controller. The `sentPacketHandler.ReceivedAck` method should inform the congestion controller when ECN validation signals congestion, so that the sender reacts to congestion that is signalled via the network rather than via packet loss.

**Acceptance criteria**
- When ECN validation on a received ACK indicates a congestion event, the congestion controller must be notified so it can reduce the send rate accordingly.
- The notification must only happen for 1‑RTT ACKs, and only when the ACK advances the largest‑acknowledged packet number (re‑ordering of ACKs must not trigger spurious congestion events).
- The congestion controller must be called with a non‑loss congestion event: the number of bytes lost should be 0, and the `priorInFlight` value (bytes in flight before this ACK was processed) should be passed so the controller can correctly estimate the flight size at the time of the congestion signal.
- Existing ECN validation logic must not be changed; only wire its result into the congestion controller and remove the `TODO` comment that says to do so.

feed ECN feedback into the congestion controller


We need a faster HKDF-Expand-Label for the QUIC handshake. Right now every secret derivation, key/IV expansion, and header-protector setup on the Initial encryption level goes through `qtls.HkdfExpandLabel`. That function builds its label using `cryptobyte.Builder`, and profiling shows it accounts for a noticeable fraction of the time spent setting up the initial AEAD and header protection—roughly on the order of 15% overhead.

The ask:

- Implement a local (package-internal) `hkdfExpandLabel` inside the `internal/handshake` package that performs TLS 1.3 HKDF-Expand-Label as described in RFC 8446 §7.1 (i.e. `HkdfExpandLabel(Secret, Label, Context, Length) = HKDF-Expand(Secret, HkdfLabel, Length)` where `HkdfLabel = Length || LabelBlob || ContextBlob`, `LabelBlob = prefix "tls13 " + Label` prefixed with its length, and `ContextBlob` is the context prefixed with its length).
- **Crucial invariant**: the new function must return exactly the same bytes as the existing `qtls.HkdfExpandLabel` for every combination of hash, secret, label, context, and length used by this package. No observable difference—it is a drop-in replacement.
- Replace **every** call to `qtls.HkdfExpandLabel` in the handshake package with the new function. The call sites are in:
  • `computeSecrets` (for "client in" / "server in")
  • `computeInitialKeyAndIV` (for "quic key" / "quic iv")
  • `newAESHeaderProtector` (for "quic hp")
  • `newChaChaHeaderProtector` (for "quic hp")
- If the underlying `hkdf.Expand` read fails (wrong length / error), the function should panic, consistent with how the existing code treats unexpected failures in key derivation.

After the change, all existing tests must still pass, and the time spent in initial AEAD/header-protector key derivation should be measurably lower (the 15% figure gives a rough expectation). No changes to test expectations should be needed because the outputs remain identical.

That’s it—just an optimized, local version of the label expansion that avoids `cryptobyte.Builder` and therefore speeds up the critical path of initial encryption setup.

add an optimized implementation of HKDF-Expand-Label


Per the QUIC transport draft, a server MUST NOT send a Version Negotiation packet in response to a packet that is smaller than the minimum Initial packet size (1200 bytes). This threshold is already defined as `MinInitialPacketSize` in the protocol package. Update the server's incoming‑packet handling to enforce this constraint: when a packet carries an unsupported version and its size is below that threshold, the server should drop it without sending a Version Negotiation response. The drop should be logged at debug level and, if a connection tracer is configured, reported via `DroppedPacket` (consistent with other packet‑drop paths). Packets meeting the size threshold must continue to trigger a Version Negotiation packet as before.

only send Version Negotiation packets for packets larger than 1200 bytes


```
Performance: stateless reset check executed on every incoming packet

When the transport processes an incoming QUIC packet, the current ordering
always runs the stateless reset detection (looking up a reset token) before
routing the packet to a connection based on its connection ID.  This means
that a packet belonging to an established connection still pays for a
separate stateless reset map lookup, even though real stateless resets are
only relevant for packets that cannot be matched to an active connection.

Stateless resets are sent with random connection IDs.  With reasonable
connection ID lengths the probability of a collision with an active
connection ID is negligible.  In the unlikely event that a reset is
misrouted to an existing connection, it will simply be dropped there and
the peer will send another reset that will then be correctly detected.

Therefore we can safely reorder the checks inside `Transport.handlePacket`:

  1. Look up the packet’s connection ID in the handler map.
  2. If a handler exists, deliver the packet to that handler and **do not**
     check for a stateless reset.
  3. Only if no handler is found, fall through to the stateless reset
     detection.

This saves one map lookup per packet on the hot path for the vast majority
of cases (packets that belong to active connections).  The deviation from
RFC 9000 §10.3.1 is minor and justified by the performance improvement.
```

only check for stateless resets if packet doesn't belong to a connection (#4322)


We need to reject duplicate pseudo-headers in HTTP/3 header parsing.
RFC 9114 §4.3 requires each pseudo-header to appear at most once, but the current parser silently overwrites an earlier value with the later one instead of reporting an error.

Add validation so that if the same pseudo-header is encountered more than once, the parser returns an error like `"duplicate pseudo header: <name>"`. This should apply to all pseudo-headers in both request headers (e.g. `:method`, `:path`, `:authority`, `:scheme`, `:protocol`) and response headers (`:status`). The check should work alongside the existing validation for ordering and unknown pseudo-headers.

http3: reject duplicate pseudo headers (#4993)


When constructing a packet that already carries other frames (crypto data, stream data, control frames, or retransmissions), the packet packer should opportunistically include any available acknowledgement information. This avoids unnecessary dedicated ACK-only packets and reduces overhead.

Current behavior only includes an ACK when an ACK alarm has fired, leading to separate small packets for acknowledgements even when a data-bearing packet is about to be sent at the same time. The desired behavior is:

- If a packet is being packed that contains non-ACK payload (data, control, retransmission), always try to attach an ACK frame if one can be generated for that encryption level, even if the ACK has not been explicitly queued.
- If no other frames are available for the packet, only attach an ACK if an ACK is actually queued (due to receipt of an ack-eliciting packet). This prevents packets that contain nothing but an unnecessary ACK.
- 0-RTT packets must never carry ACK frames; the decision logic should explicitly exclude them.
- The logic for deciding when to attach an ACK should consider both the existence of fresh data/control/retransmission frames and the state of the ACK frame source (queued vs. unqueued).

As part of this, the frame source used during packing should expose a way to check whether it actually has data to contribute (as opposed to just always calling append functions). This allows the packer to avoid building empty payloads and to correctly decide the ACK bundling condition.


```
Title: Simplify session state type conversions using direct unsafe casts

Our handshake layer wraps a standard `tls.ClientSessionCache` in order to bridge
between `tls.ClientSessionState`, our internal QUIC `qtls.ClientSessionState`,
and the local `clientSessionState` struct used for serialisation.

Right now the `Get` and `Put` methods perform the conversion by:

1. taking the `unsafe.Sizeof` of the source type,
2. creating a same-sized byte slice for the destination type, and
3. copying the bytes over.

This is unnecessarily verbose; all three struct types have identical memory
layout and we already enforce that invariant via runtime checks in `unsafe.go`.

Please simplify these conversions to direct `unsafe.Pointer` casts (e.g.
`(*clientSessionState)(unsafe.Pointer(src))`).  The same simplification should
also be applied in any test helper functions that currently construct a
`tls.ClientSessionState` or `qtls.ClientSessionState` from a
`clientSessionState` using byte-copy—those can be written as a direct cast as
well.

The only behavioural requirement is that the session marshalling and un-
marshalling (reading/appending the revision, app data, RTT, and nonce) must
continue to work exactly as before; the diff should purely simplify the
mechanical type bridging, not change what is written or read from the cache.
```

simplify casting of the (q)tls.ClientSessionCache


We need to change how buffered 0‑RTT packets are handed to a newly created session. Right now the server drains the zero‑RTT queue one packet at a time inside a `for` loop (calling `Dequeue` repeatedly). Replace that with a single call that delivers **all** queued packets for a given connection to the session in one go, then immediately removes the queue entry.

The new method on the zero‑RTT queue should:

- Accept the connection ID and a `packetHandler` (the session).
- While holding the queue lock, find the entry for that connection ID.
- If there is no entry, simply return (no error, no packets delivered).
- Otherwise, stop the entry’s expiry timer, iterate over all stored packets in FIFO order, and call `handlePacket` on the session for each one.
- Delete the queue entry after the last packet is handed off.
- Do **not** return any packets to the caller; the method’s signature should convey “take a session and feed it all queued packets”.

Update the call site in the server (`handleInitialImpl`) to use this new method instead of the one‑by‑one loop. The old single‑packet `Dequeue` can be removed.

Ensure the existing test coverage for the zero‑RTT queue is updated to reflect the new behaviour: verify that all packets are drained for a known connection ID, that an unknown connection ID produces zero packets, and that limits on queue depth and duration still work.

dequeue all 0-RTT packets to the session in one go


**Title:** Wire `Config.Tracer` through the multiplexer into the packet handler map

**Background**
`Config.Tracer` allows users to provide a `logging.Tracer` for per-QUIC connection logging, but it is currently not passed to the `packetHandlerMap`. The multiplexer’s `AddConn` method does not accept a tracer, and the packet handler map has no way to receive it.

**Request**
1. Extend `multiplexer.AddConn` to accept a `logging.Tracer` parameter and forward it to the packet handler map constructor.
2. Store the tracer in the packet handler map (no immediate usage required, but it should be available for future logging).
3. Enforce consistency when the same `net.PacketConn` is added multiple times: the tracer must match the one stored for that connection, analogous to the existing checks for connection ID length and stateless reset key. If a mismatched tracer is provided, `AddConn` should return an error.
4. Update the callers (`dialContext` in the client, `listen` in the server) to pass `config.Tracer` when calling `AddConn`.
5. Ensure that any integration‑level code that shares a single `PacketConn` across multiple invocations (for example, the self‑test suite with `enableQlog`) uses the same tracer to avoid the new consistency error.

pass a logging.Tracer to the packet handler map


```
Title: Stop timer when session run loop exits
Description:

When a session's run loop for a QUIC connection exits (i.e., when the session
closes), the internal timer (`*utils.Timer`) is not stopped. This can cause a
timer to fire after the session is gone, potentially resulting in a goroutine
leak or a spurious channel send that might interfere with later operations.

We should ensure the timer is properly cleaned up when the run loop returns.
The `utils.Timer` wrapper currently does not expose a `Stop` method; we need
to add one that delegates to the underlying `time.Timer.Stop()`, and then call
it from the session's cleanup path at the end of the run loop before the
function returns.
```

stop the timer when the session's run loop returns


**Title:** HTTP/3 client silently drops request body data when `Read` returns bytes and `io.EOF` in the same call

**Description:**
When the HTTP/3 client sends a request that includes a body (e.g., POST), the current request‑writing implementation may discard the final chunk of body data if the `io.Reader` provided as `Request.Body` returns a positive number of bytes together with `io.EOF` from a single `Read` call.

The `io.Reader` contract allows `Read` to return `(n > 0, io.EOF)` to indicate that it successfully read `n` bytes and that no more input will follow. The request writer must process those `n` bytes (frame them and write them to the QUIC stream) **before** handling the end‑of‑input signal. Presently the writer exits the reading loop early when it encounters `io.EOF`, causing the data returned in that same call to be lost. This results in truncated request bodies for certain body readers.

**Acceptance criteria:**
1. If `Request.Body.Read` returns `n > 0` and `io.EOF`, the caller must:
   - write a DATA frame containing those `n` bytes to the outgoing stream,
   - then close the stream normally (no error).
2. If `Read` returns `0` and `io.EOF` (i.e., no bytes), the writer must simply stop reading and close the stream, without sending an empty frame.
3. If `Read` returns an error **other than** `io.EOF`, the writer must cancel the stream with the appropriate cancellation error and log the error, regardless of whether any bytes were returned.
4. The fix must not alter the framing format, the headers, or the gzip handling; it should only correct the control flow around the body‑reading loop.

The change should be verified against a body that, in a single `Read`, returns a non‑zero number of bytes and `io.EOF` (e.g., a reader wrapping `bytes.NewReader` that returns exactly the buffer size on its last read, or a custom reader that exhibits the combined return). The stream’s written content must contain the expected DATA frame for those bytes, and the stream must be closed cleanly without a cancellation error.

fix HTTP request writing if the Request.Body reads data and returns EOF


### Post-handshake TLS messages split across QUIC CRYPTO frames cause crypto/tls failures

**Summary**
When a post-handshake TLS message (e.g., `NewSessionTicket`) is transmitted in multiple QUIC `CRYPTO` frames on the 1‑RTT encryption level, quic‑go delivers each fragment individually to `crypto/tls`. The Go TLS stack expects a complete handshake message and cannot process fragmented input outside the initial handshake. This breaks session resumption and any other post‑handshake exchange that happens to be fragmented.

**Expected behavior**
For the **1‑RTT crypto stream only**, quic‑go should buffer incoming `CRYPTO` frame data and return whole TLS handshake messages from `GetCryptoData`. A message boundary is indicated by the standard 4‑byte TLS record header (which encodes the total message length). While a full message is not yet available, `GetCryptoData` should return no data (i.e., an empty result). Once a complete message has been buffered, it should be returned and removed from the input buffer, preserving ordering.

**Non‑goal**
The Initial and Handshake crypto streams must **not** be changed—they must continue to return data as it arrives, without waiting for a complete TLS record. Only the 1‑RTT stream needs this reassembly.

**Impact**
Without this fix, any post‑handshake TLS message that spans more than one QUIC packet (and thus generates multiple `CRYPTO` frames) will cause `crypto/tls` to fail with a parse error. This silently breaks session ticket delivery and other post‑handshake features that rely on large messages.

reassemble post-handshake TLS messages before passing them to crypto/tls (#4038)


**Request: Handle 0‑RTT rejection on the client side**

When a QUIC server rejects 0‑RTT (e.g. because its transport parameters changed since the session ticket was issued), the `EarlySession` returned by `DialAddrEarly` does not properly signal the rejection to the caller. Observably:

- Streams opened and written to during the 0‑RTT window may appear to succeed, but the data is not processed by the server.
- The handshake-complete context alone does not distinguish between accepted and rejected 0‑RTT.
- The caller cannot obtain the server’s new transport parameters (e.g. updated max stream limits) without building a new connection from scratch.
- Attempting to accept a stream on the early session after rejection may block indefinitely or succeed without reflecting the server’s actual capabilities.

The implementation should provide a client‑visible way to:

1. **Detect** that the server rejected 0‑RTT. When the handshake completes and 0‑RTT was rejected, future operations on the *early* session (particularly accepting streams) should return a distinct, exported sentinel error (`Err0RTTRejected`), allowing callers to branch immediately.

2. **Obtain the post‑handshake session** that carries the server’s true transport parameters and stream limits. This new session must:
   - Reflect the stream‑limit constraints announced by the server during the handshake (not the limits saved from the previous connection).
   - Never expose streams that were opened during 0‑RTT as receivable or writable after the rejection.
   - Correctly report its TLS state (`Used0RTT == false`).

3. **Cleanly discard** 0‑RTT state internally so that old streams, flow‑control windows, and framing state do not leak into the new session. In particular:
   - Opening a new stream on the resulting session should respect the server’s exact limits (e.g., attempting to open more streams than allowed must fail with an appropriate error).
   - Any data buffered for 0‑RTT streams must not be delivered to the server after rejection, and the server side must not see those streams.

The fix should keep the `EarlySession` interface as the entry point and not break existing callers that do not use 0‑RTT. Integration tests should demonstrate that when the server’s transport parameters change between the original session and the 0‑RTT resumption, the client can detect rejection, switch to the new session, and successfully transfer data with the server under the new limits.

return an Err0RTTRejected when the server rejects a 0-RTT connection


The `ByteOrder` interface currently only supports decoding integer values from an `io.ByteReader`. It would be useful to also be able to decode `uint16`, `uint24`, and `uint32` directly from a `[]byte` slice, to avoid wrapping every slice in a `bytes.Reader` when the data is already fully in memory. Please extend the `ByteOrder` interface with methods for slice-based decoding, and implement them for the existing `BigEndian` concrete type. The implementation should follow the same big-endian semantics as the existing `Read*` methods, be bounds-check-safe, and delegate to `encoding/binary` where appropriate (for 16- and 32-bit) while handling the 24-bit case correctly.

extend ByteOrder interface to allow converting from a byte slice


Send stream: eliminate empty STREAM frames when flow control prevents data inclusion

When a send stream has buffered data but the flow-control send window or the per-frame byte limit prevents any data from being packed into a new STREAM frame, the frame-popping path currently generates an empty STREAM frame (no payload). This wastes network resources and is semantically incorrect—STREAM frames should carry data.

Required behavior:

- If `popNewStreamFrame` determines that the maximum data length for the next frame is zero (e.g., because of a zero send window or insufficient allowed bytes), it must not return a frame. It must instead signal that more data remains but no frame is available now.
- The caller `popNewOrRetransmittedStreamFrame` must handle a nil frame from `popNewStreamFrame` by returning early — without updating `writeOffset` and without calling `AddBytesSent` on the stream’s flow controller — and must correctly propagate the “hasMoreData” flag.
- No empty STREAM frame should ever be returned from the pop path when there is data pending but nothing can be sent.

Acceptance criteria:

1. Writing data to a send stream followed by calling `popStreamFrame` with a `maxBytes` that is just large enough for the frame header but too small for any payload (or when the send window is zero) must return `ok=false` and `hasMore=true`, and must not increment the stream’s write offset.
2. The same condition must not cause an extra empty frame to appear before the real data frame in a subsequent `popStreamFrame` call that provides enough space.
3. The stream’s internal write offset and the flow controller’s `AddBytesSent` must only be called when a frame with a positive data length is actually popped.

don't generate empty STREAM frames in the send stream (#4812)


Replace the environment-variable-based test override for the key update interval with a mechanism that does not incur per-packet parsing overhead. `keyUpdateInterval()` is called on the hot path in `shouldInitiateKeyUpdate` and currently re-parses `QUIC_GO_TEST_KEY_UPDATE_INTERVAL` every time, which harms benchmarks.

Requirements:
- Provide a way for tests to set a custom key update interval without touching environment variables.
- The default interval must remain `protocol.KeyUpdateInterval`.
- Setting and reading the interval must be safe for concurrent use.
- Tests must be able to reset the interval to the default after use so that they are isolated.
- The read in the packet-processing path must be cheap — no string parsing or syscall-like operations.

handshake: store key update interval in an atomic (#5234)


We should modernize the sent packet history iteration to use Go 1.23+ iterator functions instead of the existing callback style. The current `sentPacketHistory` in the internal ackhandler package exposes `Iterate` and `IteratePathProbes` methods that take a callback returning a `bool` to signal whether to continue. This makes loops that need early exit, error returns, or logical `continue`/`break` awkward to read and write.

Replace those callback-based methods with functions that return an `iter.Seq[*packet]`, so callers can use natural `for p := range history.Packets()` and `for p := range history.PathProbes()` loops. The rest of the handler code must be rewritten to use standard `for range` loops instead of callbacks, matching Go’s recent range-over-function idiom.

**Acceptance criteria**
- The `sentPacketHistory` no longer exposes `Iterate` and `IteratePathProbes`; it exposes new iterator-returning methods (`Packets` and `PathProbes`) that are usable with `for range`.
- All call sites in `sent_packet_handler.go` are updated to use the new range loops instead of callbacks.
- The behavior of every iterator consumer must be preserved exactly:
  - **Iteration order**: packets (or path probes) are yielded in their existing order.
  - **Nil packet skipping**: the iterator must skip `nil` entries in the packet list (packets that were already removed but not compacted away) without yielding them.
  - **Early termination**: any loop that previously returned `false` from the callback to stop iteration now uses `break` and yields the same control flow (including stopping immediately without processing further packets).
  - **Skip-to-next**: any callback that returned `true` to continue to the next packet now uses `continue`.
  - **Error returns from the middle of iteration**: `detectAndRemoveAckedPackets` must still be able to return an error directly from within the loop (via `return nil, err`), and the iteration must stop at that point before processing further packets.
  - **Deletion during iteration**: loops that call `Remove` (or `RemovePathProbe`) on the history while iterating must remain safe; the iteration must not be disrupted by concurrent mutations in the same goroutine (the history’s internal slice representation may shift, but the iterator must handle that as before).
- The existing test suite for the `ackhandler` package passes without modification of test *assertions*. Test code that used the old callback calls must be updated to use the new `for range` form, but they must still exercise the same scenarios (stopping early, deleting during iteration, etc.) and verify the same expected packet sequences.
- No functional change to QUIC connection behavior, loss detection, acknowledgment processing, retransmission logic, or path migration logic.

**Constraints**
- Use the `iter` package (*not* `golang.org/x/exp/iter` – this is for Go 1.23+).
- The iteration implementation must be fully self-contained within the `sentPacketHistory` methods; the caller never needs to manage an explicit iterator state.
- Do not introduce new exported types outside the `ackhandler` package; this is an internal refactoring.

ackhandler: use Go iterators to iterate over sent packets (#4952)


Add support for the HTTP CONNECT method in the HTTP/3 layer. Currently, `requestFromHeaders` assumes every request has a non-empty `:path` pseudo-header, but CONNECT requests are defined with no `:path` — only `:authority` (host:port). The parsing should be updated so that:

- CONNECT requests produce a valid `*http.Request` where `Method == "CONNECT"`, `RequestURI` is set to the raw authority value, and `URL.Host == authority` (no path or scheme).
- Non-CONNECT requests keep their current behavior and error checks (`:path`, `:authority`, `:method` all must be present).
- Validation for CONNECT: if `:authority` is missing or empty, return an error; if a `:path` pseudo-header is present, also return an error (it must be absent entirely), with an error message clearly indicating the invariant (`":path must be empty and :authority must not be empty"`).
- The rest of the request construction (headers, content length, etc.) should treat CONNECT requests the same as other methods except the URI handling.

This will allow the HTTP/3 server to correctly receive CONNECT requests (e.g., for proxying/tunneling) without rejecting them at the pseudo-header stage.

add support for the HTTP CONNECT method (#2761)


Improve the optimistic ACK defense so that it does not rely on a limited-size buffer for skipped packet numbers. Currently the sender tracks skipped packet numbers in a separate ring buffer and validates ACKs against that buffer in the packet number generator state. This has a hard cap on tracked skips, which can be exceeded and then fail to detect optimistic ACKs.

Integrate skipped packet numbers into the standard sent‑packet history as dummy entries. This eliminates the artificial cap and lets the existing ACK‑processing path check for the protocol violation of an ACK covering a skipped packet number.

Requirements and invariants:

- Every intentionally skipped packet number must be recorded in the sent‑packet history as a dummy entry that is not ack‑eliciting, not included in bytes‑in‑flight, not eligible for loss detection, and not queued for retransmission.
- Dummy entries must participate in old‑packet deletion and iteration, but must be excluded from all outstanding‑packet logic (first‑outstanding, length, retransmit on key drop or retry, etc.).
- If an ACK arrives that covers any dummy entry’s packet number, it is a protocol violation (the peer acknowledged a skipped packet number).
- Non‑ack‑eliciting packets must still be processed by the history so that gaps created by skipped numbers after them are correctly filled with dummy entries. (Prior to this, non‑ack‑eliciting packets were not stored in the history at all.)
- Remove the per‑generator `Validate` method and the `MaxTrackedSkippedPackets` constant; the validation now happens inside the sent‑packet handler when identifying acknowledged packets by looking at the already‑maintained history entries.
- The packet‑number generator no longer needs to keep its own history of skipped numbers. Its `Pop` logic remains the same (skip according to the random schedule) but stops appending to a history slice.

save dummy packets in the packet history when skipping packet numbers


```
Title: HTTP/3 surfaces raw QUIC errors; needs its own error type

When the HTTP/3 layer encounters a QUIC transport error (stream error or
connection close with application error), it currently passes the raw
`quic.StreamError` or `quic.ApplicationError` back to the caller. This
makes it hard for HTTP/3 users to:

- detect well-known HTTP/3 error codes (e.g. request cancellation)
  without importing the QUIC package
- distinguish whether an error was generated locally or by the remote peer
- rely on a stable error string that reflects the HTTP/3 error name
  (especially for recognized codes like `H3_REQUEST_CANCELLED`)
- keep their code decoupled from QUIC-internal error types, which may
  change over time

We need a dedicated HTTP/3 error type in the `http3` package that wraps
the relevant QUIC transport errors and exposes them as HTTP/3 errors.
The new error type should be used uniformly at all HTTP/3 public API
boundaries where an error can originate from the QUIC transport:
  - HTTP client round trips
  - reading request/response bodies
  - writing to the response writer in a handler

Acceptance criteria (visible behavior):

1. Wrapping only QUIC errors
   When an error is a `quic.StreamError` (stream-level) or a
   `quic.ApplicationError` (connection-level), the HTTP/3 layer returns
   an error of the new HTTP/3-specific error type. Non-QUIC errors
   (context cancellation, network I/O errors, parsing failures, etc.)
   must be passed through unchanged, preserving their original dynamic
   type and message.

2. Error code
   The error carries the HTTP/3 error code (type `ErrCode`) that
   corresponds to the original QUIC error code. For recognized codes
   (those defined in RFC 9114), the string representation shows the
   symbolic name (e.g. `H3_REQUEST_CANCELLED`). For unrecognized
   numeric codes, it must still be distinguishable as an HTTP/3 error,
   using a generic format such as `H3 error (0x1337)`.

   Important: The existing `ErrCode.String()` method currently returns
   `"unknown error code: 0x…"` for unrecognized codes, and that
   behavior must *not* change – it remains part of the public API and is
   used elsewhere. The new error type may use an internal helper, but
   the public `String()` must keep its output.

3. Remote vs. local origin
   The error exposes whether the error was sent by the remote peer
   (`Remote == true`, the common case) or was generated locally
   (`Remote == false`). In the error string, remote errors do not carry
   an extra label (because that is the default expectation), whereas
   local errors are explicitly marked, e.g.:
     `H3_REQUEST_CANCELLED (local)`
   or, if a message is present:
     `H3_REQUEST_CANCELLED (local): …`

4. Optional error message
   If the original QUIC error carries a diagnostic message (only
   `quic.ApplicationError` provides one), the HTTP/3 error should
   include it. The string format appends `": " + message` when the
   message is non-empty.

5. Where the conversion happens
   The conversion must happen at the point where errors leave the HTTP/3
   layer and become visible to user code. In particular:
   - `http3.client.RoundTripOpt` must return the new error (for both the
     error path and the response path when an error is attached to a
     response).
   - `body.Read` and `hijackableBody.Read` must return the new error
     when the underlying stream read fails with a QUIC error.
   - `responseWriter.Write` and `responseWriter.FlushError` must return
     the new error when the underlying stream write/flush fails with a
     QUIC error.

6. Backward-compatible error inspection
   Users must be able to use standard `errors.As` to check for the new
   error type and inspect its fields (`ErrorCode`, `Remote`,
   `ErrorMessage`). The error must implement the `error` interface
   and have a meaningful string representation as described above.

The goal is to let users write HTTP/3-aware code that no longer needs to
import or know about `quic.StreamError` / `quic.ApplicationError` for
common error handling.
```

http3: introduce an HTTP/3 error type (#4039)


When calling `RoundTripper.Close` (or `CloseIdleConnections`) on an HTTP/3 `RoundTripper` while a new QUIC connection is still being dialed for an outstanding request, a nil pointer dereference crash occurs. The same crash can happen if `CloseIdleConnections` is called while a dial is in flight.

The `RoundTripper` should be safe to close at any time, even during an active `RoundTrip` call that hasn’t yet established a connection. Closing the `RoundTripper` should gracefully interrupt any in-progress dials, wait for those dial goroutines to finish, and only then close any successfully established connections. This must be done without data races on the connection field.

The same safe shutdown behavior is expected from `CloseIdleConnections`: if an idle entry still has an ongoing dial goroutine (connection not yet obtained), closing it should cancel that dial and clean up without panicking.

The desired invariants are:

- No nil pointer dereference when a `roundTripperWithCount` struct has a nil `conn` because the dial hadn’t completed.
- All pending dials are cancelled when `RoundTripper.Close` is called, and the close operation waits for the dial goroutines to terminate.
- The same safety applies to `CloseIdleConnections` for entries with zero usage count.
- No goroutine leaks from abandoned dial attempts.

http3: fix race condition when closing the RoundTripper (#4458)


```
http3: fix double close of chan when using DontCloseRequestStream

When an HTTP/3 round trip encounters a request-level error (a `requestError` from the server handler) and the `DontCloseRequestStream` option is true, the request-done channel is closed twice, causing a panic. The client should instead return the error immediately after handling the request error and closing the underlying QUIC connection, without proceeding to the later "close request stream" logic.
```

http3: fix double close of chan when using DontCloseRequestStream (#3561)


The HTTP/3 client response (`RoundTripper`) never sets `http.Response.ContentLength`, regardless of whether a `Content-Length` header is present in the server's response. This breaks callers that rely on `ContentLength` to determine body size or to detect chunked vs. fixed-length bodies.

Implement content-length propagation according to the rules in [RFC 7230 §3.3.2](https://tools.ietf.org/html/rfc7230#section-3.3.2). Specifically:

- Do **not** set `ContentLength` (leave it at the default −1) if any of these hold:
  - A `Transfer-Encoding` header is present.
  - The response status is 1xx (informational).
  - The response status is 204 No Content.
  - The request method is `CONNECT` and the response status is 2xx.
- Otherwise, if exactly one `Content-Length` header field is present and its value parses as a valid integer, set `res.ContentLength` to that integer.
- If multiple `Content-Length` values are present or the value is unparseable, leave `ContentLength` unset (−1).

Additionally, ensure that `ContentLength` is extracted **before** the gzip-handling path that may delete the `Content-Length` header from `res.Header`. When a gzip-encoded response carries a `Content-Length`, the response object must still have `ContentLength` set to the original value, even though the header is stripped during decompression.

set the Content-Length for HTTP/3 responses


**Feature request: Broadly advertise all active HTTP/3 listeners in Alt-Svc, and use the correct ALPN-based header format**

---

### Problem

When an `http3.Server` is configured to listen on multiple endpoints (e.g., via `Serve` with several packet connections or listeners), its `SetQuicHeaders` method currently only advertises a single port. The port is taken from `Server.Addr` or an explicit `Server.Port`, and the `Alt-Svc` header is built on every call by parsing the address and extracting the port. This has several undesirable effects:

- **Clients only discover one port**, even if the server is reachable on others.
- **The header format is outdated** – it uses the deprecated `quic=":port"; v="…"` form instead of the HTTP/3 ALPN identifiers `h3` and `h3-29`.
- **Performance is poor** when `SetQuicHeaders` is called for every HTTP response, because the port extraction and string construction are repeated each time.
- **Listeners without a port** (e.g., via a `PacketConn` that wraps a non-UDP transport) cannot contribute, and there is no clear fallback when absolutely no port is available.

### Desired behaviour

1. **All listening ports are advertised**
   When the server has multiple active listeners, the `Alt-Svc` header must include an entry for every distinct port on which the server is actually listening. This allows clients to discover and use any of the reachable endpoints.

2. **Correct HTTP/3 Alt-Svc format**
   The header must advertise the appropriate ALPN tokens for the server's supported QUIC versions. For example:
   `Alt-Svc: h3=":443"; ma=2592000, h3-29=":8443"; ma=2592000`
   (the exact set of versions depends on the `QuicConfig` or the default supported versions).

3. **Explicit port override still works**
   Setting `Server.Port` to a non-zero value must take precedence over any listener-derived ports. When `Port` is set, *only* that port should be advertised, regardless of the listeners’ actual addresses.

4. **Fallback for listeners without a port**
   If a listener’s address does not contain a port (e.g., a Unix domain socket), the server should still attempt to advertise the port from `Server.Addr` (when set), provided no other listener yielded a port. If no port can be determined at all, `SetQuicHeaders` should return a descriptive error rather than silently producing an empty/invalid header.

5. **Cached, concurrency-safe computation**
   The Alt-Svc header value should be computed once and cached. It must be recomputed only when the set of listeners changes (listener added or removed). `SetQuicHeaders` should be safe for concurrent calls from multiple goroutines without introducing new data races or excessive locking.

6. **“No port” error**
   When the server has no listeners, or all listeners lack a port and `Server.Addr` is empty/unresolvable, `SetQuicHeaders` must return an error that clearly communicates the condition (for example, `ErrNoAltSvcPort`).

### Acceptance criteria (observable)

- After calling `ListenAndServe` with multiple listeners (or `Serve` with multiple packet connections), `SetQuicHeaders` adds an `Alt-Svc` header that contains entries for **each distinct port** of those listeners.
- If `Server.Port` is explicitly set, the header **only** contains entries for that port, even when listeners on other ports exist.
- When a listener that is not associated with a resolvable port is added alongside one that is, the port from the resolvable listener is still advertised.
- When no listener provides a usable port but `Server.Addr` contains a valid port, that port is advertised.
- Repeated calls to `SetQuicHeaders` produce the same result without re-parsing addresses or re-computing the header string (e.g., there should be no repeated calls to `SplitHostPort` / `LookupPort` on every invocation).
- Adding a new listener causes the header to be updated immediately; subsequent `SetQuicHeaders` calls reflect the new port set. Removing a listener similarly updates the set.
- Concurrent calls to `SetQuicHeaders` complete without data races.
- If no port is available (no listeners, or all listeners have no port and `Server.Addr` is missing or unparseable), `SetQuicHeaders` returns a dedicated error.

advertise multiple listeners via Alt-Svc and improve perf of SetQuicHeaders (#3352)


## Bug: HTTP/3 request cancellation does not abort QUIC stream opening

When using the `http3.RoundTripper` as an HTTP transport, cancelling a request (e.g., via `context.WithCancel` or a deadline) should abort the entire round‑trip, including the initial step that establishes a new QUIC stream for that request. Currently the request’s context is ignored while waiting for a stream to become available; if the session’s stream concurrency limit is reached, the client blocks until a stream slot opens, regardless of cancellation. This causes canceled or timed‑out requests to hang longer than expected, violating the standard HTTP client contract that a canceled `http.Request.Context` must cause `RoundTrip` to return promptly with a context error.

### Acceptance criteria

1. Cancelling the `http.Request.Context()` before or during the stream‑creation phase must cause the `http3.RoundTripper` to abort the trip immediately, returning a context cancellation or deadline error (e.g., `context.Canceled` or `context.DeadlineExceeded`).
2. The fix must not alter the error returned when cancellation occurs after the stream is already open and data is being transferred.
3. The change must apply to all `http3.RoundTripper` methods that perform a QUIC round‑trip (the common `RoundTrip` path), without affecting the client’s handshake or connection-establishment logic.

use the HTTP request context when opening the request stream


```
Request: Improve HTTP/3 response writer buffering and flush semantics.

Currently, the HTTP/3 server's response writer writes every call to Write directly to the underlying QUIC stream. This generates many small network transmissions and can be inefficient. Furthermore, the response writer states that it implements http.Flusher, but the Flush method is a no‑op – it does not actually flush any data. Handlers that rely on Flush to push data to the client (e.g., for streaming or chunked responses) may see delayed or incomplete delivery.

We need the response writer to buffer writes internally, so that data is sent to the QUIC stream only when the buffer is full or when the handler explicitly calls Flush. The Flush method must actually flush the buffered data to the underlying stream.

Additionally, to prevent data loss when a handler finishes without calling Flush, the HTTP/3 server should automatically flush any remaining buffered data after the handler returns.

Edge‑cases:
- If flushing the buffer fails, log the error but do not panic or crash.
- The existing logger available in the response writer should be used for error logging.

Acceptance criteria:
- Writes to the HTTP/3 response writer are buffered, not written directly to the QUIC stream on every call.
- Calling Flush on the response writer causes the buffered data to be written to the QUIC stream.
- After a request handler completes, any data left in the buffer is flushed automatically.
- The http.Flusher interface continues to be satisfied.
```

use a buffered writer for the http3 response writer


We need the `http3` server's response writer to support hijacking, so that handlers can obtain the underlying `StreamCreator` and perform raw QUIC stream operations instead of using normal HTTP response semantics.

The `Hijacker` interface already exists (in `body.go`) and is implemented by the request body; we need the same interface on the response writer. A hijackable response writer must return a valid `StreamCreator` from its `StreamCreator()` method, giving access to the QUIC connection that owns the response stream. The server already holds the connection when handling a request—it just needs to wire it into the response writer.


**Title:** http3: qlog recorder closed too early, events from active stream handlers are lost

**Area:** `http3` connection lifecycle, qlog recording

**Report:**

When using the HTTP/3 client or server with qlog tracing enabled, the qlog recorder is closed as soon as the underlying QUIC connection context is done. However, at that point HTTP/3 stream-handling goroutines (request streams, control streams, unidirectional streams, and the datagram receive loop) may still be running. Those goroutines can attempt to record qlog events after the recorder has been closed, which means:

- qlog events that should be part of the connection trace are silently lost.
- Concurrent access to a closed recorder is undefined and may cause data races or panics.

**Expected behavior:**
The qlog recorder must stay open until every goroutine that might emit a qlog event has finished. The moment when it is safe to close the recorder is after all HTTP/3 stream activity has completed, not merely when the QUIC connection ends. The QUIC connection end should act as the trigger to *begin waiting* for the HTTP/3 work to drain, and only then close the recorder.

**Safety/edge-case requirements for a correct fix:**

1. **All event-producing paths must be tracked.** This includes at least: handling of bidirectional request streams, processing of the control stream, handling of incoming unidirectional streams, and the background datagram-receive goroutine (when datagrams are enabled).
2. **Nil-recorder handling must be preserved.** When no qlog recorder is configured, no extra coordination should be introduced (no waiting, no extra goroutine tracking).
3. **Early termination / handshake failure must be handled.** If the connection fails during the handshake or before the control stream is fully set up, the coordinator must still eventually close the recorder when all in-flight work has completed, and must not block indefinitely.
4. **No premature close on “streams empty.”** The recorder must not be closed on the `onStreamsEmpty` signal, because datagram handling and other non-stream work may still be in progress.
5. **Thread safety.** The close must happen solely after the last possible event emission, without race conditions.

**Impact:**
When qlog tracing is enabled for debugging or monitoring, the produced logs are incomplete and unreliable. This makes it harder to diagnose connection issues in production systems that rely on quic-go’s HTTP/3 implementation, such as load balancers, proxies, and CDN services.

http3: close qlogger after all streams have been handled (#5524)


The pacer in `internal/congestion` uses a fixed maximum datagram size (the IPv4 initial packet size) for its token bucket calculations, including burst size and the time-until-send threshold. This value stays constant throughout the connection, even when the actual maximum datagram size is updated via path MTU discovery. As a result, connections that negotiate larger packet sizes (e.g., 1350 bytes or more) experience incorrect pacing: the pacer may allow bursts that are too large relative to the true packet size, or may throttle sends too aggressively because it still thinks each "packet" is the initial small size.

The pacer should use the connection's current maximum datagram size, not the fixed constant. Specifically, it should be initialized with the initial maximum datagram size, and its state should be updated whenever the congestion controller's `SetMaxDatagramSize` is called. The burst cap should be expressed as a number of packets multiplied by the current `maxDatagramSize` rather than a fixed byte constant, so that it scales correctly with the actual packet size.

Make sure the cubic sender propagates the size change to the pacer. Note that cubic is currently disabled, but the change should not interfere with its future re‑enablement.supervision_value: 3
difficulty: 3
reasoning: The task involves connecting the pacer's internal state to the actual max datagram size that is updated elsewhere. It requires understanding that `SetMaxDatagramSize` already exists on the sender but is not propagated to the pacer, and then making the pacer use a dynamic field instead of a package-level constant. The fix is bounded and localized, but it demands a modest investigation: checking the call chain, adjusting burst calculation semantics, and ensuring tests still pass. This is a solid medium-difficulty, ordinary-value task.

use the actual maximum packet size in the pacer