# Durable FIXP store pathname review — 2026-10-09

Review session: `codex-review-paths-1009`, isolated worktree on BEELINK2. Private
repository: `atrekes/libhft-devel`. Initial base: `745f7cb83`.

## Reproduced defects and fixes

C++ and Rust retained a relative pathname for the finalized-session journal and
opened it again when an ID was retired. The replay file already had a live file
descriptor. After the process changed its working directory, finalization could
succeed in a different directory, so reopening the original replay file forgot
that ID. The mmap stores also kept relative paths between split open and recovery,
and across release/reacquisition of the primary lock. They could combine one
file's replay records with another file's retirement history.

Both languages now capture an absolute store pathname at admission and use that
same spelling for replay and metadata. Standalone finalization journals capture
it too. POSIX resolution prefixes the current directory without folding `..`,
so a path traversing a symlink retains its filesystem meaning. C++ checks the
expanded pathname against its fixed buffer limit before creating the replay
file. Windows uses `_fullpath`; only the Linux cases were executed here.

Rust's file replay store additionally converted a native `Path` through
`to_string_lossy()` for the payload journal while using the original native bytes
for retirement metadata. Different non-UTF8 filenames could share a replay file
and writer fence, or an existing replacement-character name could be recovered
and changed accidentally. The internal journal open methods now accept
`AsRef<Path>`; configured native bytes reach all filesystem operations unchanged.
Lossy text remains appropriate only in diagnostics. Public classic store string
APIs and on-disk formats are unchanged.

Java's mmap store returned boolean/status failures for I/O errors, but a NUL in
the configured path escaped as `InvalidPathException` before file admission. The
open boundary now catches `IllegalArgumentException` alongside `IOException`,
matching the file provider. It reports failure, releases the preceding handle as
its existing reopen contract specifies, and accepts a healthy retry. The five
entry points tested are direct default/sized open, split open, configured non-HA
open and configured HA open.

## Verification

Three language agents reviewed and implemented disjoint claimed paths, with
cross-review of each other's changes. Root ran every heavy job under the host's
shared CPU lease, with at most 16 build jobs. Each job checked active compilers
and available memory. Outputs were placed under the session build root. Java
fixtures use `TestTempFiles`; Rust CWD cases run in isolated test subprocesses
and restore their directory before removing fixtures. C++ test executables run
separately and clean their fixture trees.

Identical new regression tests were run against the original production sources
and the fixes. No source behavior was stubbed to manufacture the failure:

| Gate | Original production | Fixed production |
| --- | --- | --- |
| C++ open path cases | 7 failures, 7 controls pass | 14 pass |
| C++ private path cases | 8 failures, 8 controls pass | 16 pass |
| C++ ASan + UBSan | — | All 30 pass |
| Rust path cases | 10 failures, 3 controls pass | 13 pass |
| Java path cases | 10 errors, 5 controls pass | 15 pass |

Java's focused runs additionally require `NativeLibraryRequiredTest`; its pass is
counted separately from the 15 path cases. The broad Java selection covers mmap,
retirement and FIXP runners. Test reports and skip lists were inspected, not just
process exits. The first Java run used Maven's default JNI LTO flags because the
wrapper had omitted its override. It was retained as preliminary evidence and
repeated with explicit `-flto=1` before publication.

| Integration gate | Final result |
| --- | --- |
| C++ open edition, layering and simulator | 152/152 test binaries pass, no reported skips; 85 private binaries excluded |
| Affected private C++ store/runner/selection binaries | 10/10 pass, no reported skips |
| Java development affected mmap/finalization/FIXP runner suites | 394 unique cases pass, no failures/errors/skips; native loading mandatory |
| Rust full default suite | 1,485 pass, one existing ignored documentation example |
| Rust full no-default-features suite | 1,485 pass, one existing ignored documentation example |
| Rust open-edition path integration | 7/7 pass, no ignored cases |
| Rust default all-target Clippy | Pass with warnings denied |
| Export-name scan | 14 changed engine files, zero findings |

The Java selection was exercised in the configured default, counters-disabled and
mode-3 forks; the 394 count deduplicates class/case names. This pass did not rerun
the entire Java suite or Java open-edition suite: the managed edit is private, and
the affected private/open runner tests ran together in development. It does not
claim a full .NET, venue or all-language conformance run.

## Scope and limits

The fixes add path work only on cold open/recovery paths, with no new per-message
branch, syscall or allocation. No throughput or latency percentile was measured.
There is no inode pinning across administrative directory renames, pathname
replacement or symlink retargeting; distinct hard-link/file-symlink spellings
continue to select companions by configured name. The C++ expanded absolute
pathname must fit its existing 4096-byte bound. Rust targets Linux.

Java has no supported ordinary CWD-changing API. Its analogous relative-path
shape was reviewed, but no reproducible Java directory-change defect was claimed
or patched. Java's code change is limited to malformed-path admission. Windows
source was reviewed, not executed. The reserved .NET borrowed-buffer proposal
remains on `s/codex-review-edges-1009-dotnet-proposal`; this pass changes no .NET
source and does not claim that pending integration is complete.

## Publication and retained evidence

Publication base is `745f7cb83`: fetch/rebase reported the branch already current,
with no incoming commits. All 14 engine files still match the tested SHA-256
manifest after that check. Only this session's three code commits are outgoing:

- `80004c560` — Java malformed FIXP mmap paths return admission errors.
- `293ad4309` — C++ retirement journals stay with the opened store path.
- `519a69c68` — Rust native paths remain exact across deferred opens.

The code commits were backed up to the private session branch during verification.
They and this record are published linearly to private main, then the main
checkout is fast-forwarded.

Evidence is retained at:

- `/home/yann/archive/libhft/reviews/2026-10-09-codex-review-paths-1009/`
- `hp:archive/libhft/reviews/2026-10-09-codex-review-paths-1009/`

The archive includes original/final source snapshots, counterfactual and final
logs, Java XML reports, C++ per-binary outputs, gate scripts, agent review notes,
source SHA-256 manifests, and a Git bundle. Manifest verification and worktree
cleanup are performed after publication.
