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}