wire/answer/release.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//! Which release this city is running, and which one npm offers.
7//!
8//! **A reading of right now, never folded from the Ledger**, on the
9//! same grounds as `McpHealth` and `Toolkits` (`crates/wire/spec/Answer/McpHealth.lean`
10//! ยง8-34): which release is newest is a fact about this minute, and a
11//! recorded copy would still name last month's release as the newest
12//! one. It is also the reason this answer is never sent unasked - the
13//! city reaches the registry when somebody presses the button and at no
14//! other time.
15//!
16//! **Two renderings rather than the six numbers behind them.** A page
17//! draws `0.0.5-pre.260912` and `2026-09-12`; the arithmetic that
18//! decides which of two releases is newer is `kernel::Release`'s and
19//! stays there, so the client has nothing to get wrong and no second
20//! ordering rule to keep in step.
21
22use kernel::{AxError, ReleaseVerdict};
23use serde::{Deserialize, Serialize};
24
25/// One release, in the two spellings a person reads.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28pub struct ReleaseLine {
29 /// `0.0.5-pre.260912` - the version npm carries, which is also what
30 /// `bunx sprawling@<version>` takes.
31 pub version: String,
32 /// `2026-09-12`. A pre-alpha version number says almost nothing
33 /// about how old a tree is, and how old it is, is what its reader
34 /// most needs to know (CHANGELOG.md, opening note).
35 pub released: String,
36}
37
38/// Where this city stands against the release channel.
39///
40/// Exhaustive rather than a pair of optional fields: "you are running a
41/// release and here is where it stands", "you built this yourself, so
42/// there is nothing to compare" and "the registry could not be read"
43/// are three different things for a person to do next, and only the
44/// first of them is a version number.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
48pub enum ReleaseAnswer {
49 /// This binary came out of a release, and here is where it stands.
50 /// `verdict` judges `mine` against npm's line, which is what
51 /// `bunx sprawling` resolves.
52 Stands {
53 mine: ReleaseLine,
54 registries: Vec<RegistryNewest>,
55 verdict: ReleaseVerdict,
56 update: UpdateHint,
57 },
58 /// This binary was built from a working tree, so it is none of the
59 /// published releases. Reporting it as out of date would be
60 /// answering about a binary the person is not running.
61 Unreleased {
62 registries: Vec<RegistryNewest>,
63 update: UpdateHint,
64 },
65 /// The check could not be made. Carries why, because "could not
66 /// check" leaves a person believing they checked.
67 Refused { refusal: AxError },
68}
69
70/// What one registry said about the newest release
71/// (`crates/wire/spec/Answer/Release.lean` D24).
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
74pub struct RegistryNewest {
75 pub registry: Registry,
76 pub reading: RegistryReading,
77}
78
79/// A registry this project publishes to.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "snake_case")]
82#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
83pub enum Registry {
84 Npm,
85 CratesIo,
86}
87
88/// What one registry answered.
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
92pub enum RegistryReading {
93 Read { newest: ReleaseLine },
94 Refused { refusal: AxError },
95}
96
97/// How this binary was installed, which decides the command that
98/// updates it.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "snake_case")]
101#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
102pub enum InstallChannel {
103 Npm,
104 Cargo,
105 Archive,
106 Source,
107}
108
109/// The command a person runs to update, printed and never run: the
110/// channel that installed the binary owns updating it.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
113pub struct UpdateHint {
114 pub channel: InstallChannel,
115 /// `None` for a binary built from source, which nothing updates.
116 pub command: Option<String>,
117}