trusty-mpm 1.8.2

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
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
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
//! #4300 acceptance: the CLI paths honour the #3455 per-project worktree
//! opt-out — no worktree AND no base clone.
//!
//! Why: before #4300 the opt-out was consulted at exactly one decision point,
//! inside the daemon. `tm launch` runs in-process and never asks the daemon;
//! the guided fallback runs precisely BECAUSE the daemon is unreachable. Both
//! provisioned a clone and a worktree for projects registered with `worktree:
//! false` (live for `bobmatnyc/writing` and `bob-duetto/cto`). These tests
//! pin both paths, and the `unwrap_or(true)` default they must not regress.
//! What: per CLI path — one opted-out case asserting the SPECIFIC main-checkout
//! path is returned and that the base-clone directory and the `.worktrees`
//! directory do NOT exist, and one opted-in/unset control asserting the
//! worktree IS created at its exact expected path.
//! Fixture remotes: every origin here uses the non-resolvable `.invalid` TLD
//! (RFC 2606) with fixture-only owner/repo names. THIS paragraph is the
//! canonical statement of that convention — cite it (file + this `Fixture
//! remotes:` heading, never a line range) from any new test that needs the
//! rationale. A previous revision forwarded readers to a line range in
//! `tests_behavior_c_tests.rs` instead; that pointer was always wrong (that
//! file lives one directory up, its cited lines hold unrelated
//! `guided_resume_plan_*` tests, and it does not contain the string
//! `.invalid` anywhere), and the line range is exactly the part that rotted.
//! An earlier revision of the FIXTURES used the REAL `bobmatnyc/writing` and
//! `bob-duetto/cto` remotes, and the guard-neutralised revert run proved why
//! that is wrong: `ensure_base_clone`
//! genuinely cloned both private repos over the network (88.43s vs 0.28s
//! baseline). The assertion under test is the opt-out DECISION, never that a
//! clone succeeds, so a host that cannot resolve is lossless here and keeps
//! the suite hermetic and fast whether the guard is present or not.
//! Concurrency: the fallback tests read `TRUSTY_MPM_REPOS_ROOT` (a
//! process-global) through `inproject::base_clone_path`, so they are
//! `#[serial_test::serial]` and restore the previous value via an RAII guard
//! that runs on panic-driven unwind too. Nothing here deletes anything: the
//! only filesystem writes are `git init`/`git worktree add` under
//! test-owned `TempDir`s, so a clobbered global can make a test FAIL but can
//! never make it destructive.
//! Test: this file.

use std::path::{Path, PathBuf};

use tempfile::TempDir;
use trusty_mpm::core::disk_usage_guard::{DiskGate, MeasuredMount};
use trusty_mpm::daemon::managed_routes::inproject;
use trusty_mpm::project::{Project, ProjectRegistry};
use trusty_mpm::session_manager::ManagedSessionId;

use super::{
    LaunchDir, ManagedWorkspace, provision_for_fallback, provision_for_launch,
    provision_for_launch_gated,
};

/// A disk gate pinned to an empty mount, for the tests that create a worktree.
///
/// Why (#7603): `provision_*` measures the REAL mount its worktree would land on
/// and reads `disk.max_usage_pct` from the operator's REAL
/// `~/.trusty-tools/trusty-mpm/config.yaml`, so the three worktree-creating
/// tests below decided their verdict from how full this machine happens to be —
/// they passed 12764/0 and went red forty minutes later on identical code. None
/// of them asserts anything about disk. `$HOME` cannot be redirected out of that
/// read from a `tm`-bin test, because `env_isolation_tests.rs` bans writing it,
/// so the measurement is injected instead.
/// What: a synthetic mount at 0.0%, which is below every threshold
/// `resolve_max_usage_pct` will accept (`1..=100`) — so no operator config, and
/// no host, can make these tests refuse.
/// Test: used by every worktree-creating test below.
fn empty_disk() -> DiskGate {
    DiskGate::Pinned(Some(MeasuredMount {
        mount_point: "/fixture-mount".to_string(),
        usage_pct: 0.0,
        bytes: None,
    }))
}

