host.sock: give HostResponse a wire-schema version, warn on mismatch

This commit is contained in:
damocles 2026-09-07 12:46:58 +02:00 committed by mara
commit a32f37c3ba
2 changed files with 87 additions and 2 deletions

View file

@ -517,8 +517,27 @@ pub struct HiveUrls {
pub matrix: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
/// Wire-schema version for [`HostResponse`]. Bump only for a breaking
/// change — removing, renaming, or retyping an existing field; adding a
/// new `Option`/`skip_serializing_if` field (the pattern every field on
/// this struct already follows) is not breaking and needs no bump. A
/// client compares this against its own compiled-in copy of the constant
/// and warns (does not fail the request) on mismatch — see
/// `hivectl::client::version_mismatch_hint`, the one place that reads it.
pub const HOST_SOCK_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HostResponse {
/// Wire-schema version this response was built against, see
/// [`HOST_SOCK_VERSION`]. `#[serde(default)]` so a pre-version daemon
/// (or a hand-built test fixture) deserializes as `0` rather than
/// failing to parse — `0` reads as "older than any real version" to a
/// mismatch check, which is the correct answer for wire predating
/// this field. Always populated on the write side: every constructor
/// below routes through `Self::default()`, whose `Default` impl (not
/// derived — see below) sets this to the current version.
#[serde(default)]
pub version: u32,
pub ok: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
@ -568,6 +587,31 @@ pub struct HostResponse {
pub quota: Option<Vec<QuotaRow>>,
}
/// Hand-written rather than `#[derive(Default)]` for one reason:
/// `version` must default to [`HOST_SOCK_VERSION`], not `u32::default()`
/// (`0`). Every field below it is the same all-`None`/`false`/empty shape
/// `derive(Default)` would have produced — only `version` differs — and
/// every constructor in `impl HostResponse` builds through `..Self::default()`,
/// so this is the one place that has to get it right.
impl Default for HostResponse {
fn default() -> Self {
Self {
version: HOST_SOCK_VERSION,
ok: false,
error: None,
agents: None,
approvals: None,
urls: None,
agent_statuses: None,
agent_exists: None,
queued_dags: None,
nodes: None,
messages: Vec::new(),
quota: None,
}
}
}
impl HostResponse {
#[must_use]
pub fn success() -> Self {