# Native .NET refuses admission after file writer failure

A native .NET session could report successful primary acquisition or begin
opening a socket after its file store had suffered a sticky writer failure.
The store retained its descriptor and writer lock, but could no longer save an
outbound frame. `HftNetStore::IsDurable` checked only `IsOpen`, treating ownership
as readiness. The corrected check also requires that the file flusher or
QuickFIX writer has not failed.

The correction keeps the existing writer fence until explicit recovery or
stop. It changes neither send/replay operations nor writer locking. Recovery
remains backend-specific: QuickFIX permits an explicit offline reset retry;
the asynchronous file store requires stop/reopen before resetting again.

## Reproduced admission failures

The failing source and fixtures are retained at `28b6cfb6a` on private branch
`s/codex-libhft-hp-file-admission-baseline-20261014`. Both TLS build
configurations reproduce **30 passes and 36 failures in 66 pro cases**, and
**15 passes and 18 failures in 33 open cases**, with zero skips.

Every backend is tested with a failed offline reset and a failed append:
the portable asynchronous file, QuickFIX C++ files and QuickFIX/J files.
The private fixture includes HA enabled and disabled; the public fixture
uses the same three formats without HA. A child process applies
`RLIMIT_FSIZE=0` and ignores `SIGXFSZ` while the actual reset/write runs,
then restores both before observation. The asynchronous writer is drained
before injection and synchronously checked afterward. No backend result or
production failure flag is mocked.

For each sticky fault, three admission checks fail on the baseline:

- `hftnet_session_try_acquire_primary` returns 1 and its readiness query returns
  true even though save and outbound preparation refuse the next frame.
- Acceptor `hftnet_session_start` returns success and opens a listener backed
  by that failed writer.
- Initiator start attempts a real TCP dial to a loopback listener before its
  Logon preparation finally reports an error. Returning an error after that
  dial is not the required refusal before opening a connection.

The other cases are recovery controls: explicit reset retry, explicit
stop/reopen/reset, and a successful reset without an injected fault. Those
30 pro and 15 open controls already pass on the baseline. An independent store
reopens each recovered file and verifies payload `CURRENT` at sequence 1 and
the durable next inbound/outbound counters at 2/2.

## Writer ownership and correction

The fixtures call the real exported C ABI against sessions from the included
native implementation. Each case uses a new physical store. A failed reset
leaves the runner counters at 8/9, and failed stores refuse both save and
outbound preparation. A second store proves that the original writer still
owns its fence. With HA enabled the contender reports standby; without HA its
open is refused. The canonical HA view continues to report the owning primary,
not a fictitious standby.

The corrected source at `9e2e61c28` adds `!WriteFailed()` for QuickFIX and
`!FlusherFailed()` for the portable file to the existing open-state predicate.
The cold open helpers refuse a retained adapter rather than silently closing
or repairing it. Primary acquisition consequently returns an error, and start
fails before bind/dial, without rewinding counters or releasing the fence.
The legacy boolean readiness query returns false; the canonical ownership
telemetry remains independent of it.

No new lock, allocation or synchronous file operation is added. File formats,
the shared FIX protocol and hot send/replay methods are unchanged. These are
functional recovery/admission checks, not latency measurements. Failed
destructive resets may partially change disk contents; recovery does not
promise transactional restoration of the previous epoch.

## Qualification

At corrected source `9e2e61c28`, all **66 pro and 33 open cases pass in each
TLS build configuration**, with zero skips. The same selection passes on GCC
11.5.0, 14.2.1 and 15.2.1, and Clang 21.1.8. GCC 14 address and undefined-behavior
sanitizers pass all four edition/TLS runs with leak checking enabled. The pro
companion includes the open cases; these execution counts are not disjoint.

The earlier native Logon-epoch fixture passes 66 pro and 24 open cases per TLS
configuration, and the mmap reset-failure fixture passes 12 per configuration.
Native durable-teardown, memory-store and QuickFIX-store controls pass in both
TLS builds. Their expected connection-refusal and port-contention diagnostics
remain negative controls, not newly introduced failures.

The normal optimized production library passes 37 HA, native-loading and
edition cases on .NET 8.0.31 and 10.0.12. The broader development selection
passes **351/351**, and the separately built open-edition selection passes
**316/316**, both with zero skips. They cover native/managed wire parity,
store interoperability, Logon, QuickFIX reset and native HA. These selections
overlap; the counts should not be added as unique tests.

The open boundary compiles 145 open sources without 67 private sources and
checks six named refusal boundaries. The publication scan reports zero findings
in the three changed engine/test files. Source and driver hashes still match at
the end of qualification. These checks used the isolated CPU pool alongside a
cooperating shared-pool conformance run; none are latency measurements.

After fetch/rebase, every qualified source input is unchanged. At integrated
pin `c34935b9c`, fresh native builds pass 66 pro and 33 open cases in both TLS
configurations. The binding reruns pass 351/351 development and 316/316 open,
with zero skips. They reuse the unchanged qualified production libraries and
managed assemblies in their normal build locations, with source and library
hashes checked before and afterward. `integrated-logs.tar.gz` retains that pin
and the final case/TRX outcomes.

## Reproduction

[run-focused.sh](evidence/native-file-writer-admission-20261014/run-focused.sh)
takes the session source root, a fresh absolute output root and `baseline` or
`fixed`. Baseline requires the exact failures above; fixed requires every case
to pass with zero skips. It builds through the registered native-test Makefile
in both editions and TLS configurations.

[run-qualification.sh](evidence/native-file-writer-admission-20261014/run-qualification.sh)
adds four compilers, sanitizers, earlier Logon/offline reset controls, native
store fixtures, normal optimized production libraries, real .NET binding/HA
selections, open-edition boundaries and the publication scan.
[run-integrated.sh](evidence/native-file-writer-admission-20261014/run-integrated.sh)
reruns the native and binding selections after integration, requiring the
qualified source and library hashes to remain unchanged. Every outer run
uses `host-cpu-slot`; builds and per-run state stay under the session build root.

Compressed logs beside the drivers retain source pins, input/driver hashes,
build commands, exact case outcomes and test reports. These are functional
review artifacts, not benchmark captures or published benchmark results.
Copies are retained under
`/home/yann/archive/libhft/native-file-writer-admission-20261014/`, and build
outputs remain under
`/home/yann/build/codex-libhft-hp/libhft/native-file-writer-admission-20261014/`.