/// A disk gate pinned ABOVE any threshold `resolve_max_usage_pct` accepts.
///
/// Why: the counterpart to [`empty_disk`] — the injection would be an off switch
/// if a pinned value could only ever allow. Pinning 100.0 has to refuse.
/// What: a synthetic mount at 100.0%, at or above every accepted threshold.
/// Test: `a_pinned_over_threshold_gate_refuses_a_launch_worktree`,
/// `a_pinned_over_threshold_gate_refuses_a_fallback_worktree`.
fn full_disk() -> DiskGate {
    DiskGate::Pinned(Some(MeasuredMount {
        mount_point: "/fixture-mount".to_string(),
        usage_pct: 100.0,
        bytes: None,
    }))
}

/// RAII override of `TRUSTY_MPM_REPOS_ROOT`, restored on drop (incl. unwind).
///
/// Why: `inproject::base_clone_path` reads this global, and a test that
/// panicked mid-assertion must not leave the rest of the binary pointed at a
/// deleted `TempDir`.
/// What: sets the var on construction, restores the prior value (or removes
/// it) on drop. Callers MUST be `#[serial_test::serial]`.
/// Test: used by the fallback tests below.
struct ReposRootGuard {
    prev: Option<std::ffi::OsString>,
}

impl ReposRootGuard {
    fn set(path: &Path) -> Self {
        let prev = std::env::var_os(inproject::REPOS_ROOT_ENV);
        // SAFETY: every caller is `#[serial_test::serial]`, so no other test
        // thread races this set/restore.
        unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, path) };
        Self { prev }
    }
}

impl Drop for ReposRootGuard {
    fn drop(&mut self) {
        // SAFETY: see `set`.
        match self.prev.take() {
            Some(v) => unsafe { std::env::set_var(inproject::REPOS_ROOT_ENV, v) },
            None => unsafe { std::env::remove_var(inproject::REPOS_ROOT_ENV) },
        }
    }
}

/// Build a registry directory containing one project with the given opt-out.
///
/// Why: both paths resolve the opt-out by reading `projects.json` out of
/// process, so the fixture must be a REAL on-disk registry, not a stub.
/// What: writes `<tmp>/projects.json` via `ProjectRegistry` and returns the
/// owning `TempDir`. Any failure `unwrap`s — a fixture that cannot be built is
/// a test failure, never a skip.
/// Test: used by every test below.
async fn registry_with(repo_url: &str, worktree: Option<bool>) -> TempDir {
    let dir = tempfile::tempdir().unwrap();
    let registry = ProjectRegistry::load(dir.path()).await.unwrap();
    registry
        .register(Project {
            name: "fixture".into(),
            repo_url: repo_url.into(),
            default_branch: "main".into(),
            stack_hint: None,
            tags: vec![],
            description: None,
            gh_user: None,
            gh_account: None,
            github: None,
            commit_name: None,
            commit_email: None,
            worktree,
        })
        .await
        .unwrap();
    assert!(
        dir.path().join("projects.json").is_file(),
        "fixture precondition: projects.json must exist at {}",
        dir.path().display()
    );
    dir
}

/// Create a real git repository at `path` with one empty commit.
///
/// Why: `create_session_worktree` runs `git worktree add`, which requires a
/// HEAD commit; the opted-IN control tests must reach that code for real.
/// What: `git init` + identity config + `commit --allow-empty`, asserting each
/// step succeeded. No skip path — a missing `git` fails the test.
/// Test: used by the opted-in control tests.
fn init_git_repo(path: &Path) {
    std::fs::create_dir_all(path).unwrap();
    let run = |args: &[&str]| {
        let status = std::process::Command::new("git")
            .args(args)
            .current_dir(path)
            .status()
            .unwrap_or_else(|e| panic!("git {args:?} failed to spawn: {e}"));
        assert!(
            status.success(),
            "git {args:?} failed in {}",
            path.display()
        );
    };
    run(&["init"]);
    run(&["config", "user.email", "ci@test.invalid"]);
    run(&["config", "user.name", "CI"]);
    run(&["commit", "--allow-empty", "-m", "init"]);
}

