v2.5.2
Safer HTTP Client Runtime¶
v2.5.2 hardens the HTTP client around interrupted streams, retries, telemetry, and deterministic LLM-backed testing. The release keeps the common HTTP API focused while making failure and replay behavior more explicit.
Transport and Streaming Correctness¶
- cURL streaming now checks completed transfer results from
curl_multi_info_read(); dropped connections are no longer reported as successfully completed streams. - Redirect response headers no longer leak or merge values across response hops.
- Guzzle, Symfony, and cURL transport failures are normalized more consistently into connection, timeout, network, and HTTP request exceptions.
- Stream completion is reported only after natural iterator exhaustion. Interrupted
ArrayStream,IterableStream, SSE, and decorated streams remain incomplete. - Raw iterables passed to
HttpResponseare rejected instead of being silently buffered. UsestreamingFromIterable()orbufferedFromIterable()explicitly. - SSE parsing emits a final unterminated event and enforces a bounded parser buffer.
Safer Retries and Idempotency¶
Retry-Afterdelays are capped bymaxDelayMs, preventing a server response from blocking a PHP worker for hours.- Retries are limited to idempotent methods by default.
POSTandPATCHrequire an explicit opt-in and should be paired withIdempotencyMiddlewarewhere supported. - Generated idempotency keys remain stable across retry attempts and middleware ordering. Header matching is case-insensitive.
- Numeric and RFC 7231
Retry-Aftervalues are parsed defensively, including invalid, negative, fractional, and oversized values.
Privacy-Safe Diagnostics and Telemetry¶
HTTP driver events and telemetry no longer attach complete request or synchronous response bodies by default. They retain safe URLs, redacted headers, status data, and body-size metadata instead. Optional streaming telemetry capture is bounded and redacts captured content.
Common credentials in URLs, authorization headers, cookies, response metadata, and known body fields are sanitized before record/replay persistence. Prompts, model outputs, PII, and provider-specific secrets may still be present in fixtures and must be reviewed before sharing.
Versioned HTTP Record/Replay¶
The HTTP client now exposes an immutable cassette API through
RecordReplayMiddleware::recordTo('/tmp/http-cassette') and
RecordReplayMiddleware::replayFrom('/tmp/http-cassette').
The cassette store provides:
- versioned, atomic filesystem persistence with payload bytes outside metadata JSON;
- strict ordered sessions for repeated and interleaved requests;
- hermetic replay by default, with live passthrough requiring explicit policy;
- typed missing, mismatch, exhausted, corrupt, and unsupported-version failures;
- canonical JSON request matching while preserving array order and scalar types;
- binary-safe, lazy, one-shot streamed replay without loading all chunks into a PHP array;
- boundary-safe credential sanitization for streamed payloads; and
- an isolated compatibility reader for existing pre-versioned one-file recordings.
Existing RecordingMiddleware and ReplayMiddleware integrations remain available as
deprecated compatibility surfaces. New integrations should use recordTo(),
replayFrom(), recordWith(), or replayWith().
Deterministic Example Verification¶
The shared examples/boot.php integration supports pass, record, and hermetic
replay modes without editing each example. Use just examples-record
getters_and_setters, just examples-replay getters_and_setters, or
just examples-replay-all to run selected examples or the corpus.
Replay provisions dummy provider keys only for configuration resolution and serves
recorded responses without contacting the provider. The new examples-qa skill
documents how to interpret hub results: ASSERT and ERROR are real failures,
live-only FLAKY results are tolerated but replay failures are not, and skipped
broken/no-replay examples are not evidence of a pass.
Upgrade Notes¶
- Replace direct mutable
RecordReplayMiddlewareconstruction and setters withrecordTo()orreplayFrom(). UserecordWith()/replayWith()for custom stores. - Replay now fails closed for missing interactions. If live fallback is genuinely
required, opt into
ReplayMissPolicy::Passthroughexplicitly. - Existing pre-versioned recordings are read through the compatibility adapter. New recordings use the versioned cassette layout; refresh fixtures when convenient.
- Review retry behavior for
POSTandPATCH: they are no longer retried by default. Opt in only when the endpoint's idempotency contract is understood. - Replace raw iterable response construction with
HttpResponse::streamingFromIterable()orHttpResponse::bufferedFromIterable(). - Treat cassettes and diagnostic output as sensitive application data even after automatic credential sanitization.