# Managed FIXP finalization metadata ownership

Three .NET FIXP finalization defects are corrected: metadata aliases could
release another live store's writer lock or append into its payload journal;
a relative metadata path followed later working-directory changes and lost
finalized IDs on restart; and a seekable device was accepted as an empty
finalization journal. The correction affects metadata opening and finalization,
not ordinary application save or replay.

## Confirmed failures

At production pin `4329bf9d1`, all four alias cases fail with no passes or
skips. A live classic `FileStore` is synced, then another FIXP store's
`.finalized` path is made a hard link or symbolic link to it. A separate
Python process proves that the writer lock was held before the operation and
is available afterwards. Snapshots use the victim's existing handle, so the
fixture does not itself open and close a second descriptor on that file.

The metadata read refuses the malformed journal but still releases the live
store's process-owned POSIX record lock when its descriptor closes. The
metadata append reports successful finalization, releases that lock and adds
one 28-byte `LHFF` record to the victim's payload file: **17 bytes become 45**.
The original four-case fixture is retained in the test commit preceding the
production correction; the baseline inputs and TRX counters are in
`evidence/dotnet-finalized-ownership-20261013/retained/baseline/`.

The relative-path reproduction opens a file store as `session.journal`,
changes directory, finalizes an ID and reopens the original absolute store
path. Before path anchoring, finalization succeeds but creates metadata only
in the new directory, and the original store no longer knows the finalized
ID. The source pin plus patch and the one expected failure are retained in
`retained/relative-baseline/` below the same evidence directory.

After those two corrections, the nine-case fixture still fails its device
case: `/dev/null` is seekable and was read as a valid empty history. The
eight passes and this remaining failure are retained in
`retained/device-baseline/`.

## Correction

`FixpFinalizedJournal` resolves its pathname once at construction. Metadata
descriptors reserve their path and file identity through the existing
`StoreWriters` registry before opening; a link to a registered live writer
is refused before any descriptor can release its lock. The reservation lasts
through descriptor close and is released even after read, parse or write
failure. Missing read-only metadata still creates no file.

Unix regular-file validation now checks the opened descriptor's file type,
not its seekability. It uses the runtime's normalized `SystemNative_FStat`
interface, avoiding platform-dependent libc `struct stat` layouts and adding
no libhft native dependency. The declared layout matches the official runtime
headers for [.NET 8](https://github.com/dotnet/runtime/blob/v8.0.0/src/native/libs/System.Native/pal_io.h),
[.NET 9](https://github.com/dotnet/runtime/blob/v9.0.0/src/native/libs/System.Native/pal_io.h)
and [.NET 10](https://github.com/dotnet/runtime/blob/v10.0.0/src/native/libs/System.Native/pal_io.h).
Every failed type check or append-position setup closes its descriptor.

The metadata record format, replay format and durability ordering are
unchanged. Uncertain finalization still refuses retry and treats session IDs
as finalized. The ownership monitor is used for cold metadata admission and
release; ordinary save and replay methods acquire no new lock. The registry
protects the managed stores it knows; it does not make arbitrary application
descriptor closes safe or permit pathname replacement during admission.

## Focused verification

All nine cases pass with zero failures or skips on Linux using .NET SDK
8.0.131. The source pin plus patch, toolchain, input hashes and TRX counters
are retained in `retained/fixed-focused/`. Cases cover both alias types for
reads and appends, reservation cleanup after success and parse failure,
missing metadata, working-directory changes and the non-regular device.

## Integrated qualification

At source pin `bd2767029`, both native libraries are rebuilt with GCC 14 and
the checks run with .NET SDK 8.0.131 under an `isol-no-shared` lease. The
source, SDK-helper and executed-driver manifests verify unchanged after the
run. Raw outputs are retained in `retained/qualification/` and the original
archive `/home/yann/archive/libhft/dotnet-finalized-ownership-20261013/`.

| Check | Passed | Skipped | Failed |
| --- | ---: | ---: | ---: |
| Development suite | 3,035 | 4 | 0 |
| Open-edition suite | 2,801 | 3 | 0 |
| Development native pool process | 3 | 0 | 0 |
| Open native pool process | 3 | 0 | 0 |
| Focused finalization and store ownership checks | 176 | 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; no missing native library
is accepted as a skip. This metadata qualification is on Linux.

The open compile check covers 145 public sources without 67 private sources
and verifies six private boundaries. All nine selected conformance guards
pass with zero skips: documented build facts, inventory and coverage, ABI
reference and exports, telemetry, parity, public exports and open projects.
The runner reports **PARTIAL 9 of 362**, not a full conformance sweep.
The changed source and fixture pass the public scan with zero findings.

## Runtime compatibility

The separately compiled consumer runs explicitly on **.NET 10.0.12** against
the qualified net8.0 library. It passes regular-file status inspection,
payload save, finalization after a working-directory change, recovery of the
payload and finalized ID, and refusal of `/dev/null` metadata without creating
a replay file. Its log prints the actual runtime version; source and assembly
hashes verify unchanged. Inputs and logs are retained in `retained/runtime10/`;
`run-runtime10.sh` reproduces this consumer check. It is not a full-suite run
on .NET 10.

## Publication integration

Rebasing onto `90b7609f3` incorporates classic FIX validation changes while
leaving this metadata source and its fixture unchanged. The integrated run
at `b13f6ecdb` rebuilds the managed test assembly, checks the native build,
and passes **219 selected cases with zero failures or skips**: the store and
finalization checks plus `ManagedSessionDivergenceReviewTests` and
`PossDupOrigSendingTimeTests`. Its source and driver hashes verify unchanged
after the run, and the changed public files scan clean. Raw inputs and TRX
counters are retained in `retained/integration/`.

## Reproduction

Use `evidence/dotnet-finalized-ownership-20261013/run-focused.sh` with explicit
`LIBHFT_REVIEW_REPO`, `LIBHFT_VERIFY_EVIDENCE`, `LIBHFT_VERIFY_BUILD_ROOT` and
`LIBHFT_VERIFY_TMPDIR`, with outputs on `/home` outside the checkout. The SDK
helper uses `LIBHFT_DOTNET_SDK_DIR` and `LIBHFT_DOTNET_HOST`; GCC 14 builds the
native library. The original four red cases require their original fixture
and production source plus `LIBHFT_EXPECT_BASELINE=1`. A selected reproduction
may set `LIBHFT_TEST_FILTER` and `LIBHFT_EXPECT_FAILED` explicitly.

Every build/test command must hold a `host-cpu-slot` lease. The broader
`run-qualification.sh` runs both native builds, the development/open suites,
pool-mode checks and the selected publication guards under an
`isol-no-shared` quiet-host lease.