/// Assert `base` shows no trace of provisioning: no clone, no worktrees dir.
///
/// Why: the acceptance bar is "neither a worktree NOR a base clone", and the
/// daemon's ordering exists precisely so the clone is not created either.
/// What: asserts the base-clone directory itself, its `.git`, and its
/// `.worktrees` child are all absent.
/// Test: used by both opted-out tests.
fn assert_nothing_provisioned(base: &Path) {
    assert!(
        !base.exists(),
        "#4300: base clone dir must NOT be created for an opted-out project: {}",
        base.display()
    );
    assert!(
        !base.join(".git").exists(),
        "#4300: base clone must NOT be initialised: {}",
        base.join(".git").display()
    );
    assert!(
        !base.join(".worktrees").exists(),
        "#4300: worktree dir must NOT be created: {}",
        base.join(".worktrees").display()
    );
}

/// Path a UUID-named session worktree lands at under `base`.
///
/// Why: the tests assert on the SPECIFIC path, not on `is_ok()`.
/// What: `<base>/.worktrees/<session-id>`.
/// Test: used by the opted-in control tests.
fn expected_worktree(base: &Path, session_id: &ManagedSessionId) -> PathBuf {
    base.join(".worktrees").join(session_id.to_string())
}

// ── Path (a): `tm launch` ────────────────────────────────────────────────────
//
// #5274 rewrote this group. Before it, these four tests asked "did the CLI read
// the project's opt-out?"; that question is now wrong at the root, because the
// project's `worktree` flag no longer decides where a SESSION runs — it decides
// whether the AGENTS that session dispatches get isolated. What the tests ask
// instead is the placement contract's own two rules: the project cannot move a
// session (rows 1 and 2), and an explicit request can (row 3).

/// Contract row 1 — a project registered `worktree: true` does NOT get its
/// session moved into a worktree (#5274).
///
/// Why: this is the whole point of the change, and the one row that used to
/// resolve the other way. `tm launch` read `worktree_enabled_for_origin_at`,
/// so a `worktree: true` registration (or, before it, the built-in `true`
/// default that 31 of 33 registered projects fall through to) provisioned a base
/// clone plus a per-session worktree and ran the session there. The owner ruling
/// of 2026-08-09 separates the two questions: the PM works in the project's own
/// checkout, and `worktree: true` means the AGENTS it dispatches are isolated.
/// A machine-global `projects.json` must not be able to relocate a human's
/// session.
/// What: registers the launch origin with an EXPLICIT `worktree: true` — the
/// strongest statement the project layer can make — passes no request, and
/// asserts the returned workspace is the live checkout with nothing provisioned
/// at the base-clone path.
/// Test: itself. RED before #5274: the registry lookup returned `true` and the
/// function returned `Worktree(<base>/.worktrees/<id>)`.
#[tokio::test]
async fn provision_for_launch_ignores_a_registered_worktree_true_project() {
    let origin = "https://github.invalid/fixture-owner/isolated-repo";
    // Registered, and registered the "isolating" way. Kept deliberately: the
    // fixture is the assertion. `provision_for_launch` no longer takes a
    // registry directory at all, so a project cannot reach this decision even
    // in principle — this test pins that the signature stays that way.
    let _registry = registry_with(
        "https://github.invalid/fixture-owner/isolated-repo",
        Some(true),
    )
    .await;

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch(
        origin,
        &base,
        live.path(),
        false,
        LaunchDir::OperatorCwd,
        &session_id,
        None, // #8994: no worktree is requested, so no ledger is touched
    )
    .await
    .unwrap();

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(base.clone()),
        "#5274 row 1: a `worktree: true` project must not relocate the session \
         into a WORKTREE; ADR-0037 (2026-08-17): the main checkout it runs in is \
         the managed one, not the unmanaged directory `tm` was typed in"
    );
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "#5274: no per-session worktree may be created without an explicit request: {}",
        expected_worktree(&base, &session_id).display()
    );
}

