Changes touching this path

  • Fix describe/finalize losing writes and crashing under shared-store contention (#293) (#303) Two concurrent lanes over one shared store could corrupt each other's persisted state because DagRepo::save_to persists the shared, append-only surface by a read-modify-write of whole files, unserialized, and (for everything but the graph) by a blind overwrite from stale in-memory state. Root cause (a) - describe/finalize reports success but does not stick: a finalized change vanishes from the shared graph and a content key vanishes from the shared keyring when a racing lane's save reads the same on-disk version and writes back its own, dropping the other's append. The verb still returns Ok (its own lane files persisted), so no read path ever sees the change, and visible content reads back as "content you can't see". Fix: hold a store-level lock (.loot/store.lock, RAII, stale-breaking) across the shared critical section and MERGE each append-only structure against the current on-disk version instead of clobbering it (graph already unioned; now keyring - re-honoring hard-maroon purges so a removal is not resurrected - escrow, manifest, purges, attestations). The lock closes the read-modify-write race so the merge always sees the other writer's already-persisted append. Root cause (b) - intermittent exit 255 on successful-looking verbs: graph and identity were written with plain std::fs::write, which truncates then writes, so a concurrent reader tears the file (~11% empty/short, measured) and load_from/save_to fail mid-command; on a torn length prefix decode_nodes' Vec::with_capacity aborts the process (0xC0000409, shell-mapped to 255) after the buffered success line already printed. #252 had moved the other shared files to atomic temp+rename for exactly this reason but missed these two and the lane-owned process files. Fix: route graph/identity and the lane-owned writers (working, tip, tree-hash, next-change, heads, working-change, abandoned) through atomic temp+rename, so a reader always sees the whole prior-or-next file. Adds tests/describe_contention.rs: describe sticks under a hammering lane; concurrent finalizes from two lanes all survive the shared graph; loads never tear while a lane rewrites the shared graph. Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> dc8b7ecb
  • Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322) 20ac82e1 · dbf3dbe6…diff
  • Fix Windows rename-window read races + move contention stress tests out of the land gate (#476) Root cause of the describe_contention flake: on Windows, atomic_write's rename-replace leaves a brief window where a concurrent reader's open of the target transiently fails (PermissionDenied / ERROR_SHARING_VIOLATION os32, or NotFound as it swaps). Fixes: - store::read_replaced now retries os error 32 (sharing violation), not just PermissionDenied. - New store::read_replaced_required also retries NotFound (for provably-present files: graph/identity/keyring) — a required file blinking missing mid-rename is transient, not absent. - load_objects_loose reads each object via read_replaced (was raw fs::read, the un-retried path open_at hits before the graph). This cut the tear from ~195 to ~0 under contention. Since the 3 describe_contention tests are strict-zero TIMING stress tests (cargo runs them in parallel, self-amplifying load), they're now #[ignore]d out of the routine 'cargo test' land gate and run via 'cargo test -- --ignored'; their invariants stay guarded in-gate by deterministic unit tests (incl. the new transient-error predicate test). be7e9418 · dbf3dbe6…diff
  • emit_snapshot calls verbs in-process; test-support feature retired (#661) 8033f451 · dbf3dbe6…diff
  • Sweep the rust-1.96 clippy debt; document the land-holds-the-binary hazard (#667, #681) 26cfbaa9 · dbf3dbe6…diff
  • test scratch directories are named by one shared helper, common::scratch, from the process id and a per-process counter rather than the clock, so two tests asking for one tag at once are no longer handed one directory that the second caller empties and refills (the #2258 mechanism): 19 test files under crates/*/tests named a temp_dir path from SystemTime nanoseconds in 21 statements and now call fresh_dir or fresh_path, loot-cli files reaching it through mod common and loot-first exit_codes compiling the same std-only file by path, and revset_everywhere and plaintext_equality_census, which already counted, call it too. scratch_dirs.rs holds the #2258 pin, moved from revset_everywhere and aimed at the shared helper, red under the clock naming in 13 runs of 13 (0 passed and 1 failed, 1 to 16 of 400 directories handed twice), and a guard that walks every .rs file under crates/*/tests and refuses a temp_dir statement that reads the clock, red over the unconverted tree naming the 19 files (1 passed and 1 failed), red with buoy_exit put back (1 and 1) and red when the walk finds nothing (0 and 1), each restored green. no other test changes what it asserts. the workspace suite is green (4533 passed over 140 binaries, 13 ignored, from 4532 over 139: the guard is new and the pin moved into its own binary). test-only, so it owes no deploy (#2358) 6dd1e05b · dbf3dbe6…diff

Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.