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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
//! Canonical launchd labels for every trusty-* LaunchAgent (#4919).
//!
//! Why: there was no single definition of what a service's launchd label IS.
//! Each daemon crate declared its own `LAUNCHD_LABEL` literal, the installer
//! kept a second hand-maintained mirror of those literals
//! (`trusty-installer::commands::plist_label`, whose own doc admitted it was
//! "verified by grepping each daemon crate"), the per-crate Makefiles named a
//! third family (`com.bobmatnyc.trusty-search`), and the signed-install scripts
//! a fourth. Nothing made them agree, so they drifted — that is #2827
//! (install-mpm-signed printed `com.trusty.trusty-mpm.plist` for a daemon whose
//! plist is `com.trusty.mpm.plist`), #2965 (a docs page with yet another
//! family), and #2938 (a stale `com.trusty.trusty-search.plist` sitting beside
//! the live `com.trusty.search.plist`).
//!
//! What it cost: `trusty-search service install` wrote and bootstrapped
//! `com.trusty.trusty-search` while the unit launchd actually had loaded was
//! `com.trusty.search`. The install therefore booted out nothing, started a
//! second daemon contending for :7878 and the index locks, and left the plist
//! fixes made under #4868 (`ExitTimeOut`) sitting in a file launchd never read.
//! Re-fixing the literals one at a time is what let the defect come back after
//! #2827; this module removes the second copy instead.
//!
//! The convention: `com.trusty.<member with its `trusty-` prefix stripped>`,
//! with sub-units suffixed (`com.trusty.mpm.supervisor`,
//! `com.trusty.search.logrotate`). [`canonical_label`] is that rule as code, and
//! [`SERVICES`] is checked against it by `canonical_consts_match_the_convention`
//! — a table entry that restates a label wrongly fails the test run rather than
//! shipping.
//!
//! How far the survey actually reaches, stated exactly, because overstating a
//! partial survey is how the direction got reversed the first time: **every unit
//! with a live daemon obeys it** — `com.trusty.mpm`, `com.trusty.memory`,
//! `com.trusty.analyze`, `com.trusty.search`, `com.trusty.console`,
//! `com.trusty.agents.slack`, all confirmed from `launchctl list` with a pid.
//! Two do not, and both are being normalised onto it here rather than being
//! counted as support for it: `com.trusty.trusty-search.logrotate` is loaded
//! with no pid and no main unit beside it, and `com.trusty.trusty-review.plist`
//! sits on disk unloaded — and that file is this codebase's own output, so it
//! was never independent evidence of anything. Both are recorded as legacy
//! aliases below.
//!
//! **A retired daemon keeps its row (#6290, #6350).** [`SERVICES`] is what an
//! install WRITES; [`RETIRED_SERVICES`] is what an upgrade must CLEAR. Two rows
//! sit in the second table, both from ADR-0032: trusty-review retired its
//! daemon outright (#6290, reviews run per invocation), and trusty-analyze
//! retired its resident one (#6350, a client starts it and it exits on its own
//! idle window). Nothing installs either unit any more, but every host that ran
//! an older binary still has one loaded under `KeepAlive::Always`. Dropping a
//! row would leave that unit unnamed by anything and therefore un-evictable,
//! which is why a retirement is a MOVE between the two tables, never a
//! deletion.
//!
//! Deliberately NOT `#[cfg(target_os = "macos")]`, unlike `crate::launchd`:
//! the registry is data, and gating it would stop the drift tests from running
//! on Linux CI, which is where a divergent literal most needs to be caught.
//!
//! Not to be confused with codesign identifiers
//! (`trusty-installer::commands::macos_signing::codesign_identifier`), which
//! live in their own namespace, use the full binary name, and must NOT be
//! renamed to match — changing a codesign identifier invalidates the binary's
//! designated requirement and re-triggers macOS TCC prompts (#2558).
//!
//! Test: `canonical_consts_match_the_convention`, `sub_unit_labels_extend_their_base`,
//! `legacy_labels_are_never_canonical`, `every_legacy_label_resolves_to_one_service`,
//! `no_stray_launchd_label_literals_in_workspace_sources`.
//!
//! [`canonical_label`]: crate::launchd_labels::canonical_label
//! [`SERVICES`]: crate::launchd_labels::SERVICES
//! [`RETIRED_SERVICES`]: crate::launchd_labels::RETIRED_SERVICES
/// Reverse-DNS domain prefix shared by every trusty-* LaunchAgent.
///
/// Why: the one place the `com.trusty` vendor prefix is written down. The
/// `com.bobmatnyc.*` family the trusty-search Makefile invented is a legacy
/// alias, not a second domain.
pub const DOMAIN: &str = "com.trusty";
/// Build a launchd label from a service stem, and optionally a sub-unit name.
///
/// Why: `concat!` is the only const-evaluable string join available without a
/// new dependency, and it takes literals only. Keeping the join in a macro
/// means [`DOMAIN`]'s value is typed once even though `const` items cannot
/// call a function.
/// What: `agent!("search")` → `"com.trusty.search"`;
/// `agent!("search", "logrotate")` → `"com.trusty.search.logrotate"`.
/// The trusty-mpm daemon (`tm daemon`). Binary is `tm`, member is `trusty-mpm`
/// — the label follows the MEMBER, which is why #4059's binary-vs-member
/// confusion cannot be resolved by looking at the executable name.
pub const MPM: &str = agent!;
/// The optional unattended supervisor that restarts the mpm daemon.
pub const MPM_SUPERVISOR: &str = agent!;
/// The trusty-memory daemon.
pub const MEMORY: &str = agent!;
/// The RETIRED trusty-analyze daemon (#6350).
///
/// trusty-analyze has no resident daemon: a client starts it on demand and it
/// exits on its own idle window. This label is kept so an upgrade can EVICT the
/// unit a pre-#6350 install left loaded — it lives in [`RETIRED_SERVICES`], not
/// [`SERVICES`], and nothing writes it any more. Deleting it would strand that
/// unit on every host that ever installed the old binary, where
/// `KeepAlive::Always` restarts the process the moment it reclaims itself.
pub const ANALYZE: &str = agent!;
/// The trusty-search daemon.
///
/// #4919: was `com.trusty.trusty-search` in
/// `trusty-search::commands::service::LAUNCHD_LABEL`, which is not the label
/// launchd has loaded on any host — see this module's header.
pub const SEARCH: &str = agent!;
/// The newsyslog-driver agent that rotates trusty-search's launchd stderr log.
pub const SEARCH_LOGROTATE: &str = agent!;
/// The trusty-console dashboard daemon.
///
/// #4919: was `com.trusty.trusty-console` in code while the loaded unit is
/// `com.trusty.console` — the same divergence as [`SEARCH`], so `console
/// service status` queried a label that does not exist.
pub const CONSOLE: &str = agent!;
/// The RETIRED trusty-review daemon (#6290).
///
/// trusty-review has no daemon: reviews run per invocation. This label is kept
/// so an upgrade can EVICT the unit a pre-#6290 install left loaded — it lives
/// in [`RETIRED_SERVICES`], not [`SERVICES`], and nothing writes it any more.
/// Deleting it would strand that unit on every host that ever installed the
/// old binary, respawning a `serve` subcommand the binary no longer has.
pub const REVIEW: &str = agent!;
/// The trusty-agents Slack gateway (`tagent --slack`).
pub const AGENTS_SLACK: &str = agent!;
/// A launchd-managed service: its member name, canonical label, and the labels
/// earlier installs used for the same service.
///
/// Why: an upgrade has to evict what the PREVIOUS installer left behind, or it
/// starts a second unit beside the first (#2938). Recording the old names beside
/// the new one makes eviction derivable instead of remembered.
/// What: `member` is the workspace member / binary family the service belongs
/// to; `label` is what a fresh install writes and bootstraps; `legacy` is every
/// label a prior install of the SAME service could have registered, newest
/// first. `legacy` is never empty for a service whose label has ever changed.
///
/// #6290: the same struct describes a RETIRED service in [`RETIRED_SERVICES`],
/// where `label` reads as "the last label this service had" rather than "what a
/// fresh install writes" — a retired service has no fresh install. Everything
/// else means what it means here.
/// Every launchd service this workspace installs.
///
/// Why: `tctl`, each daemon's own `service` subcommand, the doctor checks, and
/// the drift tests all need the same answer to "which label, and what does an
/// upgrade have to evict". This is that answer.
/// Test: `canonical_consts_match_the_convention` proves no entry restates a
/// label the convention would not produce.
pub const SERVICES: & = &;
/// Services this workspace once installed and now only EVICTS (#6290, #6350).
///
/// Why: retiring a daemon is not the same as never having had one. Every host
/// that installed trusty-review before #6290 has `com.trusty.review` loaded,
/// pointed at a `serve` subcommand the binary no longer has; every host that
/// installed trusty-analyze before #6350 has `com.trusty.analyze` loaded,
/// restarting the process the moment its idle window reclaims it. Both units
/// carry `KeepAlive::Always`, so both respawn forever. Deleting a row would
/// leave that unit unnamed by anything, so nothing could boot it out; keeping
/// it in [`SERVICES`] would keep an install WRITING it. This table is the third
/// answer: named, so it can be evicted; separate, so it is never installed.
///
/// What: the same [`Service`] shape, read as "the labels an upgrade must clear
/// for this member" — `label` plus every entry in `legacy`.
/// [`retired_labels_for_member`] flattens the two.
///
/// A row moves here the moment its daemon is retired and stays forever: the
/// population of hosts carrying a stale unit only ever grows more diffuse, and
/// an eviction that costs one `launchctl bootout` against a label that is not
/// loaded costs nothing at all.
///
/// Test: `retired_services_are_not_installed`,
/// `retired_review_carries_both_its_labels`,
/// `retired_analyze_carries_both_its_labels`.
pub const RETIRED_SERVICES: & = &;
/// Derive a member's canonical main-daemon label from the convention.
///
/// Why: the convention has to exist as executable code, or "the convention"
/// degrades into whatever the literals happen to say — which is how
/// `com.trusty.trusty-search` survived four issues.
/// What: strips a leading `trusty-` from `member` and prefixes [`DOMAIN`].
/// A member that is already bare (`"mpm"`) is left alone, so the function is
/// idempotent on stems.
/// Test: `canonical_consts_match_the_convention` runs it over every
/// [`SERVICES`] entry.
/// Derive a sub-unit's label from its base label.
///
/// What: `sub_label("com.trusty.search", "logrotate")` →
/// `"com.trusty.search.logrotate"`.
/// Test: `sub_unit_labels_extend_their_base`.
/// Look up a service by its canonical label.
///
/// What: returns the [`SERVICES`] or [`RETIRED_SERVICES`] entry whose `label`
/// matches, or `None`.
///
/// #6290, #6350 — why retired rows are searched too: the eviction of a retired
/// unit needs its `legacy` list exactly as an install needs a live one's, and
/// `com.trusty.review` and `com.trusty.analyze` are still labels this workspace
/// names. Excluding them would make [`legacy_labels_for`] return empty, so an
/// upgrade would boot out the canonical unit and leave the `com.trusty.trusty-*`
/// alias loaded beside it — #2938's two-units-one-service shape, arrived at from
/// the other direction.
/// Test: `every_legacy_label_resolves_to_one_service`,
/// `retired_review_carries_both_its_labels`,
/// `retired_analyze_carries_both_its_labels`.
/// The retired service registered for `member`, if any.
///
/// What: the [`RETIRED_SERVICES`] entry whose `member` matches. `None` for a
/// member that never had a retired unit.
/// Test: `retired_services_are_not_installed`.
/// Every launchd label an upgrade must clear for `member`.
///
/// Why: an installer asking "is there anything to evict for this member?" wants
/// one list, not a canonical label plus a separate legacy walk it has to
/// remember to do — forgetting the second half is how a pre-rename unit
/// survives an upgrade (#2938).
/// What: the retired service's own `label` followed by its `legacy` aliases,
/// newest first; empty for a member with no retired unit. The canonical label
/// comes first because it is the one holding the running process.
/// Test: `retired_review_carries_both_its_labels`,
/// `retired_analyze_carries_both_its_labels`.
/// Labels an upgrade must evict before bootstrapping `label`.
///
/// Why: this is what makes an install label-correct across a rename. Without
/// it, `service install` bootstraps the new label and leaves the old unit
/// running — two daemons, one port (#2938).
/// What: the `legacy` list for the service owning `label`, or empty when the
/// label is unknown (an unknown label evicts nothing rather than guessing).
/// Test: `legacy_labels_are_never_canonical`.
/// Whether a string is a CANONICAL launchd label this workspace installs.
///
/// Why: deliberately excludes legacy aliases. A legacy label appearing as a
/// literal in production source is not "a known label", it is the #4919 defect
/// — `trusty-search::commands::service::LAUNCHD_LABEL` was exactly such a
/// literal, and a membership test that accepted it would have passed while the
/// installer bootstrapped a unit launchd does not have.
/// What: true iff some [`SERVICES`] entry's `label` equals `candidate`.
/// Test: `no_stray_launchd_label_literals_in_workspace_sources` calls it to
/// decide whether a Makefile / shell / plist literal is acceptable — those
/// files cannot import a Rust constant, so naming the canonical label is the
/// best they can do, while a legacy or unknown one still fails.
/// What became of ONE launchd label an eviction pass tried to clear.
///
/// Why: an eviction that reports only "which labels were evicted" cannot tell
/// "there was nothing there" apart from "the removal failed" (#6290). Those
/// need opposite handling: the first is the steady state on every host after
/// the first pass, the second leaves a retired unit loaded and respawning, and
/// an installer that treats it as success exits 0 with the daemon still up.
/// What: per label, one of evicted / absent / failed-with-a-reason.
/// Test: `eviction_outcome_only_failed_is_a_failure`.
/// One label paired with what an eviction pass did to it.
///
/// Why: the caller reports per label, so the label has to travel with its
/// outcome rather than being recoverable only by position (#6290).
/// Test: `eviction_outcome_only_failed_is_a_failure`.