# .NET store recovery and writer lock verification

Recovery, alias identity errors and rejected FIXP/classic cross-provider opens
now pass their independent reproductions, with freshly rebuilt native
libraries. Both full .NET editions pass after the owner's store and TLS-fixture
fixes. A further public-API probe confirms a concurrent FIXP last-close/new-open
race that still drops the new primary's writer lock. That race is requested
from the source owner; the all-flavour review is not closed.

## Sources and checks

Source checkpoint is `d184db2704f6b2b38eed6ba9a8c484b52982f3f2`. The corrections
landed as `583d4e81e` (position before tail repair, without querying Length after
the cut) and `c844d9799` (writer ownership by file identity, shared across store
kinds). The retained baseline reproductions and their source are described in
[the journal reset review](RECORD_2026-10-04_hp-journal-reset-io.md).

All builds and tests ran under shared host-cpu-slot leases, with temporary
fixtures and outputs under this session's build root on /home. SDK 10.0.112
built net8.0; runtime 8.0.31 executed the tests. The native producer used the
normal Makefile, GCC 14 and stock optimized kernels. The open build used
`LIBHFT_OPEN=1` and its normal `native-open` output, explicitly selected through
`LIBHFTNET_LIBRARY`; no library was moved ad hoc. Native availability was
required in both suites.

| Check | Development edition | Open edition |
| --- | --- | --- |
| Selected official store and native availability tests | 149 passed | 113 passed |
| Independent recovery cases and healthy controls | 6 passed | 6 passed |
| Independent same path and symbolic or hard link lock cases | 3 passed | 3 passed |

Official suites report zero failures and zero skips. The two existing CS1690
warnings in ManagedTlsWriteCompletionTests appeared during each test-project
build; these are not zero-warning builds. The standalone probes build without
warnings. Source hashes checked unchanged after both editions finished.

## Independent recovery probe

Four damaged inputs recover their valid prefix: payload 14 to 13 bytes,
legacy checkpoint 29 to 28, incomplete header 1 to 0 and incomplete payload
13 to 0. A Linux x86-64 interposer arms a one-shot fstat64 EIO only after a
successful ftruncate64 on the fixture. On the old implementation that rejected
the constructor after changing the file. On the corrected implementation all
four constructors return, with no post-cut fstat64 call consuming the fault.
Healthy payload and empty controls also open normally.

The probe disables injection immediately after construction, before checking
the bytes or appending, so its own inspection cannot consume the armed fault
and be misattributed to recovery. It verifies the retained payload and recovered
outbound/inbound counters, appends through the public API, updates the session
checkpoint, synchronizes, closes and reopens. All payloads and new counters
survive. No reflection or production test hook is used.

## Independent writer lock probe

A public FileStore constructor refuses the same path and both symbolic-link
and hard-link aliases. A separate C process asks F_SETLK for the writer byte
range before the attempted duplicate and after its refusal. Every attempt is
busy while the original store remains open, unlike the old implementation's
alias close that admitted the foreign writer. The official development suite
also includes mmap cross-provider refusal and FIXP aliased-standby lifetime
tests; those are official suite results, not the independent FileStore probe.

## Retired managed staging handoff

Read-only objdump inspection finds ABI 68 in the frozen managed bundle,
ABI 61 in the installed hftnet library, and ABI 69 in the current source
contract. The unchanged frozen manifest was insufficient to prove currency.
The home helper `~/stage-libhft-managed.sh` now fails before queueing, sudo or
installation, with an explicit obsolete-bundle message. Its previous bytes
are archived and match the old manifest hash
`78b45a03a76bc00c8c3413a0bdfed31eb6d823eb22863d25d11702748d34413b`.
No installed library, runtime or running NIC sitting was changed. A newly
matched Java/.NET bundle, fresh checks and a quiet staging window remain
necessary before current-generation managed NIC tests.
The retired helper was executed under the follow-up lease and returned 2
before any sudo/install action; its message and successful shell syntax checks
are retained. The previous helper is also tracked beside this record.

## Reproduced file identity lookup error

At the original checkpoints, a public-constructor probe supplies a statx wrapper through the public
NativeLibrary.SetDllImportResolver API, targeting only the alias pathname.
It returns EIO for that lookup and delegates other lookups to libc. The library
silently treats the failed lookup as unavailable identity, opens a second
descriptor and obtains its inode key, but does not re-check whether that key
already belongs to a live writer. The duplicate is admitted.

