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
//! Client-side orphan-daemon check for `tm doctor` (issue #4230).
//!
//! Why: every existing daemon probe treats a 200 from `/health` as proof the
//! stack is fine. #4230 is the case where that is exactly wrong: an orphaned
//! 1.0.2 daemon (PID 98606, PPID 1, `cwd=$HOME`) held :7880 for two days while
//! launchd's `com.trusty.mpm` reported `state = not running`, answering every
//! request successfully. A fresh signed install plus behavioural verification
//! all passed against a binary the install had just replaced, and `launchctl
//! bootout` was a silent no-op because launchd did not own the listener. A check
//! that cannot fail when the thing it checks is broken has no value.
//!
//! What: [`orphan_daemon_check`] compares WHO ANSWERED against WHO LAUNCHD RUNS.
//! Those two facts come from independent sources — `/health`'s `pid` and
//! `launchctl list <label>` — so neither the daemon nor launchd alone can make
//! the check pass.
//!
//! The `supervised` self-report is a FALLBACK, not the primary signal (#4230
//! review). It was an environment-variable heuristic — an `XPC_SERVICE_NAME`
//! probe with a `getppid() == 1` fallback, both of which a non-launchd process
//! satisfies — until #4469 replaced it with an authoritative `launchctl` query.
//! It is STILL not the primary signal here, for two reasons that outlive that
//! fix: the flag is computed once at daemon startup, and the responding daemon
//! may be an older build whose flag is the old heuristic. A detector resting on
//! a self-report is blind to exactly the population it targets, however that
//! self-report is derived.
//!
//! This lives in the `tm` CLI binary rather than the daemon's server-side
//! `run_doctor` for the same reason as the #2332 stale-daemon check
//! (`doctor_stale`): the answer depends on state outside the responding process.
//! Only a client can compare "who answered" against "who launchd was told to
//! run", and in the #4230 state the daemon that answers is precisely the one
//! whose self-report cannot be trusted.
//!
//! Cost: one `launchctl list` invocation plus a `read_dir`, on top of the
//! `/health` snapshot the caller already fetched and passes in. No spawn, no port
//! scan, no network.
//!
//! Test: `crates/trusty-mpm/src/bin/tm/commands/doctor_orphan_tests.rs`.
use HealthSnapshot;
use ;
use cratelaunchd_probe;
use crateLaunchdOwnership as Ownership;
/// Name of this check as it appears in `tm doctor` output.
pub const CHECK_NAME: &str = "daemon_orphan";
/// Assemble the #4230 orphan check from the caller's `/health` snapshot.
///
/// Why: the snapshot is passed in rather than re-fetched (#4230 review, LOW-1) —
/// `commands::misc::doctor` already needs it for the #2332 staleness check, and
/// issuing a second GET both contradicted this module's own doc and sampled the
/// daemon twice, which could straddle a restart.
/// What: resolves the registered daemon launchd label and, when there is one,
/// asks launchd which PID it currently runs for it; delegates the decision to the
/// pure [`verdict`]. `None` (the caller's fetch failed) yields `Unknown` — an
/// unreachable daemon is explained in detail by the report's own checks, and an
/// undetermined check must not read as a pass (#4005 precedent). This is also what
/// keeps the documented `bootout → install → bootstrap` window from producing a
/// spurious `Fail`.
/// Test: the decision table is covered by `doctor_orphan_tests.rs`; the two live
/// probes are thin wrappers tested in `launchd_probe`.
pub
/// Pure verdict: is the daemon answering `/health` an unsupervised orphan?
///
/// Why: separated from [`orphan_daemon_check`] so every branch — above all the
/// FAIL branches — is directly testable. A test that could only call the wrapper
/// would depend on the host's real `~/Library/LaunchAgents` and its real daemon,
/// so it would assert the happy path and silently lose the regression guard: the
/// same shape of blind spot #4230 is about.
/// What: a three-tier decision, strongest evidence first.
///
/// 1. No daemon launchd unit registered → `Ok`. A bare dev daemon is the expected
/// arrangement, and the supervisor plist does not count (see
/// [`launchd_probe::daemon_launchd_label_in`]).
/// 2. AUTHORITATIVE — the responding daemon reports its pid, and launchd gave a
/// POSITIVE answer. Equal pids → `Ok`; different → orphan. `NotRunning` while
/// something answers is the #4230 state verbatim: the job is down, yet the
/// port is served.
/// 3. FALLBACK — the responding daemon predates `pid`. Then and only then does the
/// `supervised` self-report decide: `Some(false)` → orphan (the daemon agrees
/// it is hazardous); `Some(true)` or `None` → `Unknown`, never `Ok`. A daemon
/// old enough to omit `pid` also predates #4469, so its `true` came from the
/// `getppid() == 1` prong that any reparented orphan satisfies.
///
/// Two guards keep the hard `Fail` — whose remediation prescribes `kill -TERM` —
/// off a healthy daemon (#4230 review round 2):
///
/// * [`launchd_probe::LaunchdOwnership::Unavailable`] never produces `Fail`. "I
/// could not ask launchd" is an absence of evidence, and the round-1 error was
/// exactly this mistake in the other direction. Verified live: with `launchctl`
/// off `PATH`, the previous code condemned the daemon it had just called `Ok`.
/// * `NotRunning` escalates to `Fail` only when `supervised != Some(true)`. When
/// launchd says the job is down and the daemon says it is supervised, the two
/// authorities contradict and neither can be acted on destructively. This does
/// not weaken the incident detection: the actual #4230 payload reported
/// `supervised: false`, so it still lands on `Fail`, and a lying daemon can only
/// reach `Unknown` — never `Ok`.
///
/// Test: `authoritative_fail_when_launchd_runs_a_different_pid`,
/// `authoritative_fail_when_launchd_runs_nothing_but_a_daemon_answers`,
/// `authoritative_ok_when_launchd_owns_the_responding_pid`,
/// `ok_when_no_launchd_unit_is_registered`,
/// `fallback_fail_when_old_daemon_reports_unsupervised`,
/// `fallback_unknown_when_old_daemon_claims_supervised`,
/// `fallback_unknown_when_supervised_is_absent`,
/// `warns_not_fails_when_the_operator_forced_it`,
/// `authoritative_verdict_ignores_a_lying_self_report`,
/// `failure_names_the_pid_lookup_the_kill_and_the_launchctl_restart`,
/// `failure_names_the_force_opt_in`, `failure_names_the_serving_version`,
/// `failure_is_a_hard_fail_not_a_warn`, `unknown_version_is_labelled_not_blank`,
/// `remediation_names_the_resolved_label`,
/// `unavailable_launchctl_is_unknown_not_a_fail`,
/// `unavailable_launchctl_never_prescribes_a_kill`,
/// `unavailable_message_names_the_domain_cause`,
/// `not_running_but_supervised_is_unknown_not_a_kill_order`,
/// `not_running_with_supervised_false_is_still_a_hard_fail`,
/// `unavailable_with_an_old_daemon_reporting_unsupervised_still_fails`.
pub
/// Build the `Ok` variant of this check.
///
/// Why: the two `Ok` paths would otherwise repeat the name/status pair.
/// What: a [`CheckStatus::Ok`] [`DoctorCheck`] carrying `message`.
/// Test: covered via `ok_when_no_launchd_unit_is_registered` and
/// `authoritative_ok_when_launchd_owns_the_responding_pid`.
/// Build the `Unknown` verdict for a daemon too old to be matched against launchd.
///
/// Why: `Unknown` is the correct third state for "this daemon cannot tell me" —
/// the reason the client models `supervised` as `Option<bool>`. Reporting `Ok`
/// here is what would inherit the self-report weakness the docs half of this
/// change argues against. Both fallback branches share the remediation, so they
/// share one builder.
/// What: `Unknown` naming the version, the registered label, the branch-specific
/// `reason`, and the restart command that makes the daemon report its pid.
/// Test: `fallback_unknown_when_old_daemon_claims_supervised`,
/// `fallback_unknown_when_supervised_is_absent`.
/// Build the `Unknown` verdict for "launchd could not be asked" (#4230 review
/// round 2).
///
/// Why: this is the branch the round-2 review found prescribing a DESTRUCTIVE
/// remediation on a false reading. `launchctl` not answering is not evidence that
/// launchd runs nothing — it happens with a scrubbed `PATH`, in a sandbox, and
/// when the unit is not visible in the caller's bootstrap domain. Since
/// `KeepAlive {SuccessfulExit: false}` means the job will not come back on its
/// own, telling the operator to `kill -TERM` a correctly supervised daemon here
/// costs them the daemon. `--force` is no escape either: it would relabel a
/// supervised daemon as a deliberate unsupervised one.
/// What: `Unknown`, naming the pid that answered, the unit, why launchd may have
/// declined, and a MANUAL comparison the operator can make. Contains no
/// `kill`/`-TERM` instruction by construction — asserted by a test.
/// Test: `unavailable_launchctl_is_unknown_not_a_fail`,
/// `unavailable_launchctl_never_prescribes_a_kill`,
/// `unavailable_message_names_the_domain_cause`.
/// Build the `Unknown` verdict when launchd and the daemon flatly contradict each
/// other (#4230 review round 2).
///
/// Why: launchd positively reporting the job down while the responding daemon
/// positively claims supervision cannot both be true. It is a real hazard shape,
/// but the safe response is to surface the contradiction, not to prescribe a kill:
/// if the daemon is right, the kill destroys a healthy supervised process. The
/// round-1 closure is preserved because the actual #4230 payload reported
/// `supervised: false` and so never reaches here, and because this state can only
/// ever reach `Unknown` — never `Ok`.
/// What: `Unknown` naming both claims and the non-destructive way to settle them.
/// Test: `not_running_but_supervised_is_unknown_not_a_kill_order`.
/// Build the orphan verdict, respecting the `--force` opt-in.
///
/// Why: the three orphan paths differ only in the EVIDENCE sentence; the
/// consequence, the remediation, and the `forced` downgrade are identical, so they
/// get one source of truth rather than three near-copies.
/// What: `Warn` when `forced` (a deliberate unsupervised run), else `Fail`. The
/// failure text names the consequence, then the `lsof` PID lookup, the
/// `kill -TERM`, and the label-correct restart command, in the order an operator
/// must run them.
/// Test: `warns_not_fails_when_the_operator_forced_it`,
/// `failure_is_a_hard_fail_not_a_warn`,
/// `failure_names_the_pid_lookup_the_kill_and_the_launchctl_restart`,
/// `failure_names_the_force_opt_in`, `remediation_names_the_resolved_label`.
/// Render a `/health` version for an operator-facing message.
///
/// Why: a daemon predating #2332 omits `version`, which arrives here as an empty
/// string; printing `version ` with nothing after it reads as a formatting bug and
/// hides that the build is unidentified — the exact ambiguity #2332 fixed.
/// What: returns the version unchanged, or `"unknown"` when it is empty.
/// Test: `unknown_version_is_labelled_not_blank`.