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
//! POST /api/v1/sessions/managed/adopt-worktree — hand a dead owner's worktree
//! to a live session (#6497).
//!
//! Why: the policy in
//! [`crate::session_manager::worktree_adopt`] needs two liveness answers that
//! live in two different registries — a dispatched agent's in the delegation
//! map, a managed session's in the session-record store — and only the daemon
//! holds both. Answering them here keeps the policy a pure function and keeps
//! the CLI from having to re-derive liveness over several round trips and guess
//! when one of them is silent.
//!
//! What: one POST carrying the worktree `path` and the `as_session` taking it
//! over. The daemon reads the ownership sentinel, resolves the CURRENT owner's
//! liveness through whichever registry owns it, collects the agents any live
//! delegation still has working inside the tree, and runs
//! [`evaluate_adoption`](crate::session_manager::worktree_adopt::evaluate_adoption).
//! A refusal is a 409 carrying the policy's own reason; nothing is written.
//!
//! It changes the sentinel, and then — #8318 — makes the tree usable by the
//! next agent: it clears a harness lock whose pid is provably dead, and detaches
//! HEAD to free the branch when the tree is provably clean. A dirty tree keeps
//! its branch, and the response's `branch_release` says why; so does a tree
//! whose harness lock names a running pid, even when the registry reported
//! its owner dead. It moves no files,
//! commits nothing, and creates and deletes no worktree.
//!
//! #7974: when the delegation registry cannot answer — the state every daemon
//! restart leaves it in — [`lock_evidence_for`] asks git's own worktree lock
//! whether the pid it was written for still exists, and
//! [`liveness_with_lock_fallback`] admits that as death evidence. Nothing else
//! about the gate changes.
//! Test: `adopt_worktree_route_refuses_a_live_owner`,
//! `adopt_worktree_route_transfers_a_dead_owners_tree`,
//! `adopt_worktree_route_takes_a_dead_agents_tree_after_a_daemon_restart`,
//! `adopt_worktree_route_still_refuses_a_live_owner_after_a_daemon_restart`,
//! `adopt_worktree_route_frees_the_branch_and_clears_a_dead_agents_lock`,
//! `adopt_worktree_route_keeps_the_branch_while_the_lock_pid_runs`.
use ;
use Arc;
use ;
use Deserialize;
use crateRouteOutcome;
use cratedelegation_state_for_agent;
use crateDaemonState;
use crateManagedSessionId;
use crate;
use crate;
use crate;
use crate;
/// The request body: which tree, and which session takes it (#6497).
/// POST /api/v1/sessions/managed/adopt-worktree (#6497).
///
/// Test: `adopt_worktree_route_refuses_a_live_owner`,
/// `adopt_worktree_route_transfers_a_dead_owners_tree`.
pub async
/// The transport-neutral body of `POST .../managed/adopt-worktree` (#6497).
///
/// Why: mirrors [`super::reconcile::reconcile_worktrees_core`] so a socket
/// transport can serve the same verb without a second copy of the policy call.
/// What: resolves the owner's liveness, runs the pure gate, and writes the
/// sentinel only on [`AdoptionVerdict::Adopt`]. A refusal is a 409 whose body
/// is the gate's own reason, so the operator reads exactly what the guard read.
///
/// **The claimant set is read TWICE, and the second read is what the write
/// stands on.** `owner_liveness` awaits the session-manager store, so the first
/// read can be milliseconds old by the time the sentinel is written, and a
/// dispatch landing in that window records a `cwd` under the tree this call is
/// about to hand away. Re-asking immediately before the write is the two-pass
/// shape [`dirt_blocks_removal`](crate::session_manager::worktree_safety::dirt_blocks_removal)
/// already uses on the reclaim path.
///
/// It NARROWS the window rather than closing it: nothing holds a lock across
/// the check and the write, so a delegation recorded between the second read and
/// the `std::fs::write` is still missed. Closing it needs a per-path lock the
/// daemon does not have today, and the residual costs an adoption racing a
/// dispatch into the same tree — an operator-typed verb racing a dispatch, not
/// two automatic sweeps.
/// Test: `adopt_worktree_route_refuses_a_live_owner`,
/// `adopt_worktree_route_transfers_a_dead_owners_tree`,
/// `adopt_worktree_route_refuses_a_claimant_under_a_symlinked_spelling`,
/// `adopt_worktree_route_keeps_the_branch_while_the_lock_pid_runs`.
pub async
/// What the registry owning `owner` says about its liveness (#6497).
///
/// Why: the two owner shapes resolve through different registries, and only one
/// of them can distinguish "ended" from "never heard of" — so the mapping is
/// per-shape rather than one lookup with a fallback.
/// What: a dispatched agent goes through the delegation map, which
/// [`OwnerLiveness::from_agent_state`] reads with #5661's absent-vs-
/// undeterminable split; a managed session goes through the session-record
/// store's own terminal-state check, whose grace window already covers a
/// sentinel written before its record was persisted. An unreadable sentinel is
/// undeterminable and gate 1 refuses it before this answer matters.
/// Test: `adopt_worktree_route_refuses_a_live_owner`.
async
/// What the git worktree lock on `path` says about its dispatched pid (#7974).
///
/// Why here rather than in the policy module: this is the half that touches the
/// machine — one `git worktree list` and one `kill(pid, 0)` — so
/// [`liveness_with_lock_fallback`] stays a pure function of the two answers.
/// What: [`LockEvidence::Silent`] whenever any step declines to answer — git
/// unavailable, the path not registered, no harness agent lock, no pid in the
/// reason, or a pid probe that returned an errno it does not recognise. Silence
/// refuses, so every one of those is the safe direction.
/// Test: `adopt_worktree_route_takes_a_dead_agents_tree_after_a_daemon_restart`,
/// `adopt_worktree_route_still_refuses_a_live_owner_after_a_daemon_restart`.
/// The agents any LIVE delegation still has working inside `path` (#6497).
///
/// Why: the sentinel names who PROVISIONED the tree, never everyone who is in
/// it. A successor dispatched into an adopted tree, or a sibling agent that was
/// pointed at it, is a claim the sentinel cannot see — and taking the tree out
/// from under one is the same harm as taking it from its owner.
/// What: every live delegation whose recorded `cwd` is the tree or sits beneath
/// it, by agent name. A delegation with no recorded `cwd` claims nothing here.
///
/// **Both sides are compared in BOTH spellings.** A raw `starts_with` is
/// component-wise, so `/a/bc` never matches `/a/b` — but it is also purely
/// lexical, and a delegation's `cwd` and the request's `path` routinely reach
/// this function through different symlinks. On macOS every `TempDir` is under
/// `/var`, which IS `/private/var`, so one side canonicalized and the other not
/// is the ordinary case rather than the exotic one. A missed match here means
/// adoption proceeds against a tree an agent is still writing in. So each side
/// contributes its canonical form AND its raw form, and any pairing that matches
/// refuses — the same both-spellings pattern
/// [`is_live`](crate::session_manager::worktree_reclaim::is_live) uses, for the
/// same reason: a failed canonicalization may only ever spare a tree.
///
/// The containment stays one-directional on purpose. `is_live` also counts a
/// claimed path that CONTAINS the candidate, because a session standing in a
/// parent still owns it. Here that would count every delegation recorded in the
/// enclosing checkout — which is exactly the population #6497 is about — and
/// refuse every adoption in the reported scenario.
/// Test: `adopt_worktree_route_refuses_a_claimant_under_a_symlinked_spelling`,
/// `adopt_worktree_route_refuses_a_claimant_under_a_trailing_slash_spelling`,
/// `adopt_worktree_route_refuses_a_live_owner` covers the empty case.
/// A path's canonical form and its raw form (#6497).
///
/// Why: canonicalization resolves symlinks and `..`, and fails outright for a
/// path that no longer exists. Keeping the raw form alongside it means a failed
/// resolution degrades to today's lexical comparison instead of dropping the
/// path from the comparison entirely.
/// What: `[canonical, raw]`, which may be the same path twice — the caller only
/// ever asks whether ANY pairing matches, so a duplicate costs one comparison.