# C++ and .NET finalization preserve borrowed session IDs

C++ and .NET could acknowledge finalization without retiring the supplied ID
when its 16-byte input borrowed storage from the owner's returned finalized-ID
view. Insertion changed the input before copying or encoding it. C++ growth
could also free it before reading it. Both flavours now snapshot a new input
into stack storage before mutating the ID set and before persistence. No lock,
additional heap allocation or application send/replay operation is introduced.

## Reproduced failures

The baseline is `3f9c9f7ed` with the new fixtures. Each seeds three or sixteen
IDs, takes a 16-byte input at byte offset one in the returned ID storage and
copies its original bytes into an independent expected value. This is a new ID,
not an existing aligned entry. The operation must retain that value, preserve
the old IDs and ordering, and persist that same value across independent reopen.
An aligned known-ID input provides duplicate controls at both occupancies.

C++ has **26 passing cases, six failing cases and zero skips**. New borrowed
inputs fail for the public `FinalizedIds` helper, `FinalizedSessionJournal` and
`FileReplayStore`, both with insertion inside spare capacity and with growth.
All six known-ID controls pass, as do the twenty earlier ownership, copy-failure,
directory-sync and recovery cases. With spare capacity, insertion moves the
input bytes before `Remember` copies them. With growth, `realloc` can invalidate
the input pointer. The separate sanitizer baseline confirms a 16-byte
heap-use-after-free inside the direct `FinalizedIds::Remember` fixture after
`Grow` frees its old 256-byte allocation.

.NET has **nine passing cases, three failing cases and zero skips**. Insertion
without growth fails in the internal ID set, internal journal and public
`FixpFileReplayStore`: each accepts the operation but the expected ID is missing.
The journal and public-store cases check both in-memory membership and the
independently reopened metadata before asserting. Growth and all six known-ID
controls pass: the managed span keeps the old array alive when growth replaces
it, but does not protect it from insertion into the same array.

These are caller-input ownership defects, reproduced without allocation failure,
concurrent mutation or a process crash. The return values and reopened records
are checked through the library API; no wire-level terminal exchange is claimed.
Java's returned snapshots are cloned, and Rust's safe API requires exclusive
mutable access for finalization while its ID view holds an immutable borrow.
Neither flavour's implementation needs this correction.

## Correction

C++ `FinalizedIds::Remember` copies a new ID before shifting or growing its set.
`FinalizedSessionJournal::Record` independently preserves the input before
`Remember`, using that preserved value for both memory and append. Both layers
need protection: fixing only the set would still leave the journal encoding an
invalidated caller pointer.

.NET applies the same two-layer correction with 16-byte `stackalloc` spans in
`FixpFinalizedIds.Remember` and `FixpFinalizedJournal.Record`. Existing invalid-ID
refusal and sticky-failure behavior are preserved. Known IDs return before
copying, so duplicate handling is unchanged. File and mmap stores share these
journal implementations. The metadata format, sync order and writer fences are
unchanged.

## Qualification

hp ran the checks under normal-priority shared CPU leases. Build outputs and
runtime scratch are under the session build root, not the checkout.

| Selection | Passed | Skipped |
| --- | ---: | ---: |
| C++ regression and controls, each of four compilers | 32 | 0 |
| C++ address and undefined-behavior sanitizers with leak checking | 32 | 0 |
| Registered C++ target with production optimization and LTO | 32 | 0 |
| New .NET regression on runtime 8.0.31 | 12 | 0 |
| New .NET regression on runtime 10.0.12 | 12 | 0 |
| .NET development storage and native-binding selection | 90 | 0 |
| .NET open storage and native-binding selection | 81 | 0 |
| Java development storage and native-binding selection | 116 | 0 |
| Java open storage and native-binding selection | 107 | 0 |

C++ passes with GCC 11.5.0, 14.2.1 and 15.2.1, and Clang 21.1.8. Existing
finalized-path, finalized-ID, file replay and classic file-store fixtures pass in
both editions. Development also passes the private mmap companions: sixteen
path cases and nine finalized-ID cases. Java JNI and .NET native libraries are
rebuilt separately for both editions; native-loading and edition-parity guards
are included, not skipped. Java's six-case exception/retry regression also
passes on Java 8, 11, 17, 21 and 25, and .NET's existing six-case exception/retry
regression passes on .NET 10. Counts overlap and are per execution, not a sum of
unique tests.

The source scan reports zero findings in the six changed engine/test files.
Qualification starts at the baseline pin with the exact implementation and
fixture patch in its logs. Input and driver hashes are checked at completion.
This is targeted functional qualification, not a latency benchmark or a complete
conformance sweep.

After rebasing onto `49b4543b6`, every input hash from the full qualification
still matches. At integrated pin `395bdab3e`, the 32 C++ cases and twelve new
.NET cases pass again with zero skips. `integrated-logs.tar.gz` retains this
clean-source rerun, including its pin, hashes and TRX report.

The final rebase onto `bcc647c29` changes only the Java migration guide among
the qualification inputs. Every other hash still matches; no qualified code,
fixture, build rule or publication manifest changes in that documentation update.

## Reproduction and retained evidence

[run-focused.sh](evidence/finalized-borrowed-id-20261014/run-focused.sh) takes a
session source root, an absolute output root and `baseline` or `fixed`. Baseline
expects the three non-growing C++ failures, passing known-ID controls and the
three .NET failures; its C++ growth outcome can depend on allocator relocation.
To reproduce the baseline, use its source pin, apply `source.patch` from the
baseline archive and install that archive's `borrowed-tests.cs` as
`engine/dotnet/tests/Libhft.Tests/FixpFinalizedBorrowedIdTests.cs` in the session
worktree. Its hash matches the published fixture.

[run-sanitized.sh](evidence/finalized-borrowed-id-20261014/run-sanitized.sh)
captures the failing sanitizer baseline or requires the corrected 32-case run.
[run-qualification.sh](evidence/finalized-borrowed-id-20261014/run-qualification.sh)
adds the compiler, storage, native-binding, edition and runtime selections.
Wrap the outermost invocation in `host-cpu-slot`; use a fresh output root per run.

Compressed baseline, sanitizer-baseline and qualification logs are tracked beside
these drivers, including TRX and Surefire reports, source patches and input hashes.
Copies are retained under
`/home/yann/archive/libhft/finalized-borrowed-id-20261014/`; build products are under
`/home/yann/build/codex-libhft-hp/libhft/finalized-borrowed-id-20261014/`.
