# Managed QuickFIX store writer ownership

The managed .NET QuickFIX C++ and QuickFIX/J stores admitted simultaneous
writers on Linux and ignored the agreed incumbent and libhft side-file fences.
A contender could therefore repair an incumbent's index or reset files that
the first writer still used. The correction acquires ownership before opening
or repairing any data file and retains it until store disposal. Normal send,
append and replay methods acquire no new lock.

## Confirmed baseline

The baseline at `ceab90d10` fails all ten new tests, with no passes or skips:
two same-process writer cases and eight incumbent-fence cases. The fence
matrix covers both store layouts, both agreed side-file names, and both Linux
`flock` and POSIX record locks held by a separate process. The original fixture
is retained at `6764c0579` and in the baseline input manifest.

Each incumbent case seeds a valid record and then a proven torn header tail.
The contender must throw `StoreLockedException` before repair. When the holder
releases its fence, a new owner may repair the tail and retrieve the original
payload. The failure is actual writer admission, not an inferred CPU or NIC
latency problem.

## Correction

`QuickFixStoreOwnership` first takes `<prefix>quickfix.writer.lock`, then
`<prefix>libhft.lock`, then registers and opens the body. The existing
`StoreWriters` registry prevents a same-process alias from opening another
descriptor onto a live managed file. On Linux, the side files hold both
`flock` and record locks: C++/Rust use the former, and Java's FileChannel uses
the latter; they are separate lock mechanisms. Body byte zero also participates
in the existing managed-store record-lock protocol.

Every failed acquisition or constructor recovery releases already-acquired
resources. Explicit `flock` release precedes descriptor close, including
cleanup, so a forked descriptor cannot retain the lifetime fence after the
parent closes. Disposal is single-winner and idempotent; an old store's second
Dispose must not unregister a replacement writer.

These are advisory, cooperative fences. An unwrapped external engine that
does not take the agreed locks still requires operational fencing. The file
layouts, message bytes, sequence limits and replay APIs are unchanged. The
Linux interop matrix does not qualify macOS record-lock interoperability:
the existing FileStream.Lock macOS limitation remains explicit in the helper.

## Evidence and reproduction

Raw inputs, source pins, source patches, toolchain and logs are retained under
`/home/yann/archive/libhft/dotnet-quickfix-writer-20261012/`.
`baseline/` retains the expected ten failures. The retained
`evidence/dotnet-quickfix-writer-20261012/run-focused.sh` requires an explicit
`LIBHFT_EXPECT_BASELINE=1` for that original fixture and production source;
ordinary execution requires zero failures and zero skips.

Additional controls cover a symlinked directory, a hard-linked body under a
different prefix, conflicting layout selection, failed recovery cleanup,
repeated disposal and independent-process lock availability before and after
close. Runtime filesystem acquisition is confined to opening and disposal.

## Focused correction verification

The corrected fixture at `0a029af1e` passes all 22 cases, with zero failures
and zero skips, using .NET SDK 8.0.131 on Linux. `fixed-focused/` retains the
source and driver manifests and the actual test log. The ten original red
cases and twelve additional ownership controls all execute. The rebase onto
`9fd67df1d` changes none of the two stores, the ownership helper or this fixture.

The first broad qualification acquired its lease before its dequeue request
was processed; its manifests preceded that rebase. It was stopped during the
native build and is explicitly marked `qualification/INTERRUPTED.txt`, not
used as passing evidence. The restarted qualification uses a fresh build
root with evidence under `qualification-r2/`.

## Integrated qualification

The integrated source pin is `a222cf633`. The default and open native libraries
were freshly built with GCC 14, and the managed checks used .NET SDK 8.0.131.
Both driver and source manifests verify unchanged after the run.

| Check | Passed | Skipped | Failed |
| --- | ---: | ---: | ---: |
| Development suite | 2,807 | 4 | 0 |
| Open-edition suite | 2,575 | 3 | 0 |
| Development native pool process | 3 | 0 | 0 |
| Open native pool process | 3 | 0 | 0 |
| Focused writer, compatibility, alias and replay checks | 138 | 0 | 0 |

The ordinary suites' skips name the conditional unsupported-reuse-port case,
multicast loopback and the opt-in native pool broadcast. The development
suite also skips the live bypass receiver because `BYPASS_IFACE` is unset.
Both separate pool processes execute the broadcast case. Native availability
is required by the moved build; no missing native library is accepted as a
skip. This batch does not qualify NIC latency or the opt-in bypass receiver.

The open-edition compile check includes 145 public sources, excludes 67 private
sources and checks six private boundaries. All nine selected conformance guards
pass with zero skips: ABI reference and exports, open-edition projects, public
export self-tests, parity, cross-binding inventory and coverage, telemetry and
documented build facts. The runner correctly reports **PARTIAL (9 of 362)**;
this is not a complete conformance sweep. Public scans of the four changed
source/test files find no private details, and `git diff --check` passes.

Reproduce through `run-qualification.sh` with explicit worktree, retained
evidence, build-root and temporary-directory environment variables, under a
`host-cpu-slot` `isol-no-shared` lease. It delegates the existing broad .NET
driver and adds the writer-specific source manifests, focused tests and scan.

The publication rebase onto `90cfcb8e4` adds only unrelated benchmark evidence
and documentation. The complete retained source and driver manifests verify
byte-for-byte unchanged after that rebase; no second broad sweep is substituted
for the source-pinned result above.