/// Contract row 2 — a project registered `worktree: false` also runs its
/// session in the main checkout (#5274, preserving #4300).
///
/// Why: rows 1 and 2 resolve the SAME way, and that identity is the contract's
/// substance — placement does not consult the project at all, rather than
/// consulting it and usually agreeing. Asserting only row 1 would leave the
/// weaker reading ("the flag still decides, we just changed its default")
/// indistinguishable from the real one. This row also keeps #4300's original
/// acceptance intact: an opted-out project gets neither a clone nor a worktree.
/// What: registers `worktree: false`, launches FROM the managed checkout with no
/// request, and asserts the session stays there with no worktree created.
///
/// ADR-0037 (2026-08-17): the launch directory here IS the managed checkout, so
/// this is the equality half of the placement rule — nothing is provisioned and
/// nothing is cloned. The paths are canonicalised because
/// `provision_for_launch` resolves the launch root through `git rev-parse
/// --show-toplevel`, which resolves the macOS `/var` → `/private/var` symlink;
/// the comparison itself is a plain equality, by owner ruling.
/// Test: itself. Green before and after by construction; it goes RED the moment
/// `provision_for_launch` provisions unconditionally, which is the mistake it
/// exists to catch.
#[tokio::test]
async fn provision_for_launch_without_a_request_uses_the_main_checkout() {
    let origin = "git@github.invalid:fixture-owner/optout-repo.git";
    let _registry = registry_with(
        "https://github.invalid/fixture-owner/optout-repo",
        Some(false),
    )
    .await;

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .canonicalize()
        .unwrap()
        .join("fixture-owner")
        .join("optout-repo");
    init_git_repo(&base);
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch(
        origin,
        &base,
        &base,
        false,
        LaunchDir::OperatorCwd,
        &session_id,
        None, // #8994: no worktree is requested, so no ledger is touched
    )
    .await
    .expect(
        "no worktree request must not attempt a clone at all — a clone error \
             here means the request was never consulted",
    );

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(base.clone()),
        "#5274 row 2: a session with no worktree request runs in the managed checkout"
    );
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "#4300: the per-session worktree must NOT exist"
    );
}

/// A launch from an UNMANAGED clone switches to the managed checkout, and the
/// unmanaged clone is not written to (ADR-0037, 2026-08-17 clarification).
///
/// Why: this is the CLI half of the defect. `provision_for_launch` ran the
/// session at `find_git_root(cwd)`, so `tm launch` from the operator's own clone
/// stayed there — no managed checkout was provisioned, and the session inherited
/// whatever configuration that tree carried. The daemon's `spawn_managed_routed`
/// had the identical bug; both now route through one rule
/// (`managed_checkout::resolve_placement_at`) so the CLI and the daemon cannot
/// resolve "the project's main checkout" two different ways.
/// What: a real git repo at the managed path (so the redirect reuses it rather
/// than cloning `.invalid`), a separate unmanaged clone as `cwd`, no request.
/// Asserts the placement is the MANAGED path and that the unmanaged clone gained
/// no `.claude` deployment.
/// Test: itself. RED before this change — it returned the unmanaged directory.
#[tokio::test]
async fn provision_for_launch_redirects_an_unmanaged_launch_to_the_managed_checkout() {
    let origin = "https://github.invalid/fixture-owner/optout-repo";

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root.path().join("fixture-owner").join("optout-repo");
    init_git_repo(&base);

    let unmanaged = tempfile::tempdir().unwrap();
    init_git_repo(unmanaged.path());
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch(
        origin,
        &base,
        unmanaged.path(),
        false,
        LaunchDir::OperatorCwd,
        &session_id,
        None, // #8994: no worktree is requested, so no ledger is touched
    )
    .await
    .expect("the managed checkout already exists, so the redirect reuses it");

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(base.clone()),
        "an unmanaged launch directory must switch to the managed checkout"
    );
    assert!(
        !unmanaged.path().join(".claude").exists(),
        "the unmanaged launch directory must never be written to: {}",
        unmanaged.path().display()
    );
}

