# QuickFIX C++ index whitespace and append recovery

Valid imported QuickFIX C++ indexes must retain their records, including the
last duplicate, when a store reopens and continues writing. This review found
two recovery defects: incomplete ASCII whitespace recognition in .NET and
Rust, and a missing append boundary after a complete unterminated final record
in C++ and Rust. The corrections are confined to store opening and recovery;
they add no locks or normal message-path work.

## Whitespace compatibility

The pinned QuickFIX C++ `FileStore::populateCache` reads records with
`fscanf("%" SCNu64 ",%" SCNi64 ",%zu ", ...)`. Its ASCII separators are space,
tab, line feed, carriage return, vertical tab and form feed. Pure Java's
`QuickFixFileStore.Cpp.isSpace` already accepts those six characters.

The managed .NET reader recognised only space, line feed and carriage return.
With valid tab, vertical-tab or form-feed separators, it could treat several
complete records as one torn suffix and truncate their index. The body stayed
on disk but its messages became unreachable. Rust's `is_ascii_whitespace`
excludes vertical tab, so its C++-layout reader also lacked that separator.
Both readers now use the same six-character ASCII set for splitting records,
recognising the final boundary and identifying a recoverable tail. No Unicode
whitespace expansion or file-format change is introduced.

The .NET baseline at `be41555c2`, with only the retained regression added,
fails 15 of 30 cases; the other 15 controls pass and none skip. The cases cover
terminated and unterminated complete records, last-duplicate replacement,
append and reopen, torn-tail repair, malformed interior records and an index
containing whitespace only. The new engine test is
`QuickFixCppIndexWhitespaceTests.cs`.

## Complete final record and later append

C++ and Rust accepted a final complete record without its separator but did
not restore that separator before writing another record. For example,
`2,6,4` followed by `3,10,5 ` became `2,6,43,10,5 `.

The C++ baseline accepts the original index and saves sequence 3, then fails
the fresh owner's open. Rust instead truncates the joined suffix on the next
open; the regression retrieves the earlier sequence-2 body `two` rather than
the last duplicate `LAST`. This is an observed stale-payload recovery result,
not merely an inferred format problem.

Both stores now restore one space only after a complete, compatible final
record has been admitted. C++ checks the final byte after successful index
validation. Rust postpones the write until its size, payload-read and append
position checks have succeeded. An already-delimited index stays unchanged;
existing torn-tail and incompatible-record refusal rules remain in place.
Pure Java and managed .NET already repaired this missing boundary.

The C++ physical-store regression and the Rust store unit each drive 12
cases: the six separators with and without a final separator. Each checks
last-duplicate selection, preserved inbound and outbound frontiers, body
bytes, the appended record and a fresh reopen. The C++ fixture now honours
`TMPDIR` so its scratch remains under the session build root.

## Reproduction and evidence

Raw logs, source pins, source patches and input manifests are retained under
`/home/yann/archive/libhft/dotnet-index-whitespace-20261012/`.
`baseline/` is the expected .NET failure, and `boundary-baseline/` is the
expected C++/Rust append-reopen failure. Core dumps are disabled for the
asserting C++ baseline child.

The drivers under `evidence/dotnet-index-whitespace-20261012/` keep the same
build-root, archive and CPU-coordinator conventions as the preceding replay
qualification. `run-regressions.sh` and `run-boundaries.sh` accept
`LIBHFT_EXPECT_BASELINE=1` for the corresponding unchanged production source;
the ordinary invocation requires the corrected tests to pass.
`run-qualification.sh` composes fresh default/open native builds, complete
.NET development/open suites, separate native pools, the nine selected
publication guards, focused recovery checks, C++ physical-store and pinned
QuickFIX C++ consumers, and the Rust file-store suite.

These are functional recovery checks. No NIC or ordinary-message latency
improvement is claimed.

The first full development attempt in `qualification/` reports 2708 passes,
one failure and four skips, then stops before the open/native-pool/guard and
C++/Rust suffix. Its failure is
`SessionRunnerTests.Runner_disposes_started_connections_when_later_start_fails`:
the probe's bind reports AddressAlreadyInUse after an ephemeral-port handoff
while other socket fixtures run in parallel. `SessionRunnerTests` now runs in
an exclusive test collection; its startup-failure and listener-disposal
assertions are unchanged. `qualification-serial/` is the separate retry, not
an overwrite of that failed attempt.

## Completed qualification prefix

The serialized run at `b595f55c6` passes both complete .NET suites and the
selected publication guards. Its source patch is empty, and its input
manifest includes the committed new regression.

