trusty-common 0.49.0

Shared utilities and provider-agnostic streaming chat (ChatProvider, OllamaProvider, OpenRouter, tool-use) for trusty-* projects
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
//! Cargo ownership guard: move downloader-placed binaries aside so
//! `cargo install` never exits 101 on a file it does not own (#5777).
//!
//! Why: `cargo install` refuses to overwrite a binary in `$CARGO_HOME/bin`
//! that Cargo's own `.crates2.json` does not record — and after the #4964
//! Phase 3 destination flip, every prebuilt-download install places exactly
//! such files there. Without this guard the first `cargo install` after a
//! prebuilt install fails with `binary <name> already exists in destination`
//! (exit 101) on all nine cargo reach points, including the in-daemon
//! `upgrade` commands and MCP tools that never consult the downloader.
//!
//! What: [`OwnershipGuard::move_aside`] renames each existing destination
//! binary to a hidden, uniquely-suffixed aside name in the SAME directory
//! (same-filesystem rename — atomic, and never a `cp` over an on-PATH binary,
//! per the macOS cdhash rule). After the cargo run the caller settles the
//! guard exactly once:
//! - [`OwnershipGuard::commit`] (cargo exited 0) — removes each aside whose
//!   final path was re-written by cargo, and RESTORES any aside whose final
//!   path is still missing (cargo's "already installed, ignoring" skip writes
//!   nothing; deleting the aside there would delete the only copy).
//! - [`OwnershipGuard::restore`] (cargo failed) — renames every aside back so
//!   the pre-existing binary keeps working.
//!
//! Errors: any settle step that cannot complete returns `Err` naming every
//! aside file left behind, so a binary is never silently lost — the aside
//! copy still exists on disk under its hidden name.
//!
//! Safety net: a guard that is never settled — the `cargo install` future is
//! dropped (daemon shutdown kills the detached `tokio::spawn` upgrade tasks),
//! the process unwinds, or Ctrl-C aborts a `tctl` cargo fallback — restores
//! its asides from [`Drop`], and [`OwnershipGuard::move_aside`] sweeps stale
//! asides left by a SIGKILL'd process (dead pid) on the next run.
//!
//! Test: `tests` below — happy path, restore-on-failure, the
//! skip-restores-rather-than-deletes case, drop-without-settle, the
//! stale-aside sweep, and concurrent guards on the same binary set.

use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};

/// Monotonic per-process counter so two concurrent guards in one process can
/// never collide on an aside name even for the same binary.
static ASIDE_SEQ: AtomicU64 = AtomicU64::new(0);

/// Binaries moved aside ahead of a `cargo install`, awaiting settle.
///
/// Why/What/Test: see the module doc — this is the module's only type.
#[derive(Debug)]
pub(crate) struct OwnershipGuard {
    /// `(final path, aside path)` pairs, in move order.
    moved: Vec<(PathBuf, PathBuf)>,
}

impl OwnershipGuard {
    /// A guard that moved nothing (no bin dir resolvable, or nothing existed).
    pub(crate) fn inert() -> Self {
        Self { moved: Vec::new() }
    }