Both symbolic and hard links reproduce in both editions. The independent
F_SETLK contender is busy before and during the duplicate open, then acquires
the writer byte range after the duplicate closes while the original remains
live. Every case logs the injected statx EIO and exits 1 under the expected
refusal/fence-preservation assertion. The no-fault controls above stay green.
This is an injected metadata error, not evidence of a physical device fault.
No lock or error-handling source is changed here because it remains claimed
by the .NET owner; the reproduced defect is requested on the claims board.

## Reproduced cross provider lock loss

Against the integrated `81a607d3b` checkpoint, the public FIXP mmap Open of a
live classic FileStore journal returns false because the file is not a FIXP
image. It nevertheless opens and closes a second descriptor, independently
of StoreWriters' exclusive registry. An external F_SETLK contender is busy
before that attempt and acquires the byte-range lock afterwards, while the
original FileStore remains live. Both the same-path and symbolic-link cases
reproduce, without injected errors, reflection or production test hooks.

The fixture first persists one classic payload and its 2/73 checkpoint through
public APIs. No probe read opens and closes the journal in the owner process
while the lock is judged; the only contested descriptor is the FIXP provider's.
These two cases are distinct from the official same-provider FIXP alias tests
and FileStore/MmapStore refusal tests, which remain green. Unifying the cold
descriptor admission across FIXP and classic providers is requested from the
current .NET owner. No source is changed here.

## Full suite attempt

The first full development run reports 2,151 passed, two failed and three
skipped. Both failures are Unix-domain telemetry tests: this probe's deeply
nested TMPDIR makes their socket paths exceed the platform's 108-character
limit. The selected store tests did not need Unix sockets and were unaffected.
The follow-up uses the shorter session build-root temp directory, still on
/home. This is an environment correction, not a production code fix.

The skipped cases are multicast loopback, the live bypass NIC receiver and a
native-pool broadcast test that needs multiple acceptor workers. No native
library availability test skipped. The first full open suite did not execute
after the full development run returned nonzero. Neither early result is
presented as a full-suite pass.

The integrated follow-up rebuilds both native libraries and managed projects
at `81a607d3b`, with unchanged input hashes through the end of testing. Selected
store suites and all earlier independent controls repeat with the same green
counts. Its full development suite reports 2,160 passed, zero failed and three
skipped. The full open suite reports 1,939 passed, one failed and two skipped
(1,942 total), so that suite is not green. The Unix-socket failures are gone.

The open failure is
`Mtls_rejects_an_untrusted_client_even_when_server_verify_mode_is_none`:
its never-logged-on assertion succeeds, but the acceptor failure counter is
still zero at the assertion. The test pumps for a fixed 1,500 ms; it does not
wait for an observed rejection. The same case passes five targeted fresh
processes in each edition, with zero skips. A too-short observation window
under parallel-suite load is a hypothesis, not a proven security defect.
Nothing here demonstrates that an untrusted client was accepted. The fixture
still needs a bounded wait for the expected rejection result, and is requested
from its owner rather than modified across the active source claim.

The native-pool broadcast case passes separately in both editions with
`LIBHFT_ACCEPTOR_THREADS=2`, one test and zero skips each. Multicast loopback
and the unarmed live-bypass case remain environmental coverage gaps. No absent
native provider is used to excuse a skip.

## Evidence and remaining scope

`~/archive/libhft/dotnet-store-verification-20261004/` retains the source pin
and full native/.NET input manifest, compiler/runtime information, native
hashes, build logs, official TRX reports, independent probe sources and logs,
the recovery interposer and the previous home helper. `followup-81a607d3b/`
contains the integrated builds, all six red lock probes (four injected alias
errors and two ordinary cross-provider cases), both complete full-suite logs
and TRX files, ten targeted TLS repeats, both two-worker pool results and the
retired-helper check. Small portable drivers
are retained under `evidence/dotnet-store-verification-20261004/` beside this
record. They use only newly created fixtures, never live stores.

The owner subsequently fixed the identity-error and cross-provider defects
as `1baae2bd8`, and the rejection-observation fixture as `1067a7410`.
The independent recheck and the remaining close/open race are detailed below.
Real device faults, power loss, Windows fault injection, current-generation
managed NIC tests and the complete .NET/conformance matrix were not run here.
No latency improvement is claimed.

## Independent fix recheck

