Files
ARCADIA-Studio/harness/release-linux-journal-publication.md
T

5.7 KiB

Linux journal publication: refuse replacement

Scope: the transition from a private staged journal directory to the pending .arcadia-save-ENTRY.journal name. This is an ownership boundary before model files can be replaced. It does not establish multi-file atomic visibility, cross-process compare-and-swap, or power-loss durability.

Evidence and decision

The actual Ubuntu 24.04.4 / kernel 6.8.0-139 / x86_64 / glibc 2.39 / ext4 probe in .local/linux-qa/rename-probe.py showed ordinary directory rename replacing an existing empty destination. Its output .local/linux-qa/runtime/linux-rename-probe.log records replacement=true, RENAME_NOREPLACE refusal errno 17, competing directory retained=true, and fresh publication=true. That probe predates this Rust implementation.

Linux now uses libc::syscall(SYS_renameat2, AT_FDCWD, stage, AT_FDCWD, journal, RENAME_NOREPLACE). The existing locked libc 0.2.189 package becomes a direct Linux-only core dependency; no new package/version is introduced. Its supplied architecture constants avoid handwritten syscall numbers. Calling syscall also avoids adding a glibc 2.28 renameat2 wrapper symbol requirement.

The Linux manual documents no-replacement refusal, kernel and filesystem support requirements, and EINVAL for unsupported flags. Kernel/ext4 support begins with Linux 3.15; this is a requirement on this save operation, not a claim that the whole application supports that old platform. Sources: rename(2), syscall(2).

Paths use original Unix OsStr bytes in live NUL-terminated buffers; embedded NUL is rejected before the syscall. Failure preserves the OS error and journal path. There is no fallback, retry, or substitute ordinary rename on Linux: unavailable/blocked syscalls and unsupported filesystems stop the save before the write-start marker or model writes. Windows retains MoveFileExW without replacement and its existing bounded scanner retries. Other operating systems explicitly retain their former standard-rename behavior; the stronger publication guarantee is scoped to Windows and Linux.

Verification boundary

Four Linux-only tests in source_files.rs, filter linux_journal_publication:

  • A competing empty directory, nonempty directory, file, or dangling symlink appearing after precheck keeps its inode and contents; our staged metadata remains intact and publication returns EEXIST at the journal path.
  • Fresh publication preserves non-UTF8 Unix path bytes. Either path containing NUL fails before moving the stage.
  • Two synchronized staged publishers have exactly one winner. The other keeps its complete stage and receives EEXIST.
  • A competitor injected immediately before full-save publication prevents ownership transfer, the write-start marker, and every model-file replacement; captured original bytes and dependency expectations remain unchanged.

An additional explicitly ignored Linux test requires syscall fault injection and OA_TEST_RENAMEAT2_ERRNO. It rejects unconfigured execution and accepts only ENOSYS, EINVAL, or EOPNOTSUPP. It exercises a full save and requires the original errno/journal path, no ownership transfer or write marker, no pending journal, and unchanged source bytes. Run the compiled core test binary directly under strace (not Cargo) with --ignored --exact source_files::transaction_tests::linux_journal_publication_injected_unsupported_error_is_fail_closed. For example, set OA_TEST_RENAMEAT2_ERRNO=38 and use strace -f -e inject=renameat2:error=ENOSYS TEST_BINARY ...; repeat EINVAL/22 and EOPNOTSUPP/95 on the x86_64 guest. Normal test runs report this test as ignored.

Source formatted and git diff --check passed. The exact three-file delta was copied into a separately hash-verified guest snapshot, preserving the original. Actual Rust 1.90 execution on Ubuntu24.04.4 x86_64/glibc2.39/ext4 passed all four normal focused tests (one fault oracle ignored), then all 42 normal library tests using the same built test binary. The ignored full-save oracle was subsequently explicitly executed and passed three times under strace6.8: ENOSYS38, EINVAL22 and EOPNOTSUPP95. Each raw trace confirms the actual renameat2(..., RENAME_NOREPLACE) injected errno; all original-byte, no-write, no-journal and exact-error assertions passed. Run:

cargo test --locked -p oa-core --lib linux_journal_publication

Frozen implementation input SHA-256:

File SHA-256
crates/oa-core/src/source_files.rs 669491DBDDB8E8E6556161426DFFA1A49AC1C826D31020BFF0956D81A4D84683
crates/oa-core/Cargo.toml 64824EC447BE777841A972BF06C894EF2624A37E4725583BCFE0389DEB9F00A3
Cargo.lock A6E070ED59B0BA19F18D60E8104494B777B470BD0268AE348CBE2B229790449F

Logs: .local/linux-qa/runtime/linux-journal-focused.log, linux-journal-all-and-injection.log, and journal-injection-{ENOSYS,EINVAL,EOPNOTSUPP}.strace in that directory. The delta archive SHA-256 is 3e57dad3be4c3c23b7952500924c41c5b671bcadb0e02a1d49d86f5bfa8f9300; all 226 original and patched snapshot file hashes were verified. Independent read-only review found the current x86_64 syscall ABI and path-buffer lifetime sound. The full library rerun includes existing interrupted-save and v1/v2 recovery cases. Linux environment and timing report.

Remaining boundaries: complete current Linux integration/application/package verification; other architectures/libcs/filesystems; network filesystems and hostile ancestor-path mutation. These results do not establish power-loss durability or model-file CAS. Journal retirement/recovery behavior was not redesigned in this change.