    /// Move every existing `bin_dir/<name>` aside, atomically per file.
    ///
    /// Why: clearing the destination BEFORE the cargo spawn is what turns the
    /// exit-101 refusal into a clean install; renaming (not deleting) is what
    /// makes failure recoverable.
    /// What: for each name, when `bin_dir/<name>` exists it is renamed to
    /// `bin_dir/.<name>.pre-cargo.<pid>.<seq>`. On any rename failure the
    /// already-moved files are restored and the error is returned — the guard
    /// never leaves a half-cleared destination behind.
    /// Also sweeps stale `.{name}.pre-cargo.*` asides whose owning pid is
    /// dead (see [`sweep_stale_asides`]) so a SIGKILL — where [`Drop`] never
    /// runs — is recovered on the NEXT upgrade instead of never.
    /// Test: `move_aside_then_commit_removes_asides`,
    /// `sweep_restores_stale_aside_when_destination_missing`,
    /// `sweep_deletes_stale_aside_when_destination_exists`,
    /// `sweep_leaves_live_process_asides_alone`; the rename-failure rollback
    /// is covered indirectly by `restore_puts_every_binary_back` (same
    /// restore path).
    pub(crate) fn move_aside(bin_dir: &Path, binaries: &[String]) -> anyhow::Result<Self> {
        let mut moved: Vec<(PathBuf, PathBuf)> = Vec::new();
        for name in binaries {
            let dest = bin_dir.join(name);
            // #5777: recover asides orphaned by a SIGKILL'd process BEFORE
            // the existence check — a stale aside with the destination
            // missing is exactly the state where the only copy of the
            // binary is hiding under the aside name.
            sweep_stale_asides(bin_dir, name, &dest);
            if !dest.exists() {
                continue;
            }
            let seq = ASIDE_SEQ.fetch_add(1, Ordering::Relaxed);
            let aside = bin_dir.join(format!(".{name}.pre-cargo.{}.{seq}", std::process::id()));
            if let Err(e) = std::fs::rename(&dest, &aside) {
                // TOCTOU with a concurrent guard: the file existed a moment
                // ago but another upgrade already moved it aside. That guard
                // owns it now; nothing here to protect.
                if e.kind() == std::io::ErrorKind::NotFound {
                    continue;
                }
                // Roll back what was already moved before propagating.
                let rollback = restore_pairs(&moved);
                let mut err = anyhow::anyhow!(
                    "cargo ownership guard could not move {} aside: {e}",
                    dest.display()
                );
                if let Err(r) = rollback {
                    err = err.context(format!("and rollback was incomplete: {r}"));
                }
                return Err(err);
            }
            tracing::debug!(
                dest = %dest.display(),
                aside = %aside.display(),
                "moved untracked binary aside before cargo install (#5777)"
            );
            moved.push((dest, aside));
        }
        Ok(Self { moved })
    }

    /// Settle after a SUCCESSFUL cargo run.
    ///
    /// What: for each moved pair — if cargo re-wrote the final path, the aside
    /// is deleted; if the final path is still missing (cargo's
    /// already-installed skip), the aside is renamed back so the binary is not
    /// lost. Collects failures rather than stopping at the first.
    /// Test: `move_aside_then_commit_removes_asides`,
    /// `commit_restores_when_cargo_skipped_writing`,
    /// `drop_after_commit_never_restores_over_cargo_output`.
    pub(crate) fn commit(mut self) -> anyhow::Result<()> {
        // #5777: take the pairs so the Drop safety net sees an empty guard
        // and cannot double-settle after this normal settle.
        let moved = std::mem::take(&mut self.moved);
        let mut failures: Vec<String> = Vec::new();
        for (dest, aside) in &moved {
            if dest.exists() {
                if let Err(e) = std::fs::remove_file(aside) {
                    failures.push(format!("could not remove aside {}: {e}", aside.display()));
                }
            } else if let Err(e) = std::fs::rename(aside, dest) {
                failures.push(format!(
                    "cargo wrote nothing at {} and the aside copy {} could not be \
                     restored: {e}",
                    dest.display(),
                    aside.display()
                ));
            }
        }
        if failures.is_empty() {
            Ok(())
        } else {
            Err(anyhow::anyhow!(
                "cargo ownership guard settle incomplete: {}",
                failures.join("; ")
            ))
        }
    }

    /// Settle after a FAILED cargo run: rename every aside back.
    ///
    /// Test: `restore_puts_every_binary_back`.
    pub(crate) fn restore(mut self) -> anyhow::Result<()> {
        // #5777: take the pairs so Drop no-ops after this normal settle.
        let moved = std::mem::take(&mut self.moved);
        restore_pairs(&moved)
    }
}

