# Rust acceptor writer ownership through callback unwind

## Reproduced failure

The acceptor acquired a writer fence before recovering the session but had no
guard to release it if recovery or a cold telemetry callback panicked before
registration. Removal moved a session out of the live vector before checkpointing;
checkpoint panic then lost that session's lease and could keep a factory-retained
owning socket clone open. Removed sessions also left an active publication row
when checkpointing or the final snapshot panicked.

Baseline: `cdfc0e7bf12b180c6ad2b861b11f5789cc10e4c3`.
First qualified candidate: `69c62865602e6432c957d0befe8f66436501f5eb`.
Final qualified candidate: `e61adbe38f1bd7a6364b6984f58e080b91a54f7c`.
The identical nine-case fixture has SHA256
`dca0fc1fd7fb7f7297898f3a08785cc092a0fed5ccaa4b37d4abc9226361c92b`.
Both baseline editions pass two ownership controls and fail seven behavior cases.
An initial baseline wrapper stopped after the default run because `rg` is absent
on Scan; its actual default result was retained, and Python checked the result
before the remaining open run. This was an orchestration error, not a compiler
failure or an additional negative runtime result.

## Change and scope

A private guard owns each successfully acquired fence until the session enters
the live vector, and again after a session leaves that vector for teardown.
Unwind invalidates owning socket aliases and invokes `release_primary` once.
The owner does not retry `Session` shutdown or checkpoint during unwind; the
backend's release hook retains its own sync/unlock behavior. Cleanup hooks must
complete without a second panic, as required for cleanup during Rust unwinding.

Registration reserves vector capacity while guarded and commits accepted counts
and the next connection ID after insertion. A failed cold snapshot no longer
counts a session that was never registered. Removal retires its publication row
and counts the removal before untrusted teardown callbacks; an unavailable final
snapshot is cleared instead of presenting an earlier removed connection's row.

Ownership transfers to the registered session. An application delivery panic
therefore keeps that session's lease until explicit teardown. Guarding begins
after `try_become_primary` successfully returns Held or Promoted; this is not a
claim about arbitrary partial acquisition inside a panicking backend. The API,
store formats, validation policy and normal FIX sequence handling are unchanged.

## Qualification

Both qualified snapshots pass the identical nine new cases in both editions.
The wider ownership/cancellation/Logout/startup/shutdown focus passes 110 default
and 106 open tests, with zero failures or ignores. Complete Rust stages pass
**2,380 default / 2,188 open**, zero failures and zero skipped stages, with the
same one existing ignored builders doctest in each. Strict all-target Clippy
passes both editions under `-D warnings`.

The first snapshot was qualified before an upstream Rust wire-survey presence
optimization landed. The rebase changed only `wire.rs` among runtime inputs, so
the complete focus, Clippy and full stages were repeated on the final snapshot.
Before/after proofs match every one of 381 Rust and 632 shared path/SHA256 pairs.
The wrapper returns 2/PARTIAL because each invocation selects one of 362 monorepo
stages; this qualifies both Rust stages, not the entire monorepo suite.

Scan used exclusive `isol-no-shared` CPU leases, serial `-j2` builds, one test
thread and no dev/test debug information. Its guard requires 64 GiB available to
start and stops this job below 32 GiB or on foreign compiler/test processes.
Tests use Cargo/Rust 1.92.0; Clippy uses the retained 1.93.1 toolchain. Every
output uses the absolute session `LIBHFT_BUILD_ROOT`. BEELINK2 runs no heavy job
for this slice. Exact commands, stdout, stderr and reports are retained.

Cold checks pass: source API inventory, its 13 unit tests, coverage matrix,
fixture rustfmt, diff check, and export scan of both changed engine files with
zero findings. Legacy compact source formatting is preserved.

## Evidence and parity

Evidence is retained under `~/archive/libhft/rust-acceptor-primary-ownership-20261014`
on BEELINK2, HP and Scan: baseline and candidate results, actual full-stage
commands/reports, strict Clippy outputs, input hashes, source bundle and checksums.
The final Rust and shared source maps were compared again after rebasing.
All 381 Rust and 631 shared files match. The only shared difference is four
added lines selecting a .NET validation-mode test in `tools/run-conformance`;
removing those exact filter/fixture lines makes the tool byte-identical to the
tested tool. Rust stage definitions, commands and inputs are unchanged. This
scoped comparison is retained in `publication-tool-review.json`.

Static scores remain 82/82 applicable APIs, 27/27 protocol rows, 43 complete
subsystems, two partial and one not applicable. Validation alignment has its
separate owner. Live production endpoint/venue and FIXP certification remain
external partials; this slice adds no physical transport or certification proof.
