Skip to main content

wire/frames/
query.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//! Everything a page may ask, and the name table the schema hash is
7//! built from; `frames` says what every Query shares.
8
9use kernel::{Address, B3Hash, GitOid, Locator, NodeId, RunId, Seq};
10use serde::{Deserialize, Serialize};
11
12use crate::named_frames::named_frames;
13
14named_frames! {
15/// A read of the city; the module documentation says what all of them share.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
19pub enum Query {
20    /// A bounded slice of the one history, ending just before `before`
21    /// or at the tail when that is absent.
22    ///
23    /// The server broadcasts what happens next and never what happened,
24    /// so a page that only listened would see a city that had been
25    /// running for a month as an empty one. Bounded because the whole ledger is not a
26    /// thing to put on a socket, and paged backwards because what a
27    /// reader wants first is the end.
28    History {
29        before: Option<Seq>,
30        limit: u32,
31    },
32    /// The same slice, narrowed to one session.
33    ///
34    /// [`Query::History`] carries no run, so a client watching four
35    /// sessions divides one bounded slice between them and a session
36    /// that started before the tab did is not in it at all.
37    /// [`Query::RunView`] says whether a run exists and where it got to,
38    /// not what happened in it. Answered with [`HistoryAnswer`], because
39    /// "a page of history" already has a shape.
40    RunHistory {
41        run: RunId,
42        before: Option<Seq>,
43        limit: u32,
44    },
45    /// The records of the one history between two sequence numbers, both
46    /// ends included: the question a page asks when the event stream tells
47    /// it what it skipped.
48    ///
49    /// [`Query::History`] walks backwards from a cursor, which answers
50    /// "what came before this" and cannot answer "what happened between
51    /// these two". A gap has a near end as well as a far one, and a page
52    /// that walked to the gap's near end from the tail would pay for every
53    /// record in between.
54    ///
55    /// Answered with [`HistoryRangeAnswer`](crate::HistoryRangeAnswer)
56    /// rather than with [`HistoryAnswer`](crate::HistoryAnswer): the
57    /// question carries no cursor a reader holds on to, so the answer has
58    /// to name the slice it is, and a page filling a gap while its record
59    /// view is open is asking both questions at once. A range the Ledger
60    /// holds more records for than `limit` allows is answered up to the
61    /// limit, and the answer says where to ask next.
62    HistoryRange {
63        from: Seq,
64        to: Seq,
65        limit: u32,
66    },
67    /// The stretches of one room, newest first, which a page loses on a
68    /// reload: [`SessionsAnswer`](crate::SessionsAnswer) (`crates/wire/spec/Answer/Sessions.lean` §8-71).
69    Sessions {
70        room: Address,
71    },
72    /// What moved between two checkpoints: paths and counts, never patch
73    /// text.
74    ///
75    /// The caller names both ends because it already knows them - a
76    /// checkpoint's oid is in the `checkpoint_committed` payload the
77    /// client folded - and computing the pair a second time on the
78    /// server would be a second answer to "which checkpoints belong to this
79    /// session". Both oids are immutable, so the answer is cacheable
80    /// forever by anybody who wants to.
81    ///
82    /// `head` absent means the working tree: a wave still running has
83    /// written files no checkpoint holds yet, and a list that ignored
84    /// them would describe the session as it was one checkpoint ago.
85    Changes {
86        base: GitOid,
87        head: Option<GitOid>,
88    },
89    /// The patch text of one file between two checkpoints.
90    ///
91    /// **A separate frame from [`Query::Changes`], and that is the
92    /// point.** `Changes` costs what the number of changed files costs
93    /// and answers which files moved; this costs what one file costs
94    /// and answers what moved inside it, the request of its own that
95    /// `storage::changes` says a hunk needs.
96    ///
97    /// One path per frame; there is no spelling that asks for all of
98    /// them. A line the credential scan matched is not echoed: the
99    /// answer reports its line number and the reason, because printing
100    /// the bytes to prove a leak is the leak.
101    Hunks {
102        oid_a: GitOid,
103        oid_b: GitOid,
104        path: String,
105    },
106    /// Which run wrote one commit the city made.
107    ///
108    /// Every commit this city makes carries five git trailers naming the
109    /// run, the resident, the model, the effort and the city itself.
110    /// Those trailers are a projection for readers outside the city, so
111    /// this query is answered from the Ledger and never from git: a city
112    /// exported and restored elsewhere, with no `.git` beside it, still
113    /// answers.
114    ///
115    /// An oid this city never wrote is [`Answer::Unavailable`], for the
116    /// reason [`Query::Changes`] gives: "I did not write it" and "it
117    /// changed nothing" are different answers.
118    Commit {
119        oid: GitOid,
120    },
121    RunView {
122        run: RunId,
123    },
124    CityView,
125    ApprovalQueue,
126    InboxView {
127        addr: Address,
128    },
129    Metrics,
130    CostView,
131    ArchiveSearch {
132        needle: String,
133    },
134    RegistryView,
135    DiscardView,
136    /// What is attached and what is chosen: the settings page's read.
137    EndpointView,
138    /// The vendors this city knows by host, and the base URL of each
139    /// face they document, so a person picks a vendor rather than
140    /// pasting its address.
141    KnownHosts,
142    /// The official harnesses, the command that starts each as an ACP
143    /// agent, and whether this machine can run it: the harness page.
144    Harnesses,
145    /// One building's own files and its archive - the pages an agent
146    /// writes for the next agent, which are also the pages a person
147    /// reads to know what happened in there.
148    BuildingView {
149        addr: Address,
150    },
151    /// What the city calls the person and the Mayor, read from the two
152    /// identity areas at the moment of asking (`crates/wire/spec/Answer/Identity.lean` §8-59).
153    Identity,
154    /// The schedule and the watch table, read at the moment of asking:
155    /// shown on the page, written by hand (`crates/wire/spec/Answer/Automation.lean` §8-62).
156    Automation,
157    /// A candidate user id: gh's login for a host (`crates/wire/spec/Answer/Github.lean` §8-67).
158    GithubLogin(Option<String>),
159    /// The first-run guide's progress (`crates/wire/spec/Guide.lean` §8-68).
160    Guide,
161    /// Who answers for this city, and what has been answered on the
162    /// person's behalf.
163    ///
164    /// Both halves in one answer because a reader needs both to make
165    /// sense of either: a list of decisions with nobody named beside it
166    /// does not say whether the person delegated them, and a delegation
167    /// with nothing decided under it does not say whether it has ever
168    /// been used.
169    Governance,
170    /// One session, folded into the rounds a person reads.
171    ///
172    /// Answered server-side, because the wire is the whole API
173    /// (ARCHITECTURE.md section 8): a second client must be able to
174    /// draw a session without reimplementing the fold.
175    ///
176    /// Bounded by [`HISTORY_MAX`](crate::HISTORY_MAX) records, which is
177    /// the same slice [`Query::RunHistory`] answers: where the fold runs
178    /// must not change how much of a session it can see.
179    Rounds {
180        run: RunId,
181    },
182    /// What one run left that somebody can check it by: the screenshots
183    /// it stored and the completions it closed plan nodes with.
184    ///
185    /// Locators, never bytes. Fetching a picture is the asset
186    /// endpoint's, for the reason [`Query::Hunks`] is separate from
187    /// [`Query::Changes`]: one question must not carry the cost of
188    /// every answer somebody might go on to want.
189    Evidence {
190        run: RunId,
191    },
192    /// What one plan node has cost, and which runs spent it.
193    ///
194    /// A node nobody claimed answers zero, not `Unavailable`: "no run
195    /// has held this node" is a true answer. The money comes from
196    /// `storage::attribution` and is priced nowhere but `gateway::cost`.
197    CostOf {
198        node: NodeId,
199    },
200    /// One directory of the city, one level deep; `None` is the root.
201    ///
202    /// The tree is the product - a building is a directory and a room
203    /// is one inside it - and this is the frame that opens a room. One level per question, so looking at a room
204    /// never pays for the ledger segments beside it.
205    Listing {
206        at: Option<Address>,
207    },
208    /// Files under `under` whose name holds `text` (`crates/wire/spec/Answer/Find.lean` §8-82).
209    Find { under: Address, text: String },
210    /// One file of the city as a version, with its first window
211    /// (`crates/wire/spec/Answer/Document.lean` §8-69). The path is an `Address`, so it cannot leave the
212    /// city root; the reserved subtree is readable on purpose, because
213    /// this door answers the person and not a resident.
214    Document {
215        at: Address,
216    },
217    /// The proposal cards still open on one document (`crates/wire/spec/Answer/Proposals.lean` §8-73).
218    Proposals(Address),
219    /// Every proposal card still open in the city, by document and
220    /// offer time (`crates/wire/spec/Answer/Proposals.lean` §8-73).
221    OpenProposals,
222    /// One window of a stored version, by its version (`crates/wire/spec/Answer/Range.lean` §8-70).
223    Range { version: B3Hash, range: documents::Span },
224    /// One document's versions, newest first (`crates/wire/spec/Answer/DocumentVersions.lean` §8-83).
225    Versions { at: Address },
226    /// One window of a stored object's bytes (`crates/wire/spec/Answer/DocumentBytes.lean` §8-80).
227    Bytes { version: B3Hash, range: documents::Span },
228    /// A stored Markdown version as one HTML file (`crates/wire/spec/Answer/DocumentBytes.lean` §8-81).
229    Export { at: Address, version: B3Hash },
230    /// One window of a stored Markdown version, laid out (`crates/wire/spec/Answer/Preview.lean` §8-74).
231    Preview { version: B3Hash, viewport: documents::Span },
232    /// A reply's text, laid out by the preview's grammar (`crates/wire/spec/Answer/Preview.lean` §8-75).
233    Reply {
234        text: String,
235        state: documents::ReplyState,
236    },
237    /// The commits this city made, newest first, a page at a time.
238    ///
239    /// `building` keeps the commits whose actor worked at that address
240    /// or under it - the actor is the authority and a session is its
241    /// projection, so filtering by session would lose the runs nobody
242    /// opened a session for. `before` is exclusive, as in
243    /// [`Query::History`]; the next page asks with the last `seq` it was
244    /// handed. Only commits this city wrote are listed: an oid a person
245    /// rewrote into trunk by hand is not this city's, and it says so by
246    /// leaving it out rather than reading a trailer back.
247    Commits {
248        building: Option<Address>,
249        before: Option<Seq>,
250        limit: u32,
251    },
252    /// What this machine has, and what this city still needs of it.
253    ///
254    /// Answered from what the city found when it started, not from a
255    /// fresh look: every item is a program asked its version, and a
256    /// query that started a dozen processes would hold the one thread
257    /// that answers every other read. A city that has not looked
258    /// answers [`Answer::Unavailable`], which is what a worker driven
259    /// one command at a time is.
260    Doctor,
261    /// The system prompt one run was frozen with: four segments, their
262    /// text, and what each was assembled from.
263    ///
264    /// Answered from the run's own `prompt_assembled` record joined to
265    /// the store, never by assembling a prefix again: what a person
266    /// needs to see is the bytes that were sent, and a second assembly
267    /// taken now would read files that have moved since. A segment the
268    /// store no longer holds says so by name rather than by an empty
269    /// string, because "this segment was empty" and "these bytes are
270    /// gone" are different answers.
271    Prefix {
272        run: RunId,
273    },
274    /// One object of the content store, bounded, with the cut stated.
275    ///
276    /// The general read behind [`Query::Prefix`], and the door every
277    /// other `cas:` reference a page is shown can be opened through -
278    /// an approval's artifact, a discarded file's way back, a norm on a
279    /// handoff's must-read list. Only the `cas:` scheme is answered: a
280    /// `file:` locator names a path in the tree, which is
281    /// [`Query::Document`]'s question and must not have a second answer
282    /// here.
283    Content {
284        locator: Locator,
285    },
286    /// What one building can do, and where each of those came from.
287    ///
288    /// Every shelf in one answer because a reader needs them together
289    /// to make sense of any: a row carries [`crate::SkillShelf`], which
290    /// names the shelves and where each row sits on one. Which runs
291    /// pinned a skill is folded from `run_started`, so a shelf nothing
292    /// has ever used says so instead of looking unused because nobody
293    /// wrote it down.
294    Skills { building: Address },
295    /// What is uncommitted in one building right now: the branch, how
296    /// far it has drifted from its upstream, the files that moved, and
297    /// the last checkpoint the city checkpointed there.
298    ///
299    /// Read from git at the moment of asking, which is the one answer
300    /// here that is about the disk rather than about the history: a
301    /// working tree is what a person is looking at, and the Ledger
302    /// records checkpoints rather than edits. The checkpoint beside it comes
303    /// from the history, for the reason [`Query::Commit`] gives.
304    GitStatus { building: Address },
305    /// Whether each tool server one address reaches is answering, and
306    /// what it offers.
307    ///
308    /// One handshake per configured server, run at the moment of
309    /// asking: a server's state is a fact about now - a program that
310    /// starts, a host that answers, an account that is still valid -
311    /// and a remembered one would tell a person their server is up an
312    /// hour after it stopped. It is the same handshake a run opens with
313    /// (`agent_protocols::handshake`, then `tools/list`), so what this answers
314    /// and what a model is given cannot disagree.
315    ///
316    /// **This is the one query that costs seconds.** A page asks it
317    /// when a person opens the MCP page or adds a server, never on a
318    /// timer.
319    McpHealth {
320        addr: Address,
321    },
322    /// Which outside applications the broker offers, and where each one stands for this city.
323    ///
324    /// **The second query that costs a round trip to somebody else**, and it is asked on the same
325    /// terms as [`Query::McpHealth`]: when a person opens the page, and when they come back to it
326    /// from the consent page they were sent to. Never on a timer - a city that asked the broker
327    /// "anything new?" on a schedule would be generating traffic nobody reads, which
328    /// `docs/third-party.md` rules out.
329    ///
330    /// Carries no key: the project key is enrolled in the vault and redeemed on the host machine,
331    /// so a frame from a socket names nothing secret and an unenrolled city answers
332    /// `ToolkitsAnswer::Unenrolled` rather than failing.
333    Toolkits,
334    /// Which release this city is running, and which one npm offers.
335    ///
336    /// **Asked when a person presses the button, and at no other time.** Not on connect, not on a
337    /// timer, and not folded into another query: `QUICKSTART.md` opens by promising that nothing
338    /// was installed and nothing outside the folder was written, and a city that reached a registry
339    /// on its own schedule would be spending that sentence on a question nobody asked.
340    /// `docs/third-party.md` rules out the same traffic for the broker.
341    ///
342    /// Answering it costs one request to `registry.npmjs.org`, so it is the slowest query here and
343    /// the only one whose cost a person chose. A registry that cannot be reached is
344    /// [`ReleaseAnswer::Refused`](crate::ReleaseAnswer::Refused) rather than
345    /// [`Answer::Unavailable`](crate::Answer::Unavailable): the city is available, the registry is
346    /// not, and the page has to be able to say which.
347    ///
348    /// Nothing it answers updates anything. Where a binary lives belongs to whoever installed it,
349    /// so this reports and stops.
350    NewestRelease,
351    /// Everything this person settled about their own reading of the
352    /// city: the language, the appearance, the chords they rebound.
353    ///
354    /// The whole table in one answer, because a browser that cached
355    /// these row by row had a dozen names for them and read three of
356    /// those names back with its own idea of what a missing one means.
357    /// The file is the authority and this is the only door to it.
358    Preferences,
359    /// What one address is actually governed by, value by value, with
360    /// the file each value came from.
361    ///
362    /// **The layer is half the answer.** The three files form a ladder
363    /// - the city's, the building's, the room's - and a page told only
364    /// the resolved figure cannot say whether it is looking at
365    /// something this address states or something it inherited, so it
366    /// would have to read all three and climb the ladder a second
367    /// time. Two climbs of one ladder is two answers to one question.
368    Config {
369        addr: Address,
370    },
371    /// What each named run has been billed, for the runs a cost view's
372    /// `by_run` leaves out. At most
373    /// [`RUN_COSTS_MAX`](crate::RUN_COSTS_MAX) runs are answered per
374    /// question and the rest go unanswered, so a directory that names
375    /// its runs newest first shows the cost of the newest cold runs; the
376    /// money is folded from the Ledger for a run the city no longer
377    /// holds warm, so the cost is proportional to the runs asked about.
378    RunCosts {
379        runs: Vec<RunId>,
380    },
381    /// The newest release of one requirement-table item, asked of its
382    /// publisher (`crates/sprawling/Spec.lean` §8-120).
383    ///
384    /// Asked by the dependency page for each item once the page is open,
385    /// which is the person asking whether their tools are behind; the
386    /// city asks nothing on its own schedule. One item per question, so
387    /// a slow publisher holds up its own row and no other. A source that
388    /// cannot be reached is [`DoctorNewest::Refused`](crate::DoctorNewest::Refused)
389    /// rather than [`Answer::Unavailable`](crate::Answer::Unavailable).
390    UpstreamVersion {
391        item: String,
392    },
393}
394
395/// The Query surface, in declaration order — the order the handshake
396/// hash mixes these names in.
397pub const QUERY_NAMES;
398}