/// Last-resort settle: restore the asides of a guard that was never settled.
///
/// Why (#5777, code-critic round on PR #5778): between `move_aside` and the
/// explicit settle sits the entire multi-minute `cargo install` `.await`.
/// If that future is dropped — both daemon MCP upgrade paths run in detached
/// `tokio::spawn` tasks that die with the runtime on daemon shutdown — or the
/// process unwinds on panic or a Ctrl-C-triggered abort during a `tctl`
/// cargo-fallback build, no settle ever runs. Without this impl the
/// destination is left EMPTY and the only copy of the binary sits under a
/// hidden aside name nothing restores: an interruption that pre-guard left
/// the old binary intact would post-guard remove it from `PATH` and break the
/// next launchd respawn. Restoring in `Drop` makes the guard
/// infallible-by-construction for every in-process exit path; the dead-pid
/// sweep in [`OwnershipGuard::move_aside`] covers SIGKILL, where `Drop`
/// cannot run.
///
/// What: best-effort [`restore_pairs`] over whatever pairs are still held.
/// [`OwnershipGuard::commit`] and [`OwnershipGuard::restore`] `mem::take` the
/// pairs first, so after a normal settle this is a no-op. `Drop` cannot
/// return errors and must never panic, so failures are logged via `tracing`
/// — the aside copies named in the log still exist on disk.
///
/// Test: `dropped_guard_restores_its_asides`,
/// `drop_after_commit_never_restores_over_cargo_output`.
impl Drop for OwnershipGuard {
    fn drop(&mut self) {
        if self.moved.is_empty() {
            return;
        }
        let moved = std::mem::take(&mut self.moved);
        match restore_pairs(&moved) {
            Ok(()) => tracing::warn!(
                restored = moved.len(),
                "cargo ownership guard dropped without settling; asides \
                 restored from Drop (#5777)"
            ),
            Err(e) => tracing::error!(
                error = %e,
                "cargo ownership guard dropped without settling and the \
                 Drop restore was incomplete — aside copies remain on disk \
                 (#5777)"
            ),
        }
    }
}

/// Rename each `(dest, aside)` pair back to `dest`, collecting failures.
fn restore_pairs(moved: &[(PathBuf, PathBuf)]) -> anyhow::Result<()> {
    let mut failures: Vec<String> = Vec::new();
    for (dest, aside) in moved {
        if let Err(e) = std::fs::rename(aside, dest) {
            failures.push(format!(
                "could not restore {} from {}: {e}",
                dest.display(),
                aside.display()
            ));
        }
    }
    if failures.is_empty() {
        Ok(())
    } else {
        Err(anyhow::anyhow!(
            "cargo ownership guard restore incomplete (aside copies left on \
             disk): {}",
            failures.join("; ")
        ))
    }
}

/// Recover `.{name}.pre-cargo.<pid>.<seq>` asides orphaned by a dead process.
///
/// Why (#5777): `Drop` covers every in-process exit path, but a SIGKILL (or
/// power loss) kills the process before `Drop` can run, stranding the moved
/// binary under its hidden aside name forever — and, when cargo never wrote
/// the destination, leaving the bin dir with NO visible copy at all. Sweeping
/// at the start of the next `move_aside` bounds the damage to one interrupted
/// run and stops aside litter accumulating in `~/.cargo/bin`.
///
/// What: scans `bin_dir` for entries named `.{name}.pre-cargo.<pid>.<seq>`
/// (sorted for determinism). Entries whose pid is this process or still
/// alive belong to a concurrent guard and are left alone. For a dead pid:
/// when `dest` is missing the aside is renamed back (it is the only copy);
/// otherwise the aside is deleted. When MORE than one stale aside exists
/// with `dest` missing, the first (sorted) aside wins the restore and every
/// later one is deleted WITH an explicit warning naming it — the copies may
/// differ and the discard must never be silent (#5778 review). Deleting
/// rather than `break`-ing is deliberate: a `break` merely defers the same
/// deletion to the next sweep's dest-exists branch, where the log line no
/// longer knows the file was the sibling of a restore. Entirely best-effort
/// — every failure is logged via `tracing` and never propagated, because an
/// unswept aside must not block the upgrade that is about to run.
///
/// Test: `sweep_restores_stale_aside_when_destination_missing`,
/// `sweep_deletes_stale_aside_when_destination_exists`,
/// `sweep_restores_first_stale_aside_and_discards_the_rest`,
/// `sweep_leaves_live_process_asides_alone`.
fn sweep_stale_asides(bin_dir: &Path, name: &str, dest: &Path) {
    let prefix = format!(".{name}.pre-cargo.");
    let entries = match std::fs::read_dir(bin_dir) {
        Ok(entries) => entries,
        Err(e) => {
            tracing::debug!(
                bin_dir = %bin_dir.display(),
                "stale-aside sweep skipped, could not read dir: {e}"
            );
            return;
        }
    };
    let mut stale: Vec<PathBuf> = entries
        .filter_map(Result::ok)
        .filter_map(|entry| {
            let file_name = entry.file_name().to_string_lossy().into_owned();
            let rest = file_name.strip_prefix(&prefix)?;
            let pid: u32 = rest.split('.').next()?.parse().ok()?;
            (pid != std::process::id() && !pid_is_alive(pid)).then(|| entry.path())
        })
        .collect();
    stale.sort();
    // #5778: whether an earlier iteration of THIS sweep already restored an
    // aside to `dest` — a later delete is then discarding a possibly
    // different copy, and must say so rather than log as routine litter.
    let mut restored_this_sweep = false;
    for aside in stale {
        if dest.exists() {
            match std::fs::remove_file(&aside) {
                Ok(()) if restored_this_sweep => tracing::warn!(
                    aside = %aside.display(),
                    dest = %dest.display(),
                    "discarded an ADDITIONAL stale pre-cargo aside — an \
                     earlier aside was already restored to the destination \
                     this sweep and the two copies may differ (#5778)"
                ),
                // #5778 review: this branch DELETES litter — it must never
                // log with the restore branch's "recovered" wording, or an
                // operator cannot tell a restore from a deletion.
                Ok(()) => tracing::warn!(
                    aside = %aside.display(),
                    dest = %dest.display(),
                    "deleted stale pre-cargo aside litter from a dead process (#5777)"
                ),
                Err(e) => tracing::warn!(
                    aside = %aside.display(),
                    "could not delete stale pre-cargo aside litter: {e}"
                ),
            }
        } else {
            match std::fs::rename(&aside, dest) {
                Ok(()) => {
                    restored_this_sweep = true;
                    tracing::warn!(
                        aside = %aside.display(),
                        dest = %dest.display(),
                        "recovered stale pre-cargo aside from a dead process (#5777)"
                    );
                }
                Err(e) => tracing::warn!(
                    aside = %aside.display(),
                    "could not restore stale pre-cargo aside: {e}"
                ),
            }
        }
    }
}

