# FIXP finalization failures remain failed until reopen

Java and .NET could acknowledge a durable finalization on retry after an allocation
exception had left its ID only in memory. C++ could acknowledge a record after its
journal failed to open. The corrections keep uncertainty failed until reopen and
preserve ordinary duplicate handling. No lock or operation is added to the
application send or replay path.

## Confirmed failures

The baseline at `4109c0279` uses deterministic injected failures, not real heap
exhaustion. All fixtures check the return value, the metadata file and independent
reopen state; healthy duplicate and reopen cases provide controls.

Java stages an ID before adding its recovery snapshot and appending the record.
An injected `OutOfMemoryError` in the snapshot list escaped without setting the
journal's failure flag. Both the direct journal and the public file replay store
then returned true on retry, despite no metadata file and no persisted ID. The
six-case fixture has **four passes, two failures and zero skips**. The existing
set-allocation failure already failed closed and remains a passing control.

.NET stages the ID before record encoding and append. Its existing file-identity
fault hook injects `OutOfMemoryException` or `ThreadInterruptedException` before
the metadata append. Both direct-journal and public-store retries falsely returned
true, again with no metadata file or persisted ID. The six-case fixture has
**two passes, four failures and zero skips**. The interruption is injected at the
hook, not delivered as an operating-system interruption.

C++ loads a valid journal, then fails the allocation copying its recovered ID
set. `Open` returns false, but the set's fail-closed `Contains` answers true for
every ID. `Record` checked that answer before checking whether the journal was
open, and returned true for both the recorded and unrelated IDs. The ownership
fixture has **eight passing cases, one failed case and zero skips**; the new
helper reports two failed assertions. This is a direct journal-helper defect:
the normal C++ file replay store already guards against a failed store open.

## Corrections

Java and .NET set their journal failure flag before staging a new ID and clear
it only after the append and sync operations succeed. Thus any later escaping
exception leaves the handle failed. Allocation exceptions return false; .NET
still propagates its unexpected interruption exception. A failed handle rejects
subsequent records and treats every ID as retired, following the existing
fail-closed contract.

C++ checks the open state before consulting ID membership. A record on an
unopened or failed-open journal returns false and poisons the handle. A successful
reopen restores normal known-ID duplicates and new durable records.

The record format, file ownership guards, replay-file sync ordering and directory
sync operations are unchanged. These are cold finalization-state corrections,
not latency optimizations.

## Qualification

hp ran the checks under normal shared CPU leases, with outputs under this
session's build root. The focused fixed pin is `1b8fb4fae`; broader C++ and Java
qualification uses `ab9191e6a`, and the completed .NET qualification uses
`c38b73774`. The latter two pins have byte-identical C++, Java, .NET, tool and
publication inputs; only the private verification driver changed.

| Selection | Passed | Skipped |
| --- | ---: | ---: |
| New C++ regression and ownership controls, each compiler | 9 | 0 |
| Java development finalization, stores and native bindings | 116 | 0 |
| Java open finalization, stores and native bindings | 107 | 0 |
| New Java regression, each JVM | 6 | 0 |
| .NET development finalization, stores and native bindings | 78 | 0 |
| .NET open finalization, stores and native bindings | 69 | 0 |
| New .NET regression on runtime 10.0.12 | 6 | 0 |

The C++ regression runs with GCC 11.5.0, 14.2.1 and 15.2.1, and Clang 21.1.8.
GCC 14 also passes existing finalized-path, finalized-ID, file replay and classic
file-store fixtures in development and open builds; development adds the private
mmap companions. The Java regression runs on Java 8, 11, 17, 21 and 25 using
Java 8-compatible classes. .NET development and open selections run on 8.0.31.
Counts are per selection or execution and overlap; Maven repeats its selection
in its configured forks.

Java JNI and .NET native libraries are rebuilt separately for development and
open editions, with native-loading guards and edition-parity assertions included.
The Java and .NET open-boundary checks pass. The source scrub reports zero
findings in the seven changed engine and test files.

The first qualification command stopped after the successful 78-test .NET run
because its console-summary matcher expected a different verbosity format. The
corrected driver reads the TRX counters; the .NET development, open and runtime
checks then complete. The initial raw output is retained, not counted as a
completed all-stage run. This is a focused storage qualification, not a full
conformance sweep. Rust sources are unchanged in this batch.

Integration onto `223474131` leaves every hashed qualification input unchanged.
The post-rebase focused rerun at `fd2c8f8d4` passes nine C++ cases, six Java
cases per configured Maven execution and six .NET cases, with zero skips.
Its raw logs are retained as `integrated-focused-logs.tar.gz` beside the other
evidence. Documentation and evidence commits after that pin change no tested
implementation or fixture bytes.

## Reproduction and retained evidence

Run `evidence/finalization-poison-20261014/run-focused.sh` with a session source
root, an out-of-tree output root and `baseline` or `fixed`. The baseline expects
the three reproduced red results; the fixed selection must pass. Native loading
is explicitly disabled for this pure/helper-only regression selection.

Run `run-qualification.sh` with source and output roots inside a shared
`host-cpu-slot` lease. Its default mode checks all selections above; a third
argument `dotnet` selects only the .NET qualification. Native loading is required
in qualification. No build outputs belong in the source tree.

Compressed baseline, fixed-focused, initial qualification and completed .NET
logs are tracked beside the drivers, including Surefire XML, TRX files, source
pins and input hashes. Copies are retained under
`/home/yann/archive/libhft/finalization-poison-20261014/`. The build outputs are
under `/home/yann/build/codex-libhft-hp/libhft/finalization-poison-20261014/`;
they are not the only evidence copy. The remote baseline checkpoint preserves
the original pre-fix fixture pin.
