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