/// Contract row 3 — an EXPLICIT request (`tm launch --worktree`) provisions the
/// base clone and the per-session worktree (#5274).
///
/// Why: rule 2 of the contract is only real if the request actually reaches the
/// filesystem. The revision of this change that preceded #5274's rewrite
/// expressed the override as a `const fn`, which no user could reach — that is
/// the failure mode this test rules out. It is also the only remaining way to
/// get a worktree from `tm launch`, so it guards the capability from being
/// quietly lost along with the default.
/// What: passes `worktree_requested = true` against a real git base clone and
/// asserts the returned variant is `Worktree` at its exact expected path AND
/// that the directory exists on disk.
/// Test: itself. RED with the request ignored (the function returns
/// `MainCheckout` and no directory is created).
#[tokio::test]
async fn provision_for_launch_explicit_request_creates_worktree() {
    let origin = "https://github.invalid/fixture-owner/isolated-repo";

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    // #7603: pin the measurement — this test asserts the `--worktree` request
    // reaches the filesystem, never anything about the host's volume.
    let workspace = provision_for_launch_gated(
        origin,
        &base,
        live.path(),
        true,
        LaunchDir::OperatorCwd,
        &session_id,
        &empty_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .unwrap();

    let expected = expected_worktree(&base, &session_id);
    assert_eq!(
        workspace,
        ManagedWorkspace::Worktree(expected.clone()),
        "#5274 row 3: an explicit `--worktree` request must provision one"
    );
    assert!(
        expected.is_dir(),
        "the worktree directory must exist at {}",
        expected.display()
    );
}

/// `tm launch` from a SUBDIRECTORY must deploy into the repo ROOT, never the
/// subdirectory.
///
/// Why (code-critic MEDIUM on PR #4303): `get_origin_url` succeeds at any depth,
/// so `cd repo/src && tm launch` passed `cwd` straight through as the workspace
/// — `.claude`, the project hooks, the tmux cwd and the daemon's `project_path`
/// all landed in `repo/src`. #5274 makes the main-checkout branch the DEFAULT
/// rather than a rarely-taken opt-out, so every launch now runs through this
/// resolution and the consequence of getting it wrong went from two projects to
/// all of them. The daemon-unreachable fallback already resolved the root via
/// `classify_cwd_project`; this pins that `tm launch` agrees.
/// What: builds a REAL git repo AT THE MANAGED PATH (so `git rev-parse
/// --show-toplevel` has a root to find and the placement rule's equality branch
/// applies), calls `provision_for_launch` with a nested subdirectory as `cwd`,
/// and asserts the returned workspace is the repo root and explicitly NOT the
/// subdirectory.
/// Test: itself. RED if `provision_for_launch` passes `cwd` through.
#[tokio::test]
async fn provision_for_launch_from_subdirectory_targets_repo_root() {
    let origin = "https://github.invalid/fixture-owner/optout-repo.git";

    // The managed checkout IS the launch tree here, so the subject stays root
    // resolution rather than the redirect (covered by its own test above).
    // Canonical because `find_git_root` resolves the macOS `/var` symlink.
    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .canonicalize()
        .unwrap()
        .join("fixture-owner")
        .join("optout-repo");
    init_git_repo(&base);
    let subdir = base.join("crates").join("inner");
    std::fs::create_dir_all(&subdir).unwrap();
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch(
        origin,
        &base,
        &subdir,
        false,
        LaunchDir::OperatorCwd,
        &session_id,
        None, // #8994: no worktree is requested, so no ledger is touched
    )
    .await
    .expect("no worktree request must not attempt a clone at all");

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(base.clone()),
        "`tm launch` from a subdirectory must target the repo ROOT"
    );
    assert_ne!(
        workspace.path(),
        subdir.as_path(),
        "the subdirectory must NEVER become the deploy target"
    );
    assert!(
        !subdir.join(".claude").exists(),
        "nothing may be provisioned inside the subdirectory: {}",
        subdir.display()
    );
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "no worktree may be created without an explicit request"
    );
}

/// A caller that already resolved placement keeps it — no redirect, no clone
/// (#5836).
///
/// Why: the redirect above is right for an operator's cwd and wrong for every
/// other caller. The guided fallback and `tm run` hand `launch()` a directory
/// they resolved themselves; re-resolving it moved the session somewhere else
/// and, when the managed checkout did not exist yet, started a clone the caller
/// had already decided against.
/// What: passes a directory that is NOT the managed checkout together with
/// `LaunchDir::CallerResolved`, against a base path that does not exist and an
/// unresolvable `.invalid` origin — so a clone attempt is both visible (the base
/// dir appears) and fatal (it fails). Asserts the passed directory comes back
/// and nothing was provisioned.
/// Test: itself. RED at 34da769f — `resolve_placement_at` ran, `ensure_base_clone`
/// tried to clone `.invalid`, and the call returned `Err`.
#[tokio::test]
async fn provision_for_launch_keeps_a_caller_resolved_placement() {
    let origin = "https://github.invalid/fixture-owner/resolved-repo";

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .canonicalize()
        .unwrap()
        .join("fixture-owner")
        .join("resolved-repo");
    // Deliberately NOT created: a redirect would have to clone it.
    let resolved = tempfile::tempdir().unwrap();
    let resolved_root = resolved.path().canonicalize().unwrap();
    init_git_repo(&resolved_root);
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch(
        origin,
        &base,
        &resolved_root,
        false,
        LaunchDir::CallerResolved,
        &session_id,
        None, // #8994: no worktree is requested, so no ledger is touched
    )
    .await
    .expect("a caller-resolved placement must not attempt a clone at all");

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(resolved_root.clone()),
        "the caller's placement must survive `provision_for_launch`"
    );
    assert_nothing_provisioned(&base);
}

