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
//! Which tree a directory launch runs in: the managed checkout, always.
//!
//! Why: ADR-0037 decided that a PM session with no explicit worktree request
//! runs on "the project's main checkout". That phrase names the MANAGED
//! checkout — `<workspace-root>/<owner>/<repo>`, managed because tm provisions
//! it for the user in a set location — and never the directory the operator
//! happened to be standing in when they typed `tm`. `spawn_managed_routed`
//! resolved it to the launch directory instead, so a launch from an unmanaged
//! clone ran the session there: no managed checkout was provisioned, and the
//! session inherited whatever configuration that tree already carried. This
//! module is the resolution ADR-0037 always described.
//!
//! **The unmanaged tree is never written to.** Redirecting a launch does not
//! clone over, modify, or migrate the directory the operator launched from —
//! that tree is simply not where the session runs. There is no migration path
//! here because there is no migration.
//!
//! This module also owns [`deny_worktree_fallback`], the other half of the same
//! rule: a placement the operator asked for explicitly is never quietly traded
//! for a different one.
//!
//! Test: `managed_checkout_tests.rs`.
use ;
use ;
use GithubPath;
use crateManagedSessionId;
/// The managed checkout for a project identity.
///
/// Why: routes through `inproject::base_clone_path` rather than computing the
/// join here, so the redirect target and the base clone the in-project spawn
/// path establishes can never drift onto two different directories.
/// What: `<workspace-root>/<owner>/<repo>`.
/// Test: `managed_checkout_is_the_base_clone_path`.
pub
/// Resolve where a directory launch runs, provisioning the managed checkout
/// when it is absent.
///
/// Why: the one place ADR-0037's "the project's main checkout" is turned into a
/// directory. Keeping it in one function is what stops the daemon spawn path and
/// any future caller from resolving the same phrase two different ways.
///
/// What: compares `launch_dir` to the managed checkout built from `gh`. Equal —
/// the launch is already managed, so it runs there unchanged. Not equal — the
/// session runs in the managed checkout instead, cloned first via
/// `inproject::ensure_base_clone` when it does not yet exist. The comparison is
/// a plain path equality because tm constructs the managed path itself from
/// workspace-root + owner + repo, so both sides are deterministic.
///
/// **A provisioning failure is an error, never a fallback.** The session must
/// not start in the unmanaged tree because the clone failed — that is a failure
/// reported as success, and it is how a session ends up running somewhere the
/// control plane never established control over.
/// Test: `managed_launch_dir_is_left_alone`,
/// `unmanaged_launch_dir_redirects_to_managed`,
/// `provisioning_failure_is_an_error_not_a_fallback`.
pub
/// [`resolve_placement`] against a managed checkout the caller already holds.
///
/// Why: `tm launch` decides placement in-process, without the daemon, and its
/// `managed_workspace::provision` had the same defect this module fixes — it
/// ran the session at `find_git_root(cwd)`. That call site is already handed
/// `inproject::base_clone_path(owner, repo)` as `base_path`, so it needs the
/// RULE, not the derivation. Two implementations of one rule is how the CLI and
/// the daemon end up disagreeing about where a session runs, which is what
/// `provision_for_launch`'s own doc warns against.
/// What: see [`resolve_placement`]. `managed` is trusted to be
/// `<workspace-root>/<owner>/<repo>` — this function does not re-derive it.
/// Test: same cases as [`resolve_placement`], which delegates here.
/// Turn a failed in-project spawn into an error when a worktree was REQUESTED,
/// and into the historical warn-and-fall-back only when it was not.
///
/// Why: `spawn_managed_routed` handled both in-project failures — base-clone
/// establishment and worktree reservation — by logging and continuing into
/// `spawn_managed_local`, which spawns a session with no worktree at all. A
/// launch that asked for an isolated worktree then reported success while
/// running somewhere the operator never chose, and the only trace was a `warn!`
/// nobody reads. An explicit request that cannot be honoured is a failure, and
/// the caller has to see it as one.
///
/// What: `worktree_requested == true` returns `Err`, naming the session and the
/// underlying cause, so the caller's `?` aborts the spawn. `false` keeps the
/// pre-existing behaviour exactly — `warn!` and `Ok(())`, letting the caller
/// fall through to the local-path branch. That branch is still correct there:
/// with no worktree asked for and no usable in-project base, `spawn_managed_local`
/// provisions its own managed clone.
/// Test: `deny_worktree_fallback_errors_when_a_worktree_was_requested`,
/// `deny_worktree_fallback_permits_the_fallback_when_none_was_requested`, and
/// the end-to-end `tests/worktree_request_fail_closed.rs`.
pub