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    /// 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}