Skip to main content

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