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
//! CLI-side managed-workspace provisioning that honours the #3455 worktree
//! opt-out (#4300).
//!
//! Why: two `tm` entry points create base clones and per-session worktrees
//! entirely IN THE CLI PROCESS — `tm launch` (which never asks the daemon)
//! and the daemon-unreachable guided fallback (which runs precisely because
//! the daemon is down). Both predate #3455's per-project `worktree: false`
//! opt-out and neither consulted it, so the setting was silently conditional
//! on the daemon being up: a project registered with `worktree: false` still
//! got a clone and a worktree from either path. Concentrating the decision
//! and the provisioning here means the opt-out is read once per launch, in
//! the SAME order the daemon uses (`managed_routes::lifecycle`, ahead of
//! `try_inproject_spawn`) — BEFORE `ensure_base_clone`, so an opted-out
//! project creates neither a worktree nor a base clone.
//! What: [`ManagedWorkspace`] names the two outcomes; [`provision`] is the
//! shared dispatcher that acts on an already-resolved decision;
//! [`provision_for_launch`] serves `tm launch` (which has already resolved
//! the base-clone path) and [`provision_for_fallback`] serves the guided
//! fallback (which parses `owner/repo` itself and reports daemon-specific
//! remediation). Each entry point reads the registry EXACTLY once, via
//! [`trusty_mpm::project::worktree_enabled_for_origin_at`], and passes the
//! answer down — so the operator-facing notice and the filesystem action can
//! never disagree.
//! Known redundancy, accepted: the daemon-unreachable fallback ends by
//! calling `launch()`, so on that composed path the registry is read twice —
//! once by [`provision_for_fallback`] and once by [`provision_for_launch`].
//! Collapsing it would mean threading a pre-resolved `ManagedWorkspace`
//! through `launch()`'s signature, a restructure that buys one avoided
//! `read_to_string` of an atomically-published file whose read path takes no
//! lock and fails open to isolation-ON. Both reads see the same bytes; the
//! only operator-visible symptom was a duplicated notice, and that is fixed
//! in [`provision_for_fallback`] directly.
//! Scope note: this deliberately does NOT change #3455's concurrency
//! behaviour. `spawn_managed_on_main` WARNS (never refuses) on a second
//! session against one main checkout, and that stays the rule here — nothing
//! in this module refuses a launch.
//! Test: `managed_workspace_tests.rs`.
use ;
use inproject;
use ManagedSessionId;
/// Where a CLI-provisioned managed session will actually run.
///
/// Why: the two outcomes have materially different lifecycles — a worktree is
/// framework-owned and disposable, the operator's main checkout is neither —
/// so callers are forced to distinguish them rather than receiving a bare
/// `PathBuf` that hides which one they got.
/// What: `Worktree` carries `<base>/.worktrees/<session-id>`; `MainCheckout`
/// carries the operator's own checkout root, returned only when the project is
/// registered with `worktree: false`.
/// Test: `managed_workspace_tests.rs`.
pub
/// Act on an already-resolved isolation decision.
///
/// Why: the opt-out check MUST run before [`inproject::ensure_base_clone`].
/// Checking afterwards would still leave a full base clone on disk for a
/// project that asked for no isolation at all — the wasted-disk complaint
/// #3455 was filed about; the daemon's `spawn_managed_routed` documents the
/// same ordering for the same reason. Taking `isolate` as a parameter (rather
/// than re-reading the registry here) guarantees the notice each caller
/// printed describes the action actually taken.
/// What: `isolate == false` returns [`ManagedWorkspace::MainCheckout`] having
/// touched nothing on disk; otherwise ensures the base clone and adds a
/// UUID-named per-session worktree. Errors are the raw `inproject` strings so
/// each caller can wrap them with its own remediation text.
/// Test: `managed_workspace_tests.rs`.
async
/// Provision the workspace for `tm launch` (#4300 call site 1).
///
/// Why: `tm launch` runs the whole provisioning flow in-process and never
/// asks the daemon, so it is the CLI path that most visibly ignored the
/// opt-out before this.
/// What: resolves the REPO ROOT of `cwd` and reads the opt-out from the
/// registry at `registry_dir`, prints the notice for whichever branch applies,
/// then delegates to [`provision`] with the caller-resolved `base_path`
/// (`inproject::base_clone_path(owner, repo)`). Errors are mapped to `tm
/// launch`'s wording.
///
/// The root resolution matters and is not incidental: `get_origin_url`
/// succeeds at ANY depth, so `cd repo/src && tm launch` on an opted-out
/// project would otherwise deploy `.claude`, the project hooks and the tmux
/// cwd into `repo/src` rather than `repo` — landing tm's furniture exactly
/// where the operator did not ask for it, which is the failure mode the
/// opt-out exists to prevent. [`super::guided::find_git_root`] is the SAME
/// helper the daemon-unreachable fallback's classifier uses, so both CLI
/// entry points cannot disagree about which directory the project is. A `cwd`
/// that is not inside a working tree (or a machine with no `git` on PATH)
/// falls back to `cwd` itself — the pre-#4300 behaviour, never worse.
/// Test: `provision_for_launch_opted_out_creates_no_clone_and_no_worktree`,
/// `provision_for_launch_from_subdirectory_targets_repo_root`,
/// `provision_for_launch_unset_creates_worktree`,
/// `provision_for_launch_unregistered_origin_creates_worktree`.
pub async
/// Provision the workspace for the daemon-unreachable guided fallback
/// (#4300 call site 2).
///
/// Why: bare `tm` with the daemon down must still never write framework files
/// into a live checkout it was not told it could (#1724) — but a project
/// registered with `worktree: false` HAS told us exactly that, and the daemon
/// would have honoured it via `spawn_managed_on_main`. Consulting the same
/// registry here is what makes the setting independent of daemon liveness.
/// What: parses `owner/repo` from `origin_url`, reads the opt-out, and
/// delegates to [`provision`] with `git_root` (the repo ROOT, not the
/// possibly-nested cwd) as the opt-out target; errors carry the fallback's
/// "start the daemon" remediation.
/// Test: `provision_for_fallback_opted_out_creates_no_clone_and_no_worktree`,
/// `provision_for_fallback_unset_creates_worktree_not_live_checkout`,
/// `provision_for_fallback_other_projects_optout_does_not_leak`.
pub async