// ── Path (b): the daemon-unreachable bare-`tm` fallback ─────────────────────

/// The daemon-unreachable fallback must ALSO honour the opt-out: no base
/// clone, no worktree, launch in the repo root.
///
/// Why (#4300): this path runs precisely because the daemon is down, so it can
/// never be served by the daemon's own check — the setting was silently
/// conditional on daemon liveness.
/// What: points `TRUSTY_MPM_REPOS_ROOT` at an empty temp dir, registers
/// `worktree: false`, and asserts the fallback returns the repo ROOT while the
/// repos root stays completely empty.
/// Test: itself. RED with the guard reverted (a clone + worktree appear).
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_opted_out_creates_no_clone_and_no_worktree() {
    let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
    let registry = registry_with(
        "https://github.invalid/fixture-owner/fallback-repo",
        Some(false),
    )
    .await;

    let repos_root = tempfile::tempdir().unwrap();
    let _guard = ReposRootGuard::set(repos_root.path());
    let git_root = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    // #7603: the opted-out branch returns before the gate is consulted at all,
    // so the pin here is belt-and-braces — it keeps the test's verdict
    // independent of the host even if that ordering ever changes.
    let workspace = provision_for_fallback(
        registry.path(),
        origin,
        git_root.path(),
        &session_id,
        &empty_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .expect(
        "#4300: an opted-out project must not attempt a clone at all — a clone \
             error here means the opt-out was never consulted",
    );

    assert_eq!(
        workspace,
        ManagedWorkspace::MainCheckout(git_root.path().to_path_buf()),
        "#4300: the daemon-unreachable fallback must launch in the repo root"
    );
    assert_nothing_provisioned(
        &repos_root
            .path()
            .join("fixture-owner")
            .join("fallback-repo"),
    );
    assert_eq!(
        std::fs::read_dir(repos_root.path()).unwrap().count(),
        0,
        "#4300: the managed repos root must stay empty for an opted-out project"
    );
}

/// The fallback's default path is unchanged: an unset `worktree` still gets a
/// per-session worktree inside the protected base clone, never the live
/// checkout (#1724 invariant preserved).
///
/// Why: this is the control that proves the new guard did not turn the whole
/// fallback into a live-checkout launcher.
/// What: pre-creates a real base clone at the path `base_clone_path` resolves
/// to, registers the project with no `worktree` key, and asserts the returned
/// workspace is the worktree at its exact expected path.
/// Test: itself.
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_unset_creates_worktree_not_live_checkout() {
    let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
    let registry = registry_with("https://github.invalid/fixture-owner/fallback-repo", None).await;

    let repos_root = tempfile::tempdir().unwrap();
    let _guard = ReposRootGuard::set(repos_root.path());
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("fallback-repo");
    init_git_repo(&base);
    let git_root = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    // #7603: pin the measurement — the subject is the unset-`worktree` default,
    // not the host's disk.
    let workspace = provision_for_fallback(
        registry.path(),
        origin,
        git_root.path(),
        &session_id,
        &empty_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .unwrap();

    let expected = expected_worktree(&base, &session_id);
    assert_eq!(
        workspace,
        ManagedWorkspace::Worktree(expected.clone()),
        "an unset `worktree` key must still redirect to a protected worktree"
    );
    assert!(
        expected.is_dir(),
        "worktree must exist at {}",
        expected.display()
    );
    assert!(
        !git_root.path().join(".worktrees").exists(),
        "#1724: nothing may be provisioned inside the live checkout"
    );
}

/// Another project's opt-out must not leak onto an unrelated origin on the
/// fallback path either — the registry lookup is keyed by `repo_url`.
///
/// Why: the fallback reads the SAME 33-entry registry, only two of whose
/// entries carry `worktree: false`; a lookup that matched too eagerly would
/// strip isolation from every other project when the daemon happens to be down.
/// What: registers only `bobmatnyc/writing` as opted out, then provisions for
/// `bob-duetto/cto` and asserts the worktree IS created.
/// Test: itself.
#[tokio::test]
#[serial_test::serial]
async fn provision_for_fallback_other_projects_optout_does_not_leak() {
    let registry = registry_with(
        "https://github.invalid/fixture-owner/optout-repo",
        Some(false),
    )
    .await;

    let repos_root = tempfile::tempdir().unwrap();
    let _guard = ReposRootGuard::set(repos_root.path());
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("fallback-repo");
    init_git_repo(&base);
    let git_root = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    // #7603: pin the measurement — the subject is registry keying, not disk.
    let workspace = provision_for_fallback(
        registry.path(),
        "https://github.invalid/fixture-owner/fallback-repo.git",
        git_root.path(),
        &session_id,
        &empty_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .unwrap();

    let expected = expected_worktree(&base, &session_id);
    assert_eq!(
        workspace,
        ManagedWorkspace::Worktree(expected.clone()),
        "an unrelated project's opt-out must not disable isolation here"
    );
    assert!(
        expected.is_dir(),
        "worktree must exist at {}",
        expected.display()
    );
}

// ── The #7603 injection itself ───────────────────────────────────────────────
//
// The three tests above now pin their measurement, which is only trustworthy if
// pinning still gates. These two prove it does in the refusing direction, and
// the third proves the gated path stops consulting the host at all.

/// A pinned OVER-threshold measurement still refuses a `tm launch --worktree`
/// (#7603).
///
/// Why: `DiskGate::Pinned` would be an off switch rather than an injection if a
/// pinned value could only ever allow. The gate has deliberately none (#7497),
/// and this change must not introduce one — the threshold still comes from the
/// operator's config and a pinned 100% is at or above every value it accepts.
/// What: the same fixture as
/// `provision_for_launch_explicit_request_creates_worktree`, with [`full_disk`]
/// instead of [`empty_disk`]. Asserts the call fails, that the refusal names the
/// gate, and that NO worktree directory was created.
/// Test: this is the test. RED if `Pinned` were ever taken as "skip the gate".
#[tokio::test]
async fn a_pinned_over_threshold_gate_refuses_a_launch_worktree() {
    let origin = "https://github.invalid/fixture-owner/isolated-repo";

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    let err = provision_for_launch_gated(
        origin,
        &base,
        live.path(),
        true,
        LaunchDir::OperatorCwd,
        &session_id,
        &full_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .expect_err("a pinned 100% mount is at or above every accepted threshold");

    let message = err.to_string();
    assert!(
        message.contains("refusing to create a worktree")
            && message.contains("/fixture-mount")
            && message.contains("disk.max_usage_pct"),
        "#7603: the refusal must be the disk gate's own, naming the PINNED \
         mount — got: {message}"
    );
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "#7497: a refused gate must create nothing"
    );
}

/// #8994 criterion 1 on the CLI route: a ledger `tm launch --worktree` cannot
/// append to refuses the launch, and no worktree is created.
///
/// What: the ledger path under the scratch home is a directory, so the append
/// open fails for any user. Asserts `Err`, a message naming the ledger, no
/// worktree directory, and nothing recorded.
/// Test: this is the test. RED if the append error were logged and swallowed.
#[tokio::test]
async fn a_launch_worktree_is_refused_when_the_ledger_cannot_record() {
    let origin = "https://github.invalid/fixture-owner/isolated-repo";
    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();
    let home = tempfile::tempdir().unwrap();
    let ledger = trusty_mpm::core::worktree_ledger::WorktreeLedger::under_home(home.path());
    std::fs::create_dir_all(ledger.path()).unwrap();

    let err = provision_for_launch_gated(
        origin,
        &base,
        live.path(),
        true,
        LaunchDir::OperatorCwd,
        &session_id,
        &empty_disk(),
        Some(home.path()),
    )
    .await
    .expect_err("an unwritable ledger must refuse the launch worktree");

    assert!(err.to_string().contains("worktree ledger"), "{err}");
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "#8994: nothing may be created when the ledger cannot record it"
    );
}

/// #8994: `tm launch --worktree` records the tree it creates.
/// Test: this is the test.
#[tokio::test]
async fn a_launch_worktree_is_recorded_in_the_ledger() {
    use trusty_mpm::core::worktree_ledger::{Origin, WorktreeLedger, fold::fold, ledger_key};
    let origin = "https://github.invalid/fixture-owner/isolated-repo";
    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();
    let home = tempfile::tempdir().unwrap();

    provision_for_launch_gated(
        origin,
        &base,
        live.path(),
        true,
        LaunchDir::OperatorCwd,
        &session_id,
        &empty_disk(),
        Some(home.path()),
    )
    .await
    .expect("provision");

    let state = fold(
        &WorktreeLedger::under_home(home.path())
            .read()
            .unwrap()
            .events,
    );
    let entry = &state.live[&ledger_key(&expected_worktree(&base, &session_id))];
    assert_eq!(entry.origin, Origin::TmCli);
    assert_eq!(
        entry.session.as_deref(),
        Some(session_id.to_string().as_str())
    );
}

/// A pinned OVER-threshold measurement still refuses on the fallback path
/// (#7603).
///
/// Why: the two entry points must not drift into disagreeing about the gate —
/// the same reason `check_measured` and `bash_refusal` share one threshold.
/// What: the `provision_for_fallback_unset_creates_worktree_not_live_checkout`
/// fixture with [`full_disk`]; asserts the refusal and that neither the base
/// clone's `.worktrees` nor the live checkout gained anything.
/// Test: this is the test.
#[tokio::test]
#[serial_test::serial]
async fn a_pinned_over_threshold_gate_refuses_a_fallback_worktree() {
    let origin = "https://github.invalid/fixture-owner/fallback-repo.git";
    let registry = registry_with("https://github.invalid/fixture-owner/fallback-repo", None).await;

    let repos_root = tempfile::tempdir().unwrap();
    let _guard = ReposRootGuard::set(repos_root.path());
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("fallback-repo");
    init_git_repo(&base);
    let git_root = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    let err = provision_for_fallback(
        registry.path(),
        origin,
        git_root.path(),
        &session_id,
        &full_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .expect_err("a pinned 100% mount must refuse on the fallback path too");

    assert!(
        err.to_string().contains("refusing to create a worktree"),
        "#7603: expected the disk gate's refusal, got: {err}"
    );
    assert!(
        !expected_worktree(&base, &session_id).exists(),
        "#7497: a refused gate must create nothing"
    );
    assert!(
        !git_root.path().join(".worktrees").exists(),
        "#1724: the live checkout must stay untouched on a refusal too"
    );
}

/// A pinned gate reads neither the host's mount nor the operator's config
/// (#7603).
///
/// Why: this is the property the whole change exists for, and it is not implied
/// by the two refusal tests above — those would also pass if `Pinned` merely
/// *added* a second measurement beside the real one. The verdict must come from
/// the pinned value ALONE, so that a host at 99% and a host at 1% agree.
/// What: pins 0.0% for a worktree whose path is a real directory on this
/// machine's volume, and asserts it is created. On a host at or above the
/// operator's `disk.max_usage_pct` the unpinned path refuses this exact call —
/// which is the six-test red the issue reports.
/// Test: this is the test. RED at 53f95234: `provision_for_launch` measured the
/// real mount and refused with "/simulated-full-volume is at 99.0% disk usage,
/// at or above the disk.max_usage_pct threshold of 90%".
#[tokio::test]
async fn a_pinned_gate_keeps_a_launch_worktree_off_the_hosts_disk() {
    let origin = "https://github.invalid/fixture-owner/isolated-repo";

    let repos_root = tempfile::tempdir().unwrap();
    let base = repos_root
        .path()
        .join("fixture-owner")
        .join("isolated-repo");
    init_git_repo(&base);
    let live = tempfile::tempdir().unwrap();
    let session_id = ManagedSessionId::new();

    let workspace = provision_for_launch_gated(
        origin,
        &base,
        live.path(),
        true,
        LaunchDir::OperatorCwd,
        &session_id,
        &empty_disk(),
        Some(tempfile::tempdir().unwrap().path()), // #8994: scratch ledger home
    )
    .await
    .expect(
        "#7603: a pinned 0% measurement must decide this call, whatever the \
         host's volume and the operator's disk.max_usage_pct say",
    );

    assert_eq!(
        workspace,
        ManagedWorkspace::Worktree(expected_worktree(&base, &session_id)),
        "the pinned measurement must be the one the gate applied"
    );
}