v2.5.0
This release closes all 21 findings of the 2026-07-11 six-dimension review (#123-#143) plus the three in-flight fixes from the previous cycle (#112, #114, #116): every silent message-loss path found in the connection layer is fixed, the client survives abandonment and terminal closes without leaking, JetStream/KV behavior moved to spec/ADR parity, and the hot paths got measured performance work.
Message-loss fixes
- The reconnect buffer is no longer cleared before the flush write - a flush failure mid-recovery used to silently destroy every publish accepted during the reconnect window (#123).
- Transport
write()on a closed/never-connected socket throws instead of silently "succeeding" while sending nothing; recovery flips the connection offOpenbefore its first await so concurrent publishes buffer instead of racing the dying socket (#124). - The reconnect handshake starts from a clean protocol parser - a drop mid-payload could previously poison every reconnect attempt against a healthy server until the budget exhausted (#125).
- One failing frame no longer discards the other frames parsed from the same TCP chunk (slow-consumer overflow, failing PONG reply, fatal
-ERR); the heartbeat read now reports fatal frames instead of swallowing them (#128). subscribeQueue()buffers deliveries that arrive before the queue object exists (#129).- Atomic batch publish fails loudly with
UnsupportedFeatureExceptionon servers without batch support instead of silently storing the batch message-by-message (#130).
Lifecycle & memory
- An abandoned open connection is now garbage-collectable (the ping timer holds it weakly) and a destructor closes the socket - zombie clients no longer PING forever and steal queue-group deliveries (#126).
- Every terminal transition to
Closedreleases runtime state; a manualconnect()after reconnect exhaustion starts clean instead of resurrecting dead-epoch subscriptions as duplicate-delivering ghosts (#127). - Bucket wrapper caches, terminal-failure sockets, and post-commit batch payloads no longer accumulate (#133).
Spec / interop
- Stream and consumer names are validated client-side (nats.go parity) - dotted names no longer corrupt
$JS.APIsubjects or silently read a sibling stream via direct get (#131). - ADR conformance roundup:
idle_heartbeathonored and unknown pull-request keys rejected, ordered consumers pinnum_replicas: 1, KV buckets are created withdeny_delete/discard: new, KV keys enforce the ADR-8 charset, HPUB is guarded by the server's headers capability, service stats report real UTC, and the README documents the NATS 2.9+ consumer-management floor (#132). - Slow-consumer drops in
SubscriptionQueueare observable viadroppedCount()and the error listener; disconnect/unsubscribe backlog-discard semantics are documented (#134).
Performance (measured against a live NATS 2.12)
- Concurrent requests park instead of polling at 1 kHz: 200 in-flight requests idling 300 ms dropped from 425 ms CPU (a full core) to 92 ms (#135).
- Outbound writes are single-hop and JetStream acks lost two of their three fiber hops: +18% serial publish throughput, ~2x ack throughput (#136).
- The reconnect resubscribe replay is one write + one bounded drain instead of ~5 ms x subscriptions (#137).
- Atomic batch commits coalesce intermediates into 512 KiB segments: 1000-message commit ~2.6x faster (#138).
- Push/KV-watch deliveries dropped a per-message fiber hop and duplicate header parses: ~12% CPU end to end (#139); inbound parsing micro-overheads trimmed (#140).
Testing & CI
- First live-server reconnect coverage: a severing transport kills a real TCP session mid-idle/mid-traffic and asserts recovery and post-reconnect delivery (#141).
- Unit gate ~12 s faster (fractional ping intervals -
pingIntervalSecondsnow acceptsint|float, the release's one[feature]), ~250 rotted line-number pins fixed, behat tolerates exception subclasses (#142). - New nightly mutation-test workflow (03:17 UTC daily, 90% MSI gate);
composer infectionno longer dies at Composer's 300 s process timeout. - README corrections (#143) and the atomic-batch version-detection documentation rewrite (#130).
Full details in CHANGELOG.md. All 21 fixes shipped with falsifiability-verified regression tests (each demonstrated to fail against the pre-fix code) and were gated on phpstan level 8, the unit suite, and the live integration suite against NATS 2.12.