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
//! Advisory locking + bare-repo detection for `RealGitBackend::ensure_base_checkout`.
//!
//! Why: split out of `workspace.rs` (which grew past the 500-SLOC production
//! cap once trusty-review's PR #1936 / #1935 findings were fixed) to keep
//! both files under the mechanical `scripts/check_line_cap.sh` gate. Grouping
//! these items together here — rather than trimming their Why/What/Test
//! documentation — keeps the fix's rationale fully documented in place.
//! What: [`is_established_bare_checkout`] replaces the fragile
//! `HEAD.is_file()` idempotency probe (review finding #2) with a real
//! `git rev-parse --is-bare-repository` check; [`acquire_base_checkout_lock`]
//! (backed by [`BaseCheckoutLock`], a dependency-free marker-file mutex) plus
//! [`lock_is_stale`] serialize the check-and-clone window across concurrent
//! callers to fix the TOCTOU provisioning race (review finding #1).
//! [`stale_base_dir_error`] turns the "the base path is occupied by a broken,
//! non-bare directory" state into an actionable operator error (issue #1937
//! item 1), and is shared by BOTH backends; [`fake_is_established_bare_checkout`]
//! / [`write_fake_bare_checkout`] let `FakeGitBackend` mirror the real backend's
//! `rev-parse`-based validity semantics rather than a superficial file-exists
//! probe (issue #1937 item 3).
//! [`stale_base_quarantine_path`] backs that error's non-destructive recovery
//! hint (issue #3605: the hint used to be a literal `rm -rf`).
//! Test: `ensure_base_checkout_recovers_from_concurrent_race`,
//! `ensure_base_checkout_rejects_stale_non_bare_directory`,
//! `base_checkout_lock_recovers_stale_lock_marker`,
//! `fake_ensure_base_checkout_rejects_stale_non_bare_directory`,
//! `stale_base_dir_error_suggests_quarantine_not_deletion`, all in
//! `provisioner/workspace/tests.rs`.
use ;
use ProvisionError;
/// Determine whether `dir` is an already-established BARE git checkout.
///
/// Why: the previous idempotency guard for [`super::RealGitBackend`]'s
/// `ensure_base_checkout` (`base_dir.join("HEAD").is_file()`) only checks for
/// a file literally named `HEAD` at the root of `base_dir` — it never
/// confirms that directory is actually a valid, complete git repository. A
/// directory that merely contains a stray `HEAD` file (e.g. left behind by a
/// `git clone --bare` that crashed mid-clone — git writes `HEAD` early,
/// before the rest of the object database/refs — or any other stale/corrupt
/// artifact) would pass the old check and be silently mistaken for an
/// established shared base (trusty-review finding #2 on PR #1936 / #1935).
/// `git rev-parse --is-bare-repository` is the canonical way git itself
/// answers this question, so it cannot be fooled by directory layout alone.
/// This helper is also reused to resolve the TOCTOU provisioning race
/// (finding #1): after acquiring [`acquire_base_checkout_lock`], re-running
/// this check tells the caller whether a concurrent caller already finished
/// establishing a valid base while this one was waiting.
/// What: shells out to `git -C dir rev-parse --is-bare-repository` and
/// returns `true` only when the command exits successfully AND stdout trims
/// to exactly `"true"`. Returns `false` (never panics) if the subprocess
/// fails to spawn, exits non-zero (not a git repository at all), or prints
/// anything else (e.g. `"false"` for a non-bare repo).
/// Test: `ensure_base_checkout_recovers_from_concurrent_race`,
/// `ensure_base_checkout_rejects_stale_non_bare_directory`.
pub
/// Build the timestamped sibling path a stale base directory should be
/// QUARANTINED to (moved aside), rather than deleted.
///
/// Why: the recovery hint in [`stale_base_dir_error`] is read and executed
/// verbatim by autonomous agents, so it must name a concrete destination —
/// an agent handed `mv <path> <somewhere-you-choose>` will improvise, and an
/// agent handed a shell-substitution (`$(date …)`) may mangle it. Emitting a
/// fully-resolved path keeps the suggested command copy-pasteable while
/// keeping it non-destructive. The timestamp suffix means repeated recoveries
/// never collide, so an earlier quarantine is never clobbered by a later one.
/// What: `<base_dir>.stale-<UTC YYYYmmdd-HHMMSS>`, a sibling of `base_dir`
/// (same parent, so the `mv` is a cheap same-filesystem rename).
/// Test: `stale_base_dir_error_suggests_quarantine_not_deletion`.
pub
/// Build an actionable error for a base-checkout path occupied by a broken,
/// non-bare directory — or return `None` when the path is safe to clone into.
///
/// Why: `ensure_base_checkout` correctly refuses to reuse a `<project_dir>/.base`
/// that is not a valid bare checkout (a leftover from a crashed mid-clone, or a
/// stale non-bare directory), but the resulting failure used to be an opaque
/// `git clone --bare` "destination path already exists" message that gives the
/// operator no recovery path (issue #1937 item 1). This error is, however,
/// consumed almost exclusively by an autonomous agent, which executes a
/// suggested command verbatim — so the message's own recovery hint is a
/// privileged instruction channel, not advice. The original hint was a literal
/// `rm -rf <path>` and is the most plausible trigger of the 2026-07-21 incident
/// (issue #3605) in which `<project>/.base` was destroyed and ~70 worktrees were
/// orphaned machine-wide, each then emitting phantom git-discovery test
/// failures. `.base` is SHARED by every worktree of the project, so deleting it
/// is never a local, recoverable act. The hint is therefore QUARANTINE
/// (`mv` aside to [`stale_base_quarantine_path`]) — matching this crate's
/// existing corrupt-state convention (`core::mcp_config`,
/// `core::standalone::trust_seed` both rename rather than delete) — and the
/// message states the shared-ownership blast radius explicitly, which is the
/// context whose absence made the destructive hint look safe to follow.
/// What: returns `None` when `base_dir` is absent or empty (either is safe —
/// `git clone --bare` accepts a missing or empty target); otherwise returns
/// `Some(ProvisionError::Git(..))` whose message names the exact path, warns
/// that the path is shared across sessions, and gives a single non-destructive
/// `mv <path> <path>.stale-<timestamp>` command. It never emits a recursive
/// delete. Callers MUST invoke this only AFTER confirming the path is not
/// already a valid bare checkout (via [`is_established_bare_checkout`]) so a
/// healthy base is never flagged.
/// Test: `ensure_base_checkout_rejects_stale_non_bare_directory`,
/// `fake_ensure_base_checkout_rejects_stale_non_bare_directory` (both assert
/// the message names the path and carries the quarantine hint), and
/// `stale_base_dir_error_suggests_quarantine_not_deletion` (asserts the message
/// contains NO destructive command — the #3605 regression guard).
pub
/// Determine whether `dir` is an already-established (fake) BARE checkout,
/// mirroring [`is_established_bare_checkout`]'s intent for `FakeGitBackend`.
///
/// Why: `FakeGitBackend::ensure_base_checkout` used to treat a lone
/// `dir.join("HEAD").is_file()` as proof of an established base — the exact
/// superficial file-existence probe the real backend abandoned in favour of
/// `git rev-parse --is-bare-repository` (issue #1937 item 3). A future test
/// author using the fake to simulate a stale `.base` (a stray `HEAD` file with
/// no repository structure) would have gotten a false-positive "already
/// established" pass, hiding the very stale-directory bug a real-backend test
/// would catch. This check restores that fidelity in the filesystem-light fake.
/// What: returns `true` only when BOTH a root-level `HEAD` file exists AND a
/// root-level `config` file marked `bare = true` exists — the structural
/// markers [`write_fake_bare_checkout`] writes to simulate a real bare clone. A
/// directory containing only a stray `HEAD` (the stale-mid-clone shape) fails
/// the `config` check and reads as NOT established, matching the real backend.
/// Test: `fake_ensure_base_checkout_rejects_stale_non_bare_directory`,
/// `provision_reuses_base_checkout_across_sessions`.
pub
/// Write the minimal structural markers that make a directory read as an
/// established (fake) bare checkout.
///
/// Why: `FakeGitBackend::ensure_base_checkout` must leave behind enough state
/// that [`fake_is_established_bare_checkout`] recognises it on the next call
/// (idempotent reuse across sessions) while still being distinguishable from a
/// stale directory that merely holds a stray `HEAD` file (issue #1937 item 3).
/// What: creates `dir` (and parents) and writes a `HEAD` ref pointer plus a
/// `config` file carrying the `bare = true` marker — the two files
/// [`fake_is_established_bare_checkout`] requires. Returns any I/O error as
/// [`ProvisionError::Io`].
/// Test: `provision_reuses_base_checkout_across_sessions` (second provision is a
/// no-op reuse), `fake_ensure_base_checkout_rejects_stale_non_bare_directory`.
pub
/// Maximum time a caller will wait to acquire the base-checkout lock before
/// giving up.
///
/// Why: a real clone of a large repository can legitimately take a while;
/// this must be generous enough not to spuriously fail a normal (non-racing)
/// first provision on a slow network, while still bounding how long a
/// session-spawn request can block.
/// What: 60 seconds.
/// Test: `ensure_base_checkout_recovers_from_concurrent_race` completes well
/// within this window against a local, near-instant clone.
pub const LOCK_ACQUIRE_TIMEOUT: Duration = from_secs;
/// Interval between retries while polling for the base-checkout lock.
///
/// Why: short enough that a waiting caller notices the lock's release
/// quickly, long enough not to busy-spin the CPU.
/// What: 50 milliseconds.
/// Test: covered indirectly by every test that exercises the lock.
const LOCK_POLL_INTERVAL: Duration = from_millis;
/// Age after which an existing lock marker file is treated as abandoned.
///
/// Why: the lock marker is a plain file, not a kernel-tracked `flock` — if
/// the process holding it crashes (killed, panicked, power loss) mid-clone,
/// the marker file is never cleaned up by a `Drop` impl that never ran. A
/// purely time-bound marker would otherwise deadlock every future
/// provisioning attempt for that project forever.
/// What: 5 minutes — comfortably longer than [`LOCK_ACQUIRE_TIMEOUT`] so a
/// live, slow-but-progressing clone is never mistaken for an abandoned one.
/// Test: `base_checkout_lock_recovers_stale_lock_marker`.
pub const LOCK_STALE_AFTER: Duration = from_secs;
/// Compute the sibling marker-file path used to serialize concurrent
/// `ensure_base_checkout` calls for the same `base_dir`.
///
/// Why: the lock must live NEXT TO `base_dir` (not inside it), since
/// `base_dir` itself may not exist yet when the lock is first acquired.
/// What: `<base_dir's parent>/<base_dir's file name>.lock`, e.g.
/// `<project_dir>/.base.lock` for the standard `.base` base-checkout name.
/// Test: exercised transitively by every `ensure_base_checkout` test.
pub
/// RAII guard holding the base-checkout provisioning lock.
///
/// Why: guarantees the marker file is removed when `ensure_base_checkout`
/// returns via ANY path (success, error, or early return) — a manual
/// remove-on-every-branch approach is error-prone and easy to miss on a new
/// return path added later.
/// What: wraps the locked path; `Drop` best-effort removes the marker file
/// (a failure to remove is not actionable here and must not panic in a
/// destructor).
/// Test: `ensure_base_checkout_recovers_from_concurrent_race` (the lock is
/// released promptly enough for every racing thread to eventually proceed).
pub
/// Acquire the advisory base-checkout provisioning lock at `lock_path`.
///
/// Why: resolves the TOCTOU race (#1935 review finding #1) at its root by
/// serializing the whole check-and-clone window, rather than trying to
/// detect and recover from corruption after the fact — real `git clone
/// --bare` is not safe against a concurrent writer into the same target
/// directory (empirically observed failure mode without this lock: two
/// interleaved clones can corrupt each other's partial checkout, e.g.
/// colliding on copying `hooks/commit-msg.sample`, rather than cleanly
/// failing with "already exists").
/// What: retries `OpenOptions::new().write(true).create_new(true)` — atomic
/// at the filesystem level, failing with `AlreadyExists` iff another caller
/// currently holds the lock — for up to [`LOCK_ACQUIRE_TIMEOUT`], sleeping
/// [`LOCK_POLL_INTERVAL`] between attempts. A lock file older than
/// [`LOCK_STALE_AFTER`] is treated as abandoned (left behind by a crashed
/// holder) and force-removed before retrying, so a crash mid-clone can never
/// permanently deadlock future provisioning attempts for the same project.
/// Returns a [`BaseCheckoutLock`] guard that releases the lock on drop.
/// Test: `ensure_base_checkout_recovers_from_concurrent_race`,
/// `base_checkout_lock_recovers_stale_lock_marker`.
pub
/// Determine whether the lock marker at `lock_path` is old enough to be
/// treated as abandoned by a crashed holder.
///
/// Why: split out of [`acquire_base_checkout_lock`] so the staleness policy
/// is independently testable and readable in isolation.
/// What: returns `true` iff the file's modified time is more than
/// [`LOCK_STALE_AFTER`] in the past. Returns `false` (never panics) if the
/// file vanished, its metadata can't be read, or the clock query fails —
/// treating an unreadable lock as NOT stale is the conservative, safe default
/// (it just means this caller waits and retries rather than force-clearing
/// state it can't reason about).
/// Test: `base_checkout_lock_recovers_stale_lock_marker`.
pub