/// Whether a process with `pid` is currently running.
///
/// What: `kill(pid, 0)` — signal 0 performs the permission/existence check
/// without delivering anything. `0` means alive; `EPERM` means alive but not
/// ours. Only `ESRCH` counts as dead — every other error is treated as alive
/// so the sweep fails CLOSED (never reclaims an aside a live guard owns).
/// Test: exercised through the `sweep_*` tests (dead pid from a reaped
/// child, live pid from the test process itself).
#[cfg(unix)]
fn pid_is_alive(pid: u32) -> bool {
    // #5778: `pid as pid_t` would wrap a pid above `i32::MAX` negative, and
    // `kill(-n, 0)` probes a process GROUP, not a process — a live guard's
    // aside could then be swept. Unreachable on today's platforms (Linux
    // caps pids at 4194304, macOS at 99998), but map the overflow to -1
    // anyway: `kill(-1, 0)` returns EPERM, which reads as alive, so the
    // sweep stays fail-closed.
    let pid = libc::pid_t::try_from(pid).unwrap_or(-1);
    // Safety: kill(2) with signal 0 only probes; it cannot affect the target.
    let rc = unsafe { libc::kill(pid, 0) };
    if rc == 0 {
        return true;
    }
    std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
}

/// Non-unix fallback: report every pid alive, so the sweep never runs.
///
/// Why: no portable cheap liveness probe; failing CLOSED (treat as alive)
/// merely leaves litter, while a wrong "dead" answer could steal a live
/// guard's aside.
#[cfg(not(unix))]
fn pid_is_alive(_pid: u32) -> bool {
    true
}

#[cfg(test)]
mod tests {
    use super::*;

    fn write(path: &Path, content: &str) {
        std::fs::write(path, content).expect("write fixture");
    }

    fn names(v: &[&str]) -> Vec<String> {
        v.iter().map(|s| (*s).to_owned()).collect()
    }

    /// Why: the happy path — cargo replaced the binaries, so the asides must
    /// be gone and the fresh files untouched.
    /// What: moves two binaries aside, simulates cargo writing new ones,
    /// commits, and asserts new content survives with no hidden litter.
    #[test]
    fn move_aside_then_commit_removes_asides() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tm"), "old-tm");
        write(&tmp.path().join("trusty-mpm"), "old-mpm");

        let guard =
            OwnershipGuard::move_aside(tmp.path(), &names(&["tm", "trusty-mpm"])).expect("move");
        assert!(!tmp.path().join("tm").exists(), "destination must be clear");

