# Classic FIX store lifecycle and recovery review — 2026-10-12

Session `codex-review-replay-1012` reviews persistent store ownership, concurrent
shutdown and sequential recovery. Work uses isolated worktrees and
`LIBHFT_BUILD_ROOT`, with one heavy build or test job per host. HP jobs hold both
CPU leases, use CPUs 0–7 and cap compilation at j4; Scan uses j8 and additionally
waits for foreign heavy jobs even when the shared lease is available.

## .NET retired QuickFIX writers

After `Dispose()` returned, either managed QuickFIX store could still execute
`UpdateSessionState`. These path-based writes reopened checkpoint files after
the old store had released writer ownership. A replacement writer's persisted
41/43 checkpoint could therefore be replaced by the retired object's 2/3.
Both layouts now reject the update with `ObjectDisposedException` before any
sequence state or file changes. This corrects calls after disposal; it does
not add support for concurrent owner mutations.

The new two-case regression closes the retired writer, opens its replacement,
attempts the retired update and finally reopens the store from disk. Reading
only the live replacement's cached counters would miss the corruption. The
tests-only baseline reproduces both failures (expected outbound 41, actual 2)
while the required native-library control passes: 2 failures, 1 pass, no skips.

Each corrected edition passes 60 cases: 2 retired-checkpoint regressions,
22 writer-ownership cases, 17 C++-layout compatibility cases, 12 J-layout
compatibility cases, 6 reset cases and 1 required native-library control.
Exact discovery and executed TRX identities match, with no failures or skips.
All 1,828 C++/.NET/shared/build input hashes match the integrated candidate.

Qualification uses HP SDK 8.0.131/runtime 8.0.31 and GCC 14.2.1/C++17. Both
editions build their native library at its normal build-root location. The
development candidate reuses the unchanged native artifact from its baseline;
managed outputs are fresh. Eight build/discovery/test stages complete, and the
test-copy native hashes match the built artifacts. The independent audit
reparses the raw reports and checks both baseline failures and candidate IDs.

The .NET correction was published as `918428266` after rebasing over three
coordination/publishing/benchmark commits. All 1,828 qualified input hashes
still matched after that integration.

## Java close admission

`FileStore` allows one owner thread to mutate the store and any thread to call
`close`. Previously the closed flag was set only at the end of close. After
the background flusher had exited but before final cleanup obtained the file
gate, the owner could append two frames successfully. With one retained RAM
slot, the second frame evicted the first even though both fit the flush ring;
final cleanup saved only the retained frame and reported no dropped flushes.

The correction retires admission before stopping the background consumer.
An owner publishes its active mutation, then checks the closing flag again
before changing any state. Close waits for an admitted operation to finish.
Append, checkpoint, sequence notes, sync and reset all follow this handshake,
and every admitted path clears the active flag in `finally`. Existing sync
and reset I/O still takes the file gate. Admission adds volatile operations
without a monitor, allocation or compare-and-swap on the owner path.

The seven-case tests-only baseline reproduces five failures: loss of the first
accepted frame, acceptance of a late checkpoint, mutation by a late sequence
note, acceptance of a late sync and acceptance of a late reset. The two controls
confirm already-admitted sync/reset operations complete before close returns.
Baseline results are 5 failures, 2 passes, no errors or skips. Fixtures use
`TestTempFiles` and preserve the journal's process-owned file lock by opening
their blocking I/O test descriptor on a separate dummy file.

The corrected development and open editions each pass the exact 195-case
inventory, including all seven new cases, store lifecycle/recovery/locking,
borrowed replay buffers, duplicate durability, FIXP persistence, classic FIX
async-file sessions and the required native-library control. Both editions
use fresh normal-path JNI builds (GCC 14.2.1/C++11/LTO1, j4) and have zero
failures, errors or skips. The preceding .NET source overlay, native artifacts
and evidence are unchanged by the Java qualification.

The Java correction was published as `42a42e836`. All 2,156 Java/C++/shared
candidate inputs and the preceding 1,828 .NET inputs still matched after the
benchmark-only rebase. The six-stage gate, raw reports, native products and
source pins comprise 2,511 copied and hash-verified files in
`evidence/hp-java-file-store-close`; `java/file-store-close-final-audit.json`
and `java/root-independent-audit.json` recheck the reports and inventories.

## Rust sequential journal recovery

`recover_records` sought to the next record before every header read even
though each successful header/payload read had already left the cursor there.
The correction seeks to zero once before scanning, then reads sequentially.
The final append-position seek remains before tail truncation, preserving the
existing refusal-before-repair ordering for seek errors. Invalid/torn tails,
oversized records, checkpoint checks and callback failures keep their existing
handling. The same journal recovery serves classic FIX and FIXP stores.

The independent probe generates 10,000 FIX frames and 100 interspersed
checkpoints (50 legacy, 50 CRC), totaling 1,042,094 bytes. A path-filtered
`strace` baseline measures 10,101 successful `lseek` calls. Every target and
return value equals the current record cursor, plus final append positioning.
Both candidate editions measure exactly two seeks, with identical recovered
payloads, timestamps and sequence values and an unchanged journal hash.
This measures eliminated recovery syscalls, not elapsed startup time or
message throughput. Empty journals now perform two seeks instead of one;
the reduction applies when scanning multiple complete records.

The fixture journal SHA-256 is
`a5b870a7a1f60ca1cbe03ed1996c3f9f04a24a39f869146146d512ea37565cf3`;
the exact expected and recovered output SHA-256 is
`8b2f8588b75804292c795065fb12907a4963fbc450892e210cdce931eefd0c48`.

| Rust edition | Store unit tests | Lock-inheritance integration tests |
| --- | ---: | ---: |
| Development | 105 | 10 |
| Open | 103 | 6 |

All selected tests pass with zero failures or ignored cases, and exact listed
and executed inventories match. Scan uses rustc/Cargo 1.92.0, default features,
test/dev debug information disabled, j8 and one test thread. Actual build-script
outputs distinguish the development and open editions; Cargo fingerprints
confirm default features including telemetry counters. All 848 qualified
Rust/shared input hashes match the integrated candidate. No additional production
code or fixtures change for the recovery optimization.

## Evidence

Final evidence is retained under
`~/archive/libhft/reviews/2026-10-12-codex-review-replay-1012/`.
The .NET lane contains `dotnet/hp-disposed-evidence`, the frozen runner package,
`dotnet/hp-disposed-final-receipt.json` and
`dotnet/root-independent-audit.json`. Its 375 evidence and artifact files were
copied from HP and verified against their remote hashes.

Rust raw traces, outputs, generated journal, exact test inventories, commands,
edition/feature proof and source/artifact hashes live in `rust/scan`.
`rust/scan/final-independently-verified.json` and
`rust/root-independent-audit.json` independently check the recovered bytes,
seek offsets and selected test executions.

Final integration imports upstream `aa920a170`, which wraps the two existing
managed TestRequest-without-TestReqID rejects in validation-counter updates,
plus its counter tests and guide. These three .NET files differ from the
qualified input inventory. They do not change either QuickFIX store or the
checkpoint/reset paths exercised here; the qualification does not cover the
new counter behavior. All Java/C++ and Rust inputs remain identical. Other
upstream imports concern coordination, publishing and benchmark tooling.

This is a scoped qualification, not a full repository suite or a throughput
benchmark.