The owner's fixes are integrated at
`e26e6ad28074b92799233965b45f6fd571377a13`, also retained in
`closure-e26e6ad28/source-pin.txt`.
Fresh native libraries and managed projects use separate output roots for each
edition, normal Makefile producers and the short session TMPDIR. The same
public-API probes run without production test hooks or reflection.

| Check | Development edition | Open edition |
| --- | --- | --- |
| Selected official store and native availability tests | 155 passed | 116 passed |
| Recovery inputs including healthy controls | 6 passed | 6 passed |
| Same-path and no-fault alias lock controls | 3 passed | 3 passed |
| Injected alias identity errors | 2 passed | 2 passed |
| Rejected classic journal open by FIXP mmap | 2 passed | Not in the open edition |
| Full test suite | 2,175 passed, 3 skipped | 1,952 passed, 2 skipped |

Every listed executed test has zero failures; selected suites and standalone
probes have zero skips. All six lock cases that were red at `81a607d3b` now
refuse the duplicate and keep the external F_SETLK contender busy. The four
identity-error cases retain their injected-EIO markers. The recovery probes
still append, synchronize and reopen with the expected payloads and counters.
The full open suite's previously failing mTLS case passes in the full run.
Native availability is required and does not skip. The existing CS1690 fixture
warnings remain; this is not a zero-warning official test-project build.

The full-suite skips still concern multicast loopback, an unarmed live bypass
receiver in development, and the native-pool broadcast needing two workers.
The two-worker broadcast passes separately once in each edition with zero
skips. The formerly failing mTLS case also passes five fresh targeted
processes in each edition, with zero skips. Those reports are retained in the
same evidence directory. Source input hashes check unchanged through
the full run. The obsolete home staging helper still refuses safely; nothing
was installed into the root-owned bench runtime.

## Reproduced FIXP last close and new open race

Three new cases against the same checkpoint reproduce another defect in the
development edition's FIXP mmap store. `ReleaseFile` removes the last entry
from `OpenFiles` under `PrimaryLockSync`, then leaves that gate before disposing
the descriptor. A new store can open another descriptor and take the primary
lock in between; the older descriptor's close then releases that process-owned
POSIX lock. `HoldsPrimaryLock` on the new store remains true.

The Linux scheduling interposer delays exactly one real close of the fixture's
device/inode; it injects no I/O error and eventually returns the real syscall's
result. An older mapped store on one thread closes while a different store
instance opens and acquires its primary role. The external C contender is busy
before the delayed close finishes, then acquires the writer byte range after
it finishes. Same-path, symbolic-link and hard-link cases all fail the fence
assertion with probe exit 1. Three corresponding sequential close/open controls
pass with exit 0. No journal inspection opens and closes a contested descriptor
in the owner process.

The probe is also valid for a corrected admission path that waits or refuses
while the old descriptor is closing: the opening work runs on a separate
thread, the barrier is always released, and a refused open is retried only
after close completion. All waits are bounded and a barrier timeout is a probe
error, not a lock-regression result. This is an injected schedule, not a claim
about the rate of occurrence in production.

At that checkpoint the production source was under the .NET owner's claim.
The request was to keep descriptor retirement serialized with new shared
admission, without a per-frame lock. `run-close-race.sh` and its public-API project are tracked beside
this record. `closure-e26e6ad28/` retains all six logs, compiler output, helper
hashes, full-suite TRX reports and the earlier green independent controls.

## Independent final close race verification

The owner's `10fdadcc7` closes the last shared descriptor and releases its
common StoreWriters reservation while still holding PrimaryLockSync. New
shared admission therefore cannot acquire its descriptor and primary lock
before that older close completes. This is cold lifecycle serialization,
not a per-record lock.

A fresh independent run at `0429e656f` passes all six original public-API
probes with the default expected exit 0. Same-path, symbolic-link and hard-link
sequential controls all keep the external C F_SETLK contender busy. In all
three concurrent cases, the actual close barrier is entered and released,
the new open does not complete while the old close is paused, and the new
primary retains the real kernel fence after that close. The contender returns
75 (busy), rather than acquiring the lock while HoldsPrimaryLock remains true.
No I/O error is injected.

`~/archive/libhft/dotnet-store-verification-20261004/close-closure/` retains
the source pin, input manifest, all six logs, helper hashes and normal
ProjectReference build output (zero warnings/errors). Input hashes match
after the run. These standalone probes do not use JNI/hftnet or rerun the
earlier 2175/1952-case official suites against this newer tree. The reproduced
last-close race is closed; the earlier full-suite results remain qualified
only at their recorded checkpoint.
