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
//! Deployment-completeness self-check for `daemon::managed_routes::lifecycle`
//! (issue #2158, softened non-blocking by #2172).
//!
//! Why: split out of `lifecycle.rs` (issue #3726 review follow-up) purely to
//! keep `lifecycle.rs` — a production file already grandfathered on the
//! `.line-cap-allowlist.tsv` SLOC ratchet, with its frozen budget
//! deliberately never allowed to grow — from growing further. This mirrors
//! the exact motivation that already split `lifecycle_tests.rs` out of
//! `lifecycle.rs`'s inline test module. Pure code motion — no behavior
//! change; every item below is verbatim from `lifecycle.rs`.
//! What: [`ensure_deployment_complete`] (validate-then-auto-repair a
//! workspace's `.claude/` payload before handoff), the
//! [`warn_if_no_persona_carrier`] diagnostic self-check it runs, and the pure
//! [`carrier_reachable`] predicate that backs it.
//! Test: `ensure_deployment_complete_*`/`carrier_reachable_*`/
//! `warn_if_no_persona_carrier_*` remain in `lifecycle_tests.rs` (a child
//! module of `lifecycle`, which re-imports these via `use
//! deployment_check::*` — `use super::*` there still resolves the bare
//! names unchanged).
use ;
use crateManagedSessionId;
/// What a launch already resolved about the host, for the deployment repair.
///
/// Why (#7763, #8545): the repair re-runs session preparation, which probes
/// trusty-memory and writes the user-tier statusLine and trust seed under a
/// home. An isolated test daemon pins its own home; production passes
/// `DaemonState::user_home`.
/// What: `memory_reachable` (`None` probes) and `home` (`None` skips the
/// user-global writes).
pub
/// Validate a workspace against the canonical bundled roster before handing
/// the session to the operator, auto-repairing first when gaps are found
/// (issue #2158).
///
/// Why: `prepare_session_inner`'s roster/output-style/hooks steps are already
/// best-effort/non-fatal (issue #2149) so a session always launches carrying
/// SOME identity — but "launches" is not the same as "launches complete". A
/// worktree whose `.claude/` payload came up incomplete (missing agents, a
/// stripped `settings.json`, no ownership manifest — see #2158) could
/// otherwise silently reach the operator. This function surfaces that gap:
/// validate, and if incomplete, re-run the deploy pipeline once via
/// [`crate::core::deploy_validate::validate_and_repair`] (which reuses
/// [`crate::core::session_launch::prepare_session_with_repo_url`] — the exact
/// #2149 pipeline, no parallel repair implementation), then re-validate.
/// **Non-blocking as of #2172 (P0):** every `spawn_managed_*`/`resume_managed`
/// call site now treats `Err` as a `tracing::warn!`-only diagnostic and always
/// proceeds to `adapter.spawn`/`adapter.spawn_resume` regardless of the
/// result. The original #2158 contract — skip the runtime launch and mark the
/// record errored on `Err` — turned out to be unsafe to wire as a hard gate:
/// the validator over-reports INCOMPLETE (#2171), so the gate was aborting
/// `adapter.spawn` on effectively every new/restarted managed session,
/// leaving the pane at a bare shell. This function's return type is
/// deliberately still `Result<(), String>` (callers/tests still want the
/// pass/fail detail to log or assert on) — it is the CALLERS' responsibility
/// to never let that `Err` skip the launch. Do not reintroduce an early
/// return/`mark_errored` on this `Err` at any call site without first fixing
/// #2171 and re-litigating whether a hard gate is safe.
/// What: no-ops (`Ok(())`) when `workspace` is the adopted-session sentinel
/// `/unknown` or does not exist on disk — an unresolved workspace has nothing
/// to validate; that case is handled separately by the `reconcile_on_boot`
/// adopted-session fix, not here. Otherwise delegates to `validate_and_repair`
/// using the caller-resolved `fw` (production call sites pass
/// [`crate::core::paths::FrameworkPaths::for_managed_workspace`]`(workspace)`;
/// tests inject a hermetic [`crate::core::paths::FrameworkPaths::under`]).
/// `Ok(())` when the workspace is (or becomes) complete; `Err(detail)` naming
/// every residual gap otherwise.
///
/// #7763: `memory_reachable` is whatever this launch's own `prepare_session*`
/// already resolved. The repair step re-runs that preparation, so passing the
/// resolved verdict is what keeps a slow or wedged trusty-memory from costing
/// the launch a second `PROBE_TIMEOUT`. `None` is correct for a caller that
/// never prepared (the resume path) and simply restores the probe.
/// Test: `ensure_deployment_complete_noops_for_unknown_workspace`,
/// `ensure_deployment_complete_ok_when_already_complete`,
/// `ensure_deployment_complete_does_not_abort_when_no_carrier_reachable`
/// (`lifecycle_tests.rs`) cover this function directly; #2172's
/// non-blocking-call-site contract (every `spawn_managed_*`/`resume_managed`
/// site logs `Err` and still proceeds to `adapter.spawn`) is asserted at
/// each of those call sites, not here.
pub
/// Warn-only self-check: is at least one delegation-persona CARRIER reachable
/// under the daemon path's `--setting-sources project,local` posture (issue
/// #2231)?
///
/// Why: `--setting-sources project,local` (see
/// `core::model_inject::SETTING_SOURCES_FLAG`) restricts the launched
/// `claude` to the project+local tiers and EXCLUDES the `user` tier that
/// `CLAUDE_CONFIG_DIR` relocates to (see `core::managed_config`'s module doc,
/// "WHICH LAYER ACTUALLY LOADS THE ROSTER") — so the PM's identity survives
/// ONLY if a project-tier carrier is reachable: either the deployed
/// `trusty-mpm` output-style file (`settings.json`'s `outputStyle` resolving
/// to a real file under `.claude/output-styles/`), or the per-workspace
/// instructions stash (`<workspace>/.trusty-mpm/last-instructions.md`,
/// written by `session_launch::prepare_session_inner` and injected via
/// `--append-system-prompt-file`). This is a DIAGNOSTIC ONLY, mirroring the
/// #2172/98b994c3 lesson this very function already embodies
/// (`ensure_deployment_complete` was softened from a hard gate to a
/// non-blocking warn by #2172, commit 98b994c3) — over-reporting "no carrier
/// reachable" must NEVER abort a real launch, so this only logs; it cannot
/// fail this function or any caller, and it never returns a value.
/// What: logs `tracing::warn!` with an actionable message (naming the missing
/// carriers and how they are normally wired) when [`carrier_reachable`]
/// returns `false`; a no-op otherwise.
/// Test: `carrier_reachable_*` cover the pure predicate directly;
/// `ensure_deployment_complete_does_not_abort_when_no_carrier_reachable`
/// asserts this self-check never turns the caller's `Ok` into an `Err`.
pub
/// Pure predicate: is at least one delegation-persona carrier reachable?
///
/// Why: isolated from the logging side effect in
/// [`warn_if_no_persona_carrier`] so the decision itself is directly
/// unit-testable — see that function's doc for the full carrier-reachability
/// rationale.
/// What: `true` when `gaps` contains NONE of the output-style-related
/// [`crate::core::deploy_validate::DeploymentGap`] variants
/// (`OutputStyleKeyMissing`, `OutputStyleUnknownId`, `OutputStyleFileMissing`)
/// — the output-style carrier is intact — OR
/// `<workspace>/.trusty-mpm/last-instructions.md` exists and is non-empty (the
/// prompt-file carrier). `false` only when NEITHER carrier is reachable.
/// Test: `carrier_reachable_true_when_no_output_style_gap`,
/// `carrier_reachable_true_when_prompt_file_present_despite_style_gap`,
/// `carrier_reachable_false_when_neither_carrier_present`.
pub