# Managed QuickFIX write failure recovery

The managed .NET QuickFIX C++ and QuickFIX/J stores allowed another append after
an I/O failure left a partial index record. The next append could report success
while extending that damaged record. Both stores now refuse further mutations
until close/reopen. A failed reset also refuses replay because the old epoch may
already be partly erased. Flush ordering and durability policy are unchanged;
append and replay acquire no new lock.

## Confirmed corruption

The baseline production pin is `20d6ae187`; the original two-case fixture and
focused driver are retained by `996784b3f`. Both cases fail with zero passes or
skips. A one-shot injected stream writes one actual index byte, flushes it, then
throws `IOException`. The fixture attempts sequence 3 after the failed sequence
2 and closes/reopens the store before asserting the result.

In the C++ text layout, the unfinished `2` joins the next record's `3` to form
sequence **23**. The second append reports success, but reopen retrieves its
`third` payload at 23 rather than 3 and advances the outbound counter to **24**.
In the Java binary layout, the accepted append follows a one-byte fragment and
reopen throws `InvalidDataException` for the malformed index.

Raw baseline logs, TRX counters, source manifests and toolchain information are
retained in
`evidence/dotnet-quickfix-write-fault-20261012/retained/baseline/`.
The baseline driver requires `LIBHFT_EXPECT_BASELINE=1`; ordinary execution
requires every selected case to pass without skips.

## Correction and recovery boundary

Append and checkpoint I/O failures, including denied checkpoint paths, latch
the writer failure. Subsequent appends return false; checkpoint updates and
reset throw `IOException` without changing the files. Disposal still releases
the existing ownership fences, and reopening reconciles the persisted index.
Invalid sequence arguments do not poison a healthy writer.

Checkpoint updates publish cached counters only after their writes succeed.
The Java layout's two checkpoint files are not an atomic pair: a failed second
write can leave the first changed on disk. The instance keeps its last published
counters and refuses further mutations; reopen reads the actual disk state.
If append's body and index completed before its checkpoint failed, that record
remains replayable and reopen recovers its successor from the index.

Reset marks the instance unusable before any truncation. It publishes an empty
index and counters of one only after every reset operation succeeds. Failure
retains ownership and the previous published counter metadata, but refuses
payload reads and replay hints. This does not make the multi-file reset atomic
or promise recovery of an epoch whose files were already truncated.

## Focused verification

All **30 cases pass with zero failures and zero skips** on .NET SDK 8.0.131.
The source pin is `996784b3f` plus the retained `source.patch`; its source and
driver manifests verify unchanged at the end of the run. Results are retained
in `evidence/dotnet-quickfix-write-fault-20261012/retained/fixed-focused/`.

The cases cover the original partial-index/reopen failures, body and index
write/flush failures, sender and target checkpoint failures, a checkpoint
failure after index commit, body and index truncation/flush failures during
reset, reset checkpoint and timestamp failures, invalid sequence controls,
successful reset reuse and recovery. Mutation checks compare the file bytes
before and after refused operations and check that another writer is refused.

Stream injection shares the owner's exact `SafeFileHandle`; body snapshots use
`RandomAccess.Read` on that handle. Opening and closing a second body descriptor
could release a process-owned POSIX record lock and invalidate the ownership
check, so the fixture does neither.

## Integrated qualification

The integrated source pin is `f8b11842f`. Both native libraries were freshly
built with GCC 14; the checks use .NET SDK 8.0.131 on Linux under an
`isol-no-shared` lease. Source, driver and SDK-helper manifests verify unchanged
after the run. Raw outputs are retained in
`evidence/dotnet-quickfix-write-fault-20261012/retained/qualification/` and the
original archive `/home/yann/archive/libhft/dotnet-quickfix-write-fault-20261012/`.

| Check | Passed | Skipped | Failed |
| --- | ---: | ---: | ---: |
| Development suite | 2,936 | 4 | 0 |
| Open-edition suite | 2,704 | 3 | 0 |
| Development native pool process | 3 | 0 | 0 |
| Open native pool process | 3 | 0 | 0 |
| Focused failure, ownership, compatibility and replay checks | 170 | 0 | 0 |

The ordinary suites skip the conditional unsupported-reuse-port case,
multicast loopback and opt-in native pool broadcast. Both separate pool
processes execute the broadcast. The development suite also skips the live
bypass receiver because `BYPASS_IFACE` is unset. Missing native libraries are
not accepted as skips. This storage correction makes no NIC latency claim.

The open compile check covers 145 public sources without 67 private sources
and verifies six private-feature boundaries. All nine selected conformance
guards pass with zero skips: ABI reference and exports, open-edition projects,
public exports, parity, inventory and coverage, telemetry and documented build
facts. The runner reports **PARTIAL 9 of 362**, not a full conformance sweep.
Scans of the two changed public stores and the new fixture find no private
details; `git diff --check` passes.

## Publication integration

Rebasing onto `d70e7922f` leaves the two stores and their failure fixture
unchanged, but incorporates new managed/native recovery-receipt behavior.
The integration run at `8d02a372c` rebuilds the native library and passes all
**236 selected cases with zero failures or skips**. These include the 170
store/ownership/compatibility/replay checks above plus recovery-receipt and
session-Reject parity, native validation counters and TestRequest checks.
Its source and driver manifests verify unchanged after the run; the public
scan has zero findings. Inputs, TRX counters and logs are retained in
`evidence/dotnet-quickfix-write-fault-20261012/retained/integration/`.

The final publication rebase onto `8688e129d` changes publisher commissioning
configuration and documentation only. All captured C++, .NET, toolchain,
tool and integration-driver inputs remain byte-identical; the changed
`publish/PUBLISHING.md` is excluded from that build-input comparison.

## Reproduction

Run `evidence/dotnet-quickfix-write-fault-20261012/run-focused.sh` with explicit
`LIBHFT_REVIEW_REPO`, `LIBHFT_VERIFY_EVIDENCE`, `LIBHFT_VERIFY_BUILD_ROOT` and
`LIBHFT_VERIFY_TMPDIR`. Outputs must be on `/home`, outside the checkout.
The retained SDK helper requires `LIBHFT_DOTNET_SDK_DIR` and
`LIBHFT_DOTNET_HOST`; the verification uses GCC 14 for the native library.
Wrap the command in `host-cpu-slot`; the full development/open qualification
driver `run-qualification.sh` requires an `isol-no-shared` quiet-host lease.
