# Terminal store-fault and Logout continuation review — 2026-10-08

Session `codex-review-faults-2207` started from `24058b1b0` in its own locked worktree. Three parallel reviewers covered C++, Java and Rust; root reviewed the changes and owned all builds. Heavy jobs were serialized through the shared host lease, with a maximum of 16 compiler workers and outputs under the session build root. No .NET source or reserved C++ FIXP policy was edited.

## Findings and corrections

### C++ nested Logout retirement

A diagnostic Logout can itself encounter a store refusal, a failed synchronization, or sequence exhaustion. Those inner paths already disconnect and notify the application. The enclosing Logout-and-disconnect helper then repeated the notification and replaced the underlying failure reason with its original diagnostic. A peer Logout reply also overwrote the store-fault reason, although its earlier notification meant that branch did not duplicate the callback.

The two enclosing helpers now preserve completed inner retirement. Separate CompID and SendingTime error paths also stop after a Reject whose post-send checkpoint failure has already performed cleanup. Previously `SENT` caused those paths to send one more Logout and consume another sequence on the retired connection. The existing policy of a best-effort cleanup Logout after an already visible frame is preserved.

The 128-case matrix covers runtime modes, both session roles, peer Logout, low sequence diagnostics, invalid ResendRequest, sequence exhaustion, CompID/SendingTime rejection, and failure before preparation, during synchronization, after Reject, or after the intended Logout. It checks callback counts, reasons, wire frames, store operations and sequence numbers. Against the original header, 80 cases fail and 48 controls pass; all 128 pass with the correction. The full existing reliability binary also runs under ASan/UBSan. This does not add a general guarantee for arbitrary reconnect/reinitialization from a lifecycle callback.

### Java deferred recovery retirement

The ordinary receive owner could fail its deferred-high administrative checkpoint and then continue dispatching the current application against a released store. Its public injection entry also continued admitting later frames after a terminal store fault. It now stops after that failed checkpoint, rejects later admission, and respects a failed batch flush at entry.

Both ordinary and TCP owners sent the deferred TestRequest's Heartbeat before checkpointing consumption of that TestRequest. The checkpoint now precedes the reply, so a failure cannot acknowledge progress that the store refused. The TCP deferred-recovery guard also permits a valid confirmation of a locally sent Logout to reach the existing validation and Logout handler; previously that guard swallowed the confirmation.

The new suite contains 60 cases across transcript, worker initiator, single TCP acceptor and multiplexed TCP acceptor, using three public durable stores: async-file, QuickFIX C++ and QuickFIX/J. It closes the real store to refuse the first checkpoint, and uses a delegating store proxy to refuse only the consumed administrative boundary. Healthy recovery, callback-triggered stop, and local Logout confirmation are controls. All fixtures use `TestTempFiles`. Against the original Java source, 15 cases fail assertions and six TCP confirmation cases time out; 39 controls pass. The required-native guard also passes. The worker owner already interrupted the first-checkpoint continuation through its callback epoch guard; the direct transcript owner reproduces that dispatch defect.

Cross-review rejected an initially proposed `status != LOGGED_ON` ordinary guard because it would also reject a valid Logout confirmation. The final guard uses the terminal latches; callback stop already interrupts continuation through the existing callback epoch mechanism.

### Rust queued output and fault precedence

`fail(E_STORE)` retired the batch but left bytes queued behind socket backpressure. Public `flush()` could subsequently write those bytes. Public administrative send methods could also compose and transmit another frame, and Logout could change the terminal status back to `LOGOUT_SENT`. The failure handler now discards unsent pending bytes as well as the batch, and the public send/flush entry points refuse the terminal store fault before side effects. Existing healthy flush and graceful Logout draining remain available.

For a non-durable store, an application frame may already have reached the transport before its save fails. The existing best-effort diagnostic Logout could then fail with `E_IO` or `E_OVERFLOW`, masking `E_STORE` through first-error retention. That allowed ordinary reconnect despite an inconsistent resend index. This specific cleanup path now retains the store failure as the terminal error; explicit offline reset remains the recovery operation. Ordinary transport/composition failures with a healthy store keep their existing reconnect behavior.

Sixteen regression methods cover 43 final combinations: both store orderings, inbound and outbound refusal, named public APIs, an already prepared frame, a real saturated Unix stream, healthy and graceful pending-output controls, cleanup composition/write failure, and offline reset versus ordinary reconnect. Eleven methods fail against the original production code and five controls pass; all 16 methods pass with the correction. Original methods stop at the first failed assertion, so the baseline is not evidence that every combination ran on old code.

## Verification

| Gate | Result |
|---|---|
| C++ original header with new regressions | 80 failing cases, 48 controls pass |
| C++ full reliability binary, optimized and ASan/UBSan | Pass; 128 new cases |
| C++11 open edition, layering and simulator | 147 selected/ran/passed; zero reported skips; 83 private binaries excluded |
| Private C++ mmap-store reliability binary | Pass; zero reported skips |
| Java original source, required JNI | 15 assertion failures, six expected confirmation timeouts, 39 controls pass; native guard passes |
| Java final focused recovery/store suite, required JNI | 473 unique cases pass; zero failures/errors/skips |
| Java full development / open, required JNI | 5,897 development / 5,454 open unique cases pass; zero failures/errors/skips |
| Rust original source with new regressions | 11 methods fail; five controls pass |
| Rust final focused / full default / full no-default | 16 focused methods pass; 1,437 full tests pass in each configuration, one existing doc example ignored in each |

The Java gates use JDK 21 with the Java 8 target and the standard relocated JNI library paths. Complete Surefire reports determine unique case counts and skips. The three telemetry-disabled checks skip in the normal configuration and execute in their dedicated Maven fork; their final reports must pass, and native-loading skips are never treated as passes. Sanitizers enable leak detection and fatal undefined behavior. Five engine files are bound by `tested-source.sha256`; public-export scanning reports zero findings. Independent reviewers checked all three production changes and their regression coverage.

No Windows, exchange NIC or latency benchmark is part of this pass. The corrections remove redundant failure-path work and preserve the normal path without new locks or allocations; no measured latency gain is claimed.

## Publication and retained evidence

Code commits: `7ae43d1a2` (C++), `ee213976d` (Java), and `3b2b84d34` (Rust). Only private `atrekes/libhft-devel` is a publication target. Before publishing: fetch/rebase, inspect incoming changes and outgoing commits, and verify exact tested source hashes. Main is updated only with `pull --ff-only`; only this session's claim block is removed.

Evidence is retained at `~/archive/libhft/reviews/2026-10-08-codex-review-faults-2207/` locally and on HP: original/final logs, complete reports, source snapshots and hashes, reproduction scripts, independent reviews, final patch and Git history bundle. Both archive manifests are verified before session worktree/build/scratch cleanup.