| Check | Passed | Failed | Skipped |
|---|---:|---:|---:|
| Complete development suite | 2709 | 0 | 4 |
| Complete open suite | 2477 | 0 | 3 |
| Development native pool with two workers | 3 | 0 | 0 |
| Open native pool with two workers | 3 | 0 | 0 |
| Focused .NET store and recovery selection | 98 | 0 | 0 |
| Selected conformance guards | 9 | 0 | 0 |
| C++ complete-index append and reopen cases | 12 | 0 | 0 |

Both native libraries are rebuilt with GCC toolset 14 and retain 209 exports.
The .NET toolchain is SDK 8.0.131/runtime 8.0.31. Open compilation admits
144 sources without 67 private sources and refuses six feature boundaries.
The guards are a selected 9-of-362 slice, not a full conformance sweep.

The native-pool executions close the full suites' pool-only broadcast skip.
Other skip identities are multicast loopback, the refusal case requiring a
non-Linux platform, and the development-only opt-in live bypass receiver. No
required-native availability test skips.

The next Rust compile exceeds its 300-second budget before executing a test.
The serialized wrapper therefore stops before the C++ full physical/pinned
QuickFIX and Rust full file-store suffix. The table is a completed prefix,
not a claim that the wrapper completed. `continuation/` rechecks the identical
source manifest and resumes that suffix with a 600-second compiler budget and
`CARGO_PROFILE_TEST_DEBUG=0`; this changes debug-symbol generation, not compiler
optimization or any benchmark prescription. The original compiler timeout
and driver hashes remain in `qualification-serial/`.

## Completed continuation

The continuation at `7ace1fd55` completes successfully on source bytes identical
to the prefix; its source manifest is checked before and after execution.
The Rust test build without debug symbols completes in 87 seconds. Its new
regression passes all 12 separator/boundary combinations, and the selected
classic FIX and FIXP file-store tests report 105 passed, zero failed and zero
ignored. This is not the complete 1455-case Rust library suite.

C++ again passes the 12 boundary cases and its complete physical-store unit.
The actual pinned QuickFIX C++ FileStore consumer passes layout, reopen,
replay, reset and crash-tail checks through `quickfix-cpp-filestore-live-smoke`;
it is not skipped and is not described as the full upstream QuickFIX suite.
Scanning all six changed engine files reports zero findings. Continuation
source and driver hashes remain unchanged.

Together, the completed prefix and unchanged-source continuation qualify the
corrections. Neither the original failed development run nor the compiler
timeout wrapper is relabelled as a completed full run.

## Acceptor retirement integration

After rebasing onto `e3a79d688`, the bounded run at `18c0f11e0` verifies that
all store, .NET and Rust source bytes are unchanged from the qualification.
The only changed selected inputs are the C++ acceptor-retirement header,
its new regression and its Makefile registration. The new actual-TCP
retirement test passes all four remove/shutdown and callback/no-callback cases.
Both native libraries are rebuilt again; 119 development and 113 open
selected .NET tests pass with zero failures or skips, including listener
ownership, native file stores, required native loading and recovery. The six
changed engine files again have zero public-scan findings. Source and driver
manifests remain unchanged throughout this integration supplement.

The final Rust supplement at `76f584ed8`, after importing the initiator's
unattached-writer cleanup, again passes the new 12-combination regression and
all 105 selected file-store tests. The upstream real-writer unwind and primary
ownership integration suite passes all 11 tests with no ignored cases. C++'s
12 boundary cases pass again. `rust-integration/` retains this supplement,
its source pin and unchanged source/driver manifests; debug symbols remain
disabled for the functional Rust test build. The complete .NET counts remain
at their prefix revision, with the newer 119/113 integration results attributed
separately above.

## Native clock integration

The final supplement at `a2e0e8cdf`, after importing the ruled C++ and .NET
Native CheckLatency/MaxLatency changes through `d66aa9646`, completes
successfully. Its protected-input comparison confirms that the corrected
stores, managed replay path and listener-ownership fixture remain byte-identical
to the complete qualification prefix. All other changed selected inputs are
enumerated in `clock-integration/integration-equivalence.json`.

C++ architecture and configurable-validation units pass. Fresh default/open
native libraries retain 209 exports. The combined clock, native option,
listener ownership and store/recovery selections pass 152 development and
137 open tests, with zero failures or skips. All four selected binding,
documentation and parity guards pass. The source and driver manifests remain
unchanged through this supplement. Earlier full-suite counts remain tied to
their earlier source revision; this is a focused protocol integration check.
