wire/answer/doctor.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What this machine has, as the city found it when it started.
7//!
8//! Every state here is an enum rather than a sentence. The terminal
9//! report is one machine's prose and a browser is two languages, so a
10//! wire that carried the wording would make the page's words the
11//! server's to choose. What an item is for travels as its `name`, the
12//! id the page looks its own clause up by. The `said` fields carry what
13//! a platform or a program said, which no reader can derive.
14
15use serde::{Deserialize, Serialize};
16
17/// This machine, item by item, with a verdict for each tier.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20pub struct DoctorAnswer {
21 /// Every item this city needs, in the order the requirement table
22 /// states them.
23 pub items: Vec<DoctorItem>,
24 /// One verdict per tier: a person who only wants to run a city is
25 /// not told about what changing this code would need.
26 pub tiers: Vec<DoctorVerdict>,
27 /// The confinement a command an agent asks for runs under, and the
28 /// axes that arm does not hold. An agent has to be able to read
29 /// that it is not in a box with the network closed before it acts.
30 pub sandbox: DoctorSandbox,
31 /// Where credentials rest on this machine, and how long they stay.
32 pub custody: DoctorCustody,
33 /// Where this machine lets the core's threads stand.
34 pub core: DoctorCore,
35 /// Whether real-time scanning stands in front of the city
36 /// directory's writes (`crates/wire/spec/Answer/Doctor.lean` D25).
37 pub scanning: super::scanning::DoctorScanning,
38}
39
40/// The level this machine gives the core's threads under the person's
41/// setting (`crates/sprawling/Spec.lean` ยง8-93). The dispatched commands are not here:
42/// they always start one level below, and lowering is never refused.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case")]
45#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
46pub enum DoctorCore {
47 /// One step above normal.
48 Raised,
49 /// Normal, because the person's `[core] priority` asks for it.
50 HeldBySetting,
51 /// Normal, because the platform refused the raise; `said` is the
52 /// platform's own words.
53 Refused { said: String },
54 /// Normal, lowered after keeping a core busy.
55 LoweredByValve,
56 /// The doctor could not ask; `said` is why.
57 Unasked { said: String },
58}
59
60/// Who needs an item, and therefore which verdict it counts towards.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
64pub enum DoctorTier {
65 /// Enough to run a city on this machine.
66 Use,
67 /// Enough to change this code and close its checks.
68 Develop,
69}
70
71/// Whether a tier can be reached without the item.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
75pub enum DoctorNeed {
76 Required,
77 /// Absent is a fact rather than a fault.
78 Optional,
79}
80
81/// One item, and this machine's answer about it.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
84pub struct DoctorItem {
85 /// The requirement table's name for the item, which is also its id:
86 /// a page says what the item is for in its own words, looked up by
87 /// this name.
88 pub name: String,
89 pub tier: DoctorTier,
90 pub need: DoctorNeed,
91 /// The item's own site, so a page can link a name to the people who
92 /// publish it. `None` where there is no one site: a platform's
93 /// shell, and this project's own connector.
94 pub homepage: Option<String>,
95 pub state: DoctorState,
96 pub install: DoctorInstall,
97 /// The version this repository pins the item at, read from the file
98 /// that pins it; `None` for an item nothing pins.
99 pub pinned: Option<String>,
100 /// The pack a page draws this item inside, with one install control
101 /// for the pack's missing members; `None` for an item drawn alone.
102 pub pack: Option<DoctorPack>,
103}
104
105/// A set of items a page draws as one row: each member is still its own
106/// item here, because each is detected, judged and installed on its own.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108#[serde(rename_all = "snake_case")]
109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
110pub enum DoctorPack {
111 /// The cargo subcommands this repository's recipes and workflows call.
112 RustTools,
113}
114
115/// The newest release of one item upstream, as its publisher states it.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
118pub struct DoctorUpstream {
119 /// The requirement table's name for the item, as `DoctorItem.name`.
120 pub item: String,
121 pub newest: DoctorNewest,
122}
123
124/// What asking the item's publisher came to.
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "snake_case")]
127#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
128pub enum DoctorNewest {
129 /// The city has asked the publisher and has no answer yet; asking
130 /// again later reads what arrived. The question never waits on the
131 /// network, because a session answers its questions one at a time.
132 Asking,
133 /// The newest stable version, as a dotted number.
134 Read { version: String },
135 /// This item has no single upstream version to read, and why.
136 Unread { why: DoctorUnread },
137 /// The source was asked and did not answer; `said` is where the
138 /// call stopped.
139 Refused { said: String },
140}
141
142/// Why an item has no upstream version to compare with.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(rename_all = "snake_case")]
145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
146pub enum DoctorUnread {
147 /// It ships inside the Rust toolchain, so the toolchain's version is
148 /// the one that moves.
149 WithToolchain,
150 /// A family of browsers, each brand with its own releases.
151 ManyBrands,
152 /// A driver whose version follows the browser beside it.
153 MatchesBrowser,
154 /// A part of this project, released with it.
155 ThisProject,
156 /// Its publisher offers no machine-readable release to ask.
157 NoSource,
158 /// The requirement table carries no item by that name.
159 UnknownItem,
160}
161
162/// Whether the item is here, and in what condition.
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
166pub enum DoctorState {
167 /// Here, and it started when asked its version.
168 Present {
169 at: String,
170 version: DoctorVersion,
171 },
172 /// Here, and unusable. Counts as missing for a verdict, and as its
173 /// own fault for a person.
174 Broken {
175 at: String,
176 fault: DoctorFault,
177 },
178 Absent {
179 absence: DoctorAbsence,
180 },
181}
182
183/// What a present program said when asked its version. Only the first
184/// arm carries text; the other three are facts about how it did not
185/// answer, and none of them makes the program absent.
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(rename_all = "snake_case")]
188#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
189pub enum DoctorVersion {
190 Said {
191 text: String,
192 },
193 /// It started, wrote nothing, and exited.
194 Silent,
195 /// Its first line was not text.
196 Unreadable,
197 /// It had not written a line when the deadline passed.
198 Late,
199}
200
201/// Why a thing that is here cannot be used.
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
205pub enum DoctorFault {
206 /// The operating system refused to start it.
207 WillNotStart {
208 said: String,
209 },
210 /// The component directory exists and the component file does not:
211 /// an install that stopped half way.
212 HalfWritten,
213 Unreadable {
214 said: String,
215 },
216}
217
218/// Which kind of not being here.
219#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220#[serde(rename_all = "snake_case")]
221#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
222pub enum DoctorAbsence {
223 NotOnSearchPath,
224 /// A person pointed at a file, and the file is not there.
225 VariableNamesNothing {
226 variable: String,
227 path: String,
228 },
229 NoComponent {
230 dir: String,
231 },
232 NoHome,
233 NotInThisBuild,
234}
235
236/// What getting the item would cost on this machine.
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "snake_case")]
239#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
240pub enum DoctorInstall {
241 /// A command this city may run once the person has agreed to it.
242 Command { spelled: String },
243 /// A command printed and never run: a script piped into a shell is
244 /// code nobody read.
245 Print { spelled: String },
246 /// Nothing here can install it; the text says what a person does.
247 Manual { how: String },
248 /// This machine is not one of the three this project names recipes
249 /// for, so nothing can be said about installing anything on it.
250 UnknownPlatform,
251}
252
253/// Whether one tier is reachable on this machine.
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
256pub struct DoctorVerdict {
257 pub tier: DoctorTier,
258 /// The required items of this tier that are absent or broken, in
259 /// table order. Empty means ready.
260 pub missing: Vec<String>,
261}
262
263/// Which backend a host command runs under on this machine, and what
264/// that arm promises.
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
266#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
267pub struct DoctorSandbox {
268 pub arm: DoctorSandboxArm,
269 /// The arm's name in the closed set a setting chooses from
270 /// (`crates/wire/spec/Answer/Doctor.lean` D26).
271 pub named: SandboxArm,
272 /// One row per axis, in the order the axes are declared. The rows
273 /// are stated rather than left to the page to infer: an arm that
274 /// holds some axes and not others is the whole reason this report
275 /// exists.
276 pub coverage: Vec<DoctorGuarantee>,
277}
278
279/// The backend itself. Closed, so a page has a word for every arm and
280/// a new arm is a compile error at every reader.
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(rename_all = "snake_case")]
283#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
284pub enum DoctorSandboxArm {
285 /// Namespaces of its own, made by a wrapper program.
286 LinuxNamespaces,
287 /// A Windows job object: the process tree ends together and the
288 /// limits hold, and the network is not isolated.
289 WindowsJobObject,
290 /// macOS Seatbelt: file writes confined to a copy and networking denied.
291 MacosSeatbelt,
292 /// The floor every platform has: the command runs in a copy of the
293 /// working tree.
294 CopiedTree,
295 /// No arm at all, and what this machine is missing.
296 Unavailable { missing: DoctorSandboxMissing },
297}
298
299pub use kernel::SandboxArm;
300
301/// What a machine lacks when it can give no confinement at all.
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
303#[serde(rename_all = "snake_case")]
304#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
305pub enum DoctorSandboxMissing {
306 ScratchDirectory,
307}
308
309/// One axis of confinement, and whether this machine's arm holds it.
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
311#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
312pub struct DoctorGuarantee {
313 pub axis: DoctorGuaranteeAxis,
314 pub kept: DoctorCoverage,
315}
316
317/// What a confinement can promise.
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
319#[serde(rename_all = "snake_case")]
320#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
321pub enum DoctorGuaranteeAxis {
322 Filesystem,
323 Network,
324 ProcessTree,
325 User,
326 Resources,
327}
328
329/// Whether the arm holds the axis. Two words rather than a boolean,
330/// because a page has to word both and a wire that carried `true` would
331/// make every reader choose its own word for `false`.
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
333#[serde(rename_all = "snake_case")]
334#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
335pub enum DoctorCoverage {
336 Kept,
337 NotKept,
338}
339
340/// Where this machine's credentials rest, and how long they stay.
341#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
342#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
343pub struct DoctorCustody {
344 pub store: DoctorCustodyStore,
345 pub keeps: DoctorCustodyLifetime,
346 /// The platform service's own refusal, when it did not keep the
347 /// value it was asked to keep. `None` is a service that worked, and
348 /// a store this city chose itself.
349 pub refusal: Option<String>,
350}
351
352/// Which store a city writes secrets to.
353#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
356pub enum DoctorCustodyStore {
357 /// The platform's own credential service.
358 PlatformService,
359 /// An encrypted file on this machine, opened once per start with a
360 /// passphrase.
361 EncryptedFile,
362 /// This process only.
363 SessionMemory,
364}
365
366/// How long a value the store keeps stays reachable.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "snake_case")]
369#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
370pub enum DoctorCustodyLifetime {
371 AcrossReboots,
372 WithPassphrase,
373 UntilReboot,
374 ThisProcess,
375}