Skip to main content

wire/
answer.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 a Query comes back as: one shape per view, and the closed set
7//! of them.
8//!
9//! These are read shapes and nothing else. Every one is derived from the
10//! city's own state by a projection above this crate, so nothing here
11//! decides anything; what this module owns is that a page and a server
12//! agree on the field names, and that `Unavailable` is a real answer
13//! (`Answer` says why).
14
15use kernel::{
16    Address, ApprovalItem, Autonomy, ClusterKey, FileChange, GitOid, Restoration, Ruling, Seq,
17    TimeMs,
18};
19use serde::{Deserialize, Serialize};
20
21use crate::command::HaltScope;
22use crate::guide::GuideProgress;
23use crate::preference::PreferencesAnswer;
24
25mod agents;
26mod automation;
27mod building;
28mod commits;
29mod config;
30mod cost;
31mod cost_of;
32mod devices;
33mod doctor;
34mod document;
35mod document_bytes;
36mod document_versions;
37mod endpoints;
38mod evidence;
39mod find;
40mod git_status;
41mod github;
42mod history;
43mod hunks;
44mod identity;
45mod known_hosts;
46mod listing;
47mod mcp_health;
48mod metrics;
49mod model_facts;
50mod model_figures;
51mod note;
52mod prefix;
53mod preview;
54mod proposals;
55mod range;
56mod release;
57mod rounds;
58mod run_summary;
59mod scanning;
60mod sessions;
61mod skills;
62mod thinking;
63mod toolkits;
64mod usage;
65
66pub use agents::{
67    AgentCatalogAnswer, AgentLine, AgentOffer, AgentSource, AuthMethod, CatalogSnapshot, LoginKind,
68    LoginState, PinState,
69};
70pub use automation::{AutomationAnswer, Cadence, ScheduledJob, WatchedSource};
71pub use building::{ArchiveLine, BlockedLine, BuildingAnswer, BuildingDoc};
72pub use building::{BuildingProgress, PlanRow, PursuitLine};
73pub use commits::{CommitAnswer, CommitAt, CommitsAnswer};
74pub use config::{
75    ConfigAnswer, ConfigLayer, SecondDomain, SettledEffort, SettledSearch, SettledSecond,
76    SupplierAccounts, TuningDefaults,
77};
78pub use cost::{CostAnswer, Renewals, UnpricedCalls};
79pub use cost_of::{CostOfAnswer, RUN_COSTS_MAX, RunCostsAnswer};
80pub use devices::{DeviceId, DeviceLine, DevicesAnswer};
81pub use doctor::{DoctorAbsence, DoctorAnswer, DoctorCore, DoctorFault, DoctorInstall, DoctorItem};
82pub use doctor::{DoctorCoverage, DoctorCustody, DoctorCustodyLifetime, DoctorCustodyStore};
83pub use doctor::{DoctorGuarantee, DoctorGuaranteeAxis, DoctorSandbox, DoctorSandboxArm};
84pub use doctor::{DoctorNeed, DoctorState, DoctorTier, DoctorVerdict, DoctorVersion};
85pub use doctor::{DoctorNewest, DoctorPack, DoctorUnread, DoctorUpstream};
86pub use doctor::{DoctorSandboxMissing, SandboxArm};
87pub use document::{Coverage, DocumentAnswer, DocumentBody, DocumentState, HeldDocument};
88pub use document_bytes::{BYTES_WINDOW_MAX, BytesAnswer, ExportAnswer};
89pub use document_versions::{DocumentVersion, VERSIONS_MAX, VersionSource, VersionsAnswer};
90pub use endpoints::{AccountStatus, ChosenSummary, EndpointSummary, EndpointsAnswer, KeyState};
91pub use evidence::{EvidenceAnswer, EvidenceItem, EvidenceKind, Picture};
92pub use find::{FIND_MAX, FIND_WALK_MAX, FindAnswer, Walked};
93pub use git_status::{Drift, GitStatusAnswer};
94pub use github::{GithubLoginAnswer, GithubReading};
95pub use history::{HistoryAnswer, HistoryRangeAnswer};
96pub use hunks::{HunksAnswer, PatchLine, Withheld};
97pub use identity::{IdentityAnswer, StatedIdentity};
98pub use known_hosts::{KnownFace, KnownHost, KnownHostsAnswer};
99pub use listing::{Entry, EntryKind, ListingAnswer};
100pub use mcp_health::{McpHealthAnswer, McpServerHealth, McpState, McpToolLine};
101pub use metrics::MetricsAnswer;
102pub use model_facts::ModelFactsSummary;
103pub use model_figures::{
104    CeilingLine, FigureFrom, FiguresAsked, FiguresLine, ModelFiguresAnswer, WindowLine,
105};
106pub use note::{HandbackNote, Note, ReplyEnd, ReplyEnded, Speaker};
107pub use prefix::{ContentAnswer, PrefixAnswer, PrefixSegment, PrefixSlot, PrefixSource};
108pub use preview::PreviewAnswer;
109pub use proposals::{OfferedCard, OpenProposalsAnswer, ProposalCard, ProposalsAnswer};
110pub use range::RangeAnswer;
111pub use release::{InstallChannel, Registry, RegistryNewest, RegistryReading};
112pub use release::{ReleaseAnswer, ReleaseLine, UpdateHint};
113pub use rounds::{
114    Call, Closing, FrozenNames, Opening, Outcome, Output, RoundsAnswer, Timing, Turn, Used,
115};
116pub use run_summary::{RunSummary, Waiting};
117pub use scanning::{DoctorDrive, DoctorExclusion, DoctorScanning, DoctorUntold};
118pub use sessions::{SESSION_PREVIEW_MAX, SESSIONS_MAX, SessionLine, SessionStart, SessionsAnswer};
119pub use skills::{SkillLine, SkillShelf, SkillsAnswer};
120pub use thinking::{EffortWord, OfferSource, Switch, ThinkingOffer};
121pub use toolkits::{Standing, ToolkitLine, ToolkitsAnswer};
122pub use usage::VersionAuthor;
123pub use usage::{
124    DayCount, ExportFormat, HeldSkill, McpServerUsage, McpToolUsage, McpUsageAnswer, McpUse,
125};
126pub use usage::{ShellCalls, ShellsAnswer, UsageExportAnswer, UsageKind, UseOutcome};
127pub use usage::{SkillAudit, SkillUsageAnswer, SkillUsageLine, SkillUse, SkillVersion};
128
129/// What moved between two checkpoints, one row per file, path order.
130///
131/// Path order rather than size order: somebody looking for one file finds
132/// it in the same place every time, and a list that reorders itself as
133/// the numbers change cannot be scanned twice.
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
136pub struct ChangesAnswer {
137    pub base: GitOid,
138    /// Absent when the comparison ran against the working tree.
139    pub head: Option<GitOid>,
140    pub files: Vec<FileChange>,
141}
142
143/// The most records one `History` answer may carry. A page asking for
144/// more gets this many; the whole ledger is not a thing to put on a
145/// socket, and a limit the caller cannot exceed is one fewer way for a
146/// client to make the server do unbounded work.
147pub const HISTORY_MAX: u32 = 500;
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
151pub struct CityAnswer {
152    /// Every active run and the latest frozen few, in RunId order;
153    /// `frozen` counts every frozen run, listed or not.
154    pub runs: Vec<RunSummary>,
155    pub active: u64,
156    pub frozen: u64,
157    /// One entry per building whose roadmap the city could read.
158    pub buildings: Vec<BuildingProgress>,
159    /// The standing goals this city is working towards, if any.
160    pub pursuits: Vec<PursuitLine>,
161    /// The scopes shut by `halt` and not yet released, in the shape a
162    /// `halt` frame names them. A page that has just opened has no
163    /// other way to learn that the city is stopped.
164    ///
165    /// The same type the command carries, not the ledger's string form:
166    /// a reader that had to take `building:<addr>` apart would be
167    /// writing a second copy of a grammar that already has one, and the
168    /// first client to get it wrong reported every shut building as
169    /// running.
170    pub halted: Vec<HaltScope>,
171    /// The last record of a history proved whole, when the proof has
172    /// found it so; absent while a served city is still proving it, or
173    /// after the proof found it broken (`crates/wire/spec/Answer.lean` §8-63).
174    pub proved: Option<Seq>,
175}
176
177/// What is waiting for a person, as the Ledger recorded it.
178///
179/// The whole item travels rather than a summary of it. An interface that
180/// groups identical questions needs the cluster key, and one that leads
181/// with the longest wait needs the arrival time; a summary type that
182/// dropped both forced the page to render every item as its own group
183/// under a sentence nobody wrote. `tainted` needs no special carriage
184/// here for the same reason - it is a field of the item itself.
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
187pub struct ApprovalsAnswer {
188    pub items: Vec<ApprovalItem>,
189}
190
191/// What a query returns. `Unavailable` is a real answer: a view this
192/// build does not evaluate yet says so by name, rather than returning an
193/// empty result a reader would mistake for an empty city.
194///
195/// `Eq` went when `History` arrived: a record's payload is arbitrary
196/// JSON, and JSON has no total equality. Nothing compared two answers
197/// for equality outside a test.
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
199#[serde(rename_all = "snake_case")]
200#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
201pub enum Answer {
202    History(Box<HistoryAnswer>),
203    HistoryRange(Box<HistoryRangeAnswer>),
204    Sessions(SessionsAnswer),
205    Changes(ChangesAnswer),
206    Commit(Box<CommitAnswer>),
207    City(CityAnswer),
208    Run(Option<Box<RunSummary>>),
209    Approvals(ApprovalsAnswer),
210    Cost(Box<CostAnswer>),
211    Endpoints(EndpointsAnswer),
212    KnownHosts(KnownHostsAnswer),
213    AgentCatalog(Box<AgentCatalogAnswer>),
214    /// One pasted spec read as an agent entry (`crates/wire/spec/Answer/Agents.lean` §8-90).
215    AgentSpec(Box<AgentOffer>),
216    Devices(Box<DevicesAnswer>),
217    Building(Box<BuildingAnswer>),
218    Inbox(InboxAnswer),
219    Discards(DiscardAnswer),
220    Registry(RegistryAnswer),
221    Archive(ArchiveAnswer),
222    Metrics(Box<MetricsAnswer>),
223    Governance(GovernanceAnswer),
224    Hunks(Box<HunksAnswer>),
225    Rounds(Box<RoundsAnswer>),
226    Evidence(EvidenceAnswer),
227    CostOf(CostOfAnswer),
228    RunCosts(RunCostsAnswer),
229    Listing(ListingAnswer),
230    Find(FindAnswer),
231    Document(Box<DocumentAnswer>),
232    Proposals(Box<ProposalsAnswer>),
233    OpenProposals(OpenProposalsAnswer),
234    Range(Box<RangeAnswer>),
235    Versions(Box<VersionsAnswer>),
236    Bytes(Box<BytesAnswer>),
237    Export(Box<ExportAnswer>),
238    Preview(Box<PreviewAnswer>),
239    /// A reply's text laid out (`crates/wire/spec/Answer/Preview.lean` §8-75): its closed blocks, and
240    /// the bytes they cover.
241    Reply(Box<documents::Laid>),
242    Commits(CommitsAnswer),
243    Doctor(Box<DoctorAnswer>),
244    Upstream(Box<DoctorUpstream>),
245    Prefix(Box<PrefixAnswer>),
246    Content(Box<ContentAnswer>),
247    Skills(Box<SkillsAnswer>),
248    GitStatus(Box<GitStatusAnswer>),
249    McpHealth(Box<McpHealthAnswer>),
250    SkillUsage(Box<SkillUsageAnswer>),
251    McpUsage(Box<McpUsageAnswer>),
252    UsageExport(Box<UsageExportAnswer>),
253    Shells(Box<ShellsAnswer>),
254    Toolkits(Box<ToolkitsAnswer>),
255    Release(Box<ReleaseAnswer>),
256    Preferences(Box<PreferencesAnswer>),
257    Config(Box<ConfigAnswer>),
258    Identity(Box<IdentityAnswer>),
259    Automation(Box<AutomationAnswer>),
260    GithubLogin(GithubLoginAnswer),
261    Guide(GuideProgress),
262    Privacy(Box<crate::PrivacyAnswer>),
263    /// What an empty model table would register (`crates/wire/spec/Answer/ModelFigures.lean` §8-97).
264    ModelFigures(ModelFiguresAnswer),
265    /// `reason` is the text of what stopped a view that tried to look
266    /// (`crates/wire/spec/Server.lean` D47); absent when the view names
267    /// the query alone.
268    Unavailable {
269        query: String,
270        #[serde(default, skip_serializing_if = "Option::is_none")]
271        reason: Option<String>,
272    },
273}
274
275/// Who answers for this city, and what was answered on the person's
276/// behalf.
277///
278/// One shape for both halves: a list of decisions with nobody named
279/// beside it does not say whether the person delegated them, and a
280/// delegation with nothing under it does not say whether it was ever
281/// used.
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
284pub struct GovernanceAnswer {
285    pub autonomy: Autonomy,
286    /// Every approval this city has answered, oldest first — the order
287    /// the ledger wrote them and the order a fold expects.
288    ///
289    /// The person's own answers are here too. A list holding only what
290    /// somebody else decided would make "I answered this" and "nobody
291    /// ever answered this" look the same, and who may answer is the
292    /// other field of this same answer.
293    pub decided: Vec<Decision>,
294}
295
296/// One approval, as it was answered.
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
298#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
299pub struct Decision {
300    /// The `ApprovalId` that was answered.
301    pub item: String,
302    pub verdict: Ruling,
303    /// What the person was shown when they answered: the answer covers
304    /// the group, not the one row.
305    pub cluster: ClusterKey,
306    pub at: TimeMs,
307}
308
309/// What waits in one room, without taking it.
310///
311/// Folded from the ledger rather than read off the queue: a queue that
312/// is read by being consumed cannot also be looked at, and a view that
313/// consumed what it showed would be a view that changes the thing it
314/// reports on.
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
317pub struct InboxAnswer {
318    pub addr: Address,
319    pub waiting: Vec<SignalLine>,
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
324pub struct SignalLine {
325    pub id: String,
326    pub kind: String,
327    pub from: String,
328    pub at: TimeMs,
329    /// The first line of the signal's `text`; `None` when its payload
330    /// carries no text (`crates/wire/spec/Reading.lean` D39).
331    #[serde(default)]
332    pub first_line: Option<String>,
333}
334
335/// The Recycle Bin: what was discarded and how each row gets back.
336///
337/// A row without a way back cannot be constructed upstream, so every
338/// row here states one; `restored` says whether somebody already took
339/// it.
340#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
341#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
342pub struct DiscardAnswer {
343    pub rows: Vec<DiscardLine>,
344}
345
346#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
347#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
348pub struct DiscardLine {
349    pub path: String,
350    /// The way back, as the record kept it - the plan itself, not a
351    /// sentence about it.
352    ///
353    /// Composing the sentence is the interface's job and it already has
354    /// one authority for it (`client/src/views/record/bin.svelte`); a server that
355    /// also rendered the plan into words would be the second. `None`
356    /// means the record names a scheme this build cannot read, which the
357    /// interface shows as a row it will not invent an action for - the
358    /// row is never dropped, because hiding a discarded thing is worse
359    /// than admitting the plan is unreadable.
360    pub restoration: Option<Restoration>,
361    pub at: TimeMs,
362    pub restored: bool,
363}
364
365/// What this city has decided is worth keeping.
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
367#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
368pub struct RegistryAnswer {
369    pub assets: Vec<RegistryLine>,
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
373#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
374pub struct RegistryLine {
375    pub addr: Address,
376    pub kind: String,
377    pub subject: String,
378    pub at: TimeMs,
379}
380
381/// Archive hits across every building, read from the shelves at the
382/// moment of asking. The files are the authority; an index kept beside
383/// them would be a second copy of what the disk says.
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
385#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
386pub struct ArchiveAnswer {
387    pub needle: String,
388    pub hits: Vec<ArchiveHit>,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
393pub struct ArchiveHit {
394    pub building: Address,
395    pub kind: String,
396    pub day: u64,
397    pub subject: String,
398}