        // Simulate `cargo install` writing fresh binaries.
        write(&tmp.path().join("tm"), "new-tm");
        write(&tmp.path().join("trusty-mpm"), "new-mpm");

        guard.commit().expect("commit");
        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tm")).expect("read"),
            "new-tm"
        );
        let leftovers: Vec<_> = std::fs::read_dir(tmp.path())
            .expect("read_dir")
            .filter_map(|e| e.ok())
            .filter(|e| e.file_name().to_string_lossy().starts_with('.'))
            .collect();
        assert!(
            leftovers.is_empty(),
            "no aside litter after commit: {leftovers:?}"
        );
    }

    /// Why (#5777 failure path): a cargo failure must leave the machine
    /// exactly as it was — the downloader-placed binary keeps working.
    /// What: moves aside, does NOT write replacements (cargo "failed"),
    /// restores, and asserts the original bytes are back at the final path.
    #[test]
    fn restore_puts_every_binary_back() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tctl"), "old-tctl");
        write(&tmp.path().join("trusty-installer"), "old-ti");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tctl", "trusty-installer"]))
            .expect("move");
        assert!(!tmp.path().join("tctl").exists());

        guard.restore().expect("restore");
        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tctl")).expect("read"),
            "old-tctl"
        );
        assert_eq!(
            std::fs::read_to_string(tmp.path().join("trusty-installer")).expect("read"),
            "old-ti"
        );
    }

    /// Why: `cargo install --locked` exits 0 WITHOUT writing when
    /// `.crates2.json` already records the exact version ("already installed,
    /// ignoring"). Deleting the aside in that case would delete the only copy
    /// of the binary — the one data-loss shape a naive delete-aside has.
    /// What: commits with the final path still missing and asserts the aside
    /// was renamed back rather than removed.
    #[test]
    fn commit_restores_when_cargo_skipped_writing() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tagent"), "only-copy");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tagent"])).expect("move");
        // Simulated cargo run: exit 0, nothing written (skip case).
        guard.commit().expect("commit");

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tagent")).expect("read"),
            "only-copy",
            "the skip case must restore the aside, never delete the only copy"
        );
    }

    /// Why: a missing destination is the common case for a crate never
    /// installed before; the guard must be a no-op, not an error.
    #[test]
    fn move_aside_skips_missing_binaries() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["not-there"])).expect("move");
        guard.commit().expect("commit is a no-op");
    }

    /// Why (#5777 concurrency): two upgrades racing over the same bin dir
    /// (e.g. a daemon MCP `upgrade` tool and a manual CLI upgrade) must never
    /// lose the binary or collide on aside names. Unique per-process sequence
    /// suffixes plus rename semantics mean exactly one thread owns the file
    /// at any time; whichever guard holds it restores it.
    /// What: N threads each move-aside + restore the same binary name in the
    /// same dir; afterwards the file must exist with its original content and
    /// no aside litter may remain.
    #[test]
    fn concurrent_guards_never_lose_the_binary() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let bin = tmp.path().join("trusty-search");
        write(&bin, "v1");

        std::thread::scope(|s| {
            for _ in 0..8 {
                let dir = tmp.path().to_path_buf();
                s.spawn(move || {
                    for _ in 0..25 {
                        let guard = OwnershipGuard::move_aside(&dir, &names(&["trusty-search"]))
                            .expect("move_aside must not error");
                        guard.restore().expect("restore must not error");
                    }
                });
            }
        });

        assert_eq!(
            std::fs::read_to_string(&bin).expect("binary must survive the race"),
            "v1"
        );
        let leftovers: Vec<_> = std::fs::read_dir(tmp.path())
            .expect("read_dir")
            .filter_map(|e| e.ok())
            .filter(|e| e.file_name().to_string_lossy().starts_with('.'))
            .collect();
        assert!(
            leftovers.is_empty(),
            "no aside litter after the race: {leftovers:?}"
        );
    }

    /// Why (#5778 review; independently flagged by the code-critic round):
    /// `concurrent_guards_never_lose_the_binary` settles every guard via
    /// `restore`, so the `commit` path was never exercised under contention —
    /// yet a committing guard racing a restoring guard is exactly what a
    /// daemon MCP `upgrade` racing a manual CLI upgrade produces.
    /// What: half the threads simulate a SUCCESSFUL cargo run (write fresh
    /// content at the destination, then `commit`); the other half simulate a
    /// FAILED run (`restore`). Whatever interleaving wins, the surviving
    /// binary must hold one of the two valid contents and no aside litter
    /// may remain.
    #[test]
    fn concurrent_commit_and_restore_keep_a_valid_binary() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let bin = tmp.path().join("trusty-search");
        write(&bin, "v1");

        std::thread::scope(|s| {
            for i in 0..8 {
                let dir = tmp.path().to_path_buf();
                s.spawn(move || {
                    for _ in 0..25 {
                        let guard = OwnershipGuard::move_aside(&dir, &names(&["trusty-search"]))
                            .expect("move_aside must not error");
                        if i % 2 == 0 {
                            // Simulated successful cargo run: cargo writes
                            // the fresh binary, then the guard commits.
                            write(&dir.join("trusty-search"), "v2");
                            guard.commit().expect("commit must not error");
                        } else {
                            guard.restore().expect("restore must not error");
                        }
                    }
                });
            }
        });

        let survivor = std::fs::read_to_string(&bin).expect("binary must survive the race");
        assert!(
            survivor == "v1" || survivor == "v2",
            "surviving binary must be one of the two valid copies, got {survivor:?}"
        );
        assert!(
            hidden_entries(tmp.path()).is_empty(),
            "no aside litter after the commit/restore race"
        );
    }

    /// Collect the hidden (dot-prefixed) entries left in `dir`.
    fn hidden_entries(dir: &Path) -> Vec<String> {
        std::fs::read_dir(dir)
            .expect("read_dir")
            .filter_map(|e| e.ok())
            .map(|e| e.file_name().to_string_lossy().into_owned())
            .filter(|n| n.starts_with('.'))
            .collect()
    }

    /// Spawn and reap a short-lived child, returning its (now dead) pid.
    ///
    /// #5778 review: unix-only — `true` does not exist on Windows, and the
    /// non-unix `pid_is_alive` stub reports every pid alive anyway, so the
    /// sweep the callers exercise can never fire there.
    #[cfg(unix)]
    fn dead_pid() -> u32 {
        let mut child = std::process::Command::new("true")
            .spawn()
            .expect("spawn `true`");
        let pid = child.id();
        child.wait().expect("wait for `true`");
        pid
    }

    /// Why (#5778 code-critic HIGH): an unsettled guard — dropped future,
    /// panic unwind, Ctrl-C abort — must restore its asides, or the
    /// destination is left empty and a launchd respawn breaks.
    /// What: moves a binary aside, drops the guard WITHOUT settling, and
    /// asserts the destination is back with the original contents and no
    /// aside litter remains.
    #[test]
    fn dropped_guard_restores_its_asides() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tm"), "only-copy");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tm"])).expect("move");
        assert!(!tmp.path().join("tm").exists(), "destination cleared");
        drop(guard);

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tm")).expect("read"),
            "only-copy",
            "Drop must restore an unsettled guard's asides"
        );
        assert!(
            hidden_entries(tmp.path()).is_empty(),
            "no aside litter after Drop"
        );
    }

    /// Why: `commit`/`restore` `mem::take` the pairs, so Drop must be a
    /// no-op after a normal settle — it must never rename the (deleted)
    /// aside back over cargo's freshly-written binary.
    /// What: settles via `commit` with cargo output in place, and asserts
    /// the NEW content survives the guard's drop.
    #[test]
    fn drop_after_commit_never_restores_over_cargo_output() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tga"), "old");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tga"])).expect("move");
        write(&tmp.path().join("tga"), "new"); // simulated cargo output
        guard.commit().expect("commit"); // guard is dropped inside commit

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tga")).expect("read"),
            "new",
            "Drop after commit must not restore the old binary"
        );
        assert!(hidden_entries(tmp.path()).is_empty());
    }

    /// Why (#5778 code-critic HIGH, SIGKILL shape): Drop never runs under
    /// SIGKILL; the next `move_aside` must restore an orphaned aside whose
    /// destination is missing — it holds the only copy of the binary.
    /// What: plants a stale aside under a reaped child's pid with no
    /// destination file, runs `move_aside` + `restore`, and asserts the
    /// binary is back at the destination with the stale copy's contents.
    #[cfg(unix)]
    #[test]
    fn sweep_restores_stale_aside_when_destination_missing() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let stale = tmp.path().join(format!(".tctl.pre-cargo.{}.7", dead_pid()));
        write(&stale, "stranded-only-copy");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tctl"])).expect("move");
        // The sweep restored the aside to the destination, so the guard then
        // moved the recovered file aside like any pre-existing binary.
        guard.restore().expect("restore");

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tctl")).expect("read"),
            "stranded-only-copy",
            "a dead-pid aside with no destination must be restored"
        );
        assert!(hidden_entries(tmp.path()).is_empty(), "no litter remains");
    }

    /// Why: when the destination EXISTS, a dead-pid aside is pure litter —
    /// deleting it (rather than restoring) is what stops asides accumulating
    /// in `~/.cargo/bin` across interrupted runs.
    /// What: plants a stale aside next to a live destination, runs
    /// `move_aside` + `restore`, and asserts the destination is unchanged and
    /// the litter is gone.
    #[cfg(unix)]
    #[test]
    fn sweep_deletes_stale_aside_when_destination_exists() {
        let tmp = tempfile::tempdir().expect("tempdir");
        write(&tmp.path().join("tagent"), "current");
        let stale = tmp
            .path()
            .join(format!(".tagent.pre-cargo.{}.3", dead_pid()));
        write(&stale, "old-litter");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tagent"])).expect("move");
        guard.restore().expect("restore");

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tagent")).expect("read"),
            "current",
            "the live destination must win over dead-pid litter"
        );
        assert!(hidden_entries(tmp.path()).is_empty(), "litter deleted");
    }

    /// Why (#5778 review): with the destination missing and TWO stale asides
    /// stranded for the same binary, the first (sorted) aside is restored
    /// and every later one is discarded — deliberately, with an explicit
    /// warning, never silently. Before this fix the second aside hit the
    /// delete branch purely because the first restore made `dest` exist.
    /// What: plants two dead-pid asides with distinct contents and no
    /// destination, sweeps via `move_aside` + `restore`, and asserts the
    /// destination holds the sorted-first aside's bytes with zero hidden
    /// litter left behind.
    #[cfg(unix)]
    #[test]
    fn sweep_restores_first_stale_aside_and_discards_the_rest() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let pid = dead_pid();
        write(
            &tmp.path().join(format!(".tctl.pre-cargo.{pid}.3")),
            "first-copy",
        );
        write(
            &tmp.path().join(format!(".tctl.pre-cargo.{pid}.7")),
            "second-copy",
        );

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tctl"])).expect("move");
        guard.restore().expect("restore");

        assert_eq!(
            std::fs::read_to_string(tmp.path().join("tctl")).expect("read"),
            "first-copy",
            "the sorted-first stale aside must win the restore"
        );
        assert!(
            hidden_entries(tmp.path()).is_empty(),
            "additional stale asides are discarded (with a warning), not kept"
        );
    }

    /// Why: an aside owned by a LIVE process belongs to a concurrent guard —
    /// stealing it would race that guard's own settle.
    /// What: plants an aside under this test process's (live) pid and asserts
    /// the sweep leaves it untouched, byte for byte. A wrong sweep would have
    /// renamed the aside to `tm` (and this guard would then carry it through
    /// commit), so `tm` materialising is the failure signal for the final
    /// assertion — it is not a vacuous never-existed check (#5778 review).
    #[cfg(unix)]
    #[test]
    fn sweep_leaves_live_process_asides_alone() {
        let tmp = tempfile::tempdir().expect("tempdir");
        let live = tmp
            .path()
            .join(format!(".tm.pre-cargo.{}.9999", std::process::id()));
        write(&live, "owned-by-a-live-guard");

        let guard = OwnershipGuard::move_aside(tmp.path(), &names(&["tm"])).expect("move");
        guard.commit().expect("commit (nothing moved)");

        assert_eq!(
            std::fs::read_to_string(&live).expect("read live aside"),
            "owned-by-a-live-guard",
            "a live process's aside must never be swept or rewritten"
        );
        assert!(
            !tmp.path().join("tm").exists(),
            "a wrong sweep would have restored the live aside to `tm`"
        );
    }
}