Skip to main content

recall_wire/
health.rs

1//! `GET /health` — deliberately unauthenticated, so uptime tooling can poll
2//! it without holding the token.
3//!
4//! The `merge` object exists for one specific reason: every merge failure
5//! degrades to last-write-wins rather than rejecting the sync, which is the
6//! right behavior and also means a broken merge step is otherwise invisible.
7//! These fields are how that degraded state becomes observable from outside.
8
9use serde::{Deserialize, Serialize};
10
11/// Whether the server can actually perform a semantic merge.
12#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
13pub struct ClaudeCliStatus {
14    /// When the check last ran, in the crate's frozen timestamp format.
15    #[serde(default, skip_serializing_if = "String::is_empty")]
16    pub checked_at: String,
17    /// Whether the `claude` binary was found and runnable. [`None`] means
18    /// the check has not run yet.
19    pub available: Option<bool>,
20    /// Whether that binary has a usable login. [`None`] means unknown.
21    pub logged_in: Option<bool>,
22    /// Why the check failed, when it did.
23    #[serde(default, skip_serializing_if = "String::is_empty")]
24    pub error: String,
25}
26
27/// The most recent failed merge attempt.
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct MergeError {
30    /// The failure, already truncated to a sane length for display.
31    pub message: String,
32    /// When it happened, in the crate's frozen timestamp format.
33    pub at: String,
34}
35
36/// The `merge` object inside [`Health`].
37#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
38pub struct MergeStatus {
39    /// Whether merging is turned on at all (`RECALL_MERGE_ENABLED`).
40    pub enabled: bool,
41    /// Whether the local `claude` CLI can actually serve a merge.
42    pub claude_cli: ClaudeCliStatus,
43    /// When a merge last succeeded.
44    #[serde(default, skip_serializing_if = "String::is_empty")]
45    pub last_merge_at: String,
46    /// The last failure, if there has been one since startup.
47    pub last_merge_error: Option<MergeError>,
48}
49
50/// Body returned by `GET /health`.
51#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
52pub struct Health {
53    /// `"ok"` whenever the server can answer at all.
54    pub status: String,
55    /// The commit the running binary was built from, so a deploy can be
56    /// confirmed from outside.
57    pub git_commit: String,
58    /// When this process started, in the crate's frozen timestamp format.
59    pub started_at: String,
60    /// When any project last synced.
61    #[serde(default, skip_serializing_if = "String::is_empty")]
62    pub last_sync_at: String,
63    /// When the database was last backed up.
64    #[serde(default, skip_serializing_if = "String::is_empty")]
65    pub last_backup_at: String,
66    /// When a copy last reached somewhere the loss of this machine does not
67    /// reach, per the stamp `deploy/backup-offbox.sh` leaves behind.
68    ///
69    /// Reported separately from [`Health::last_backup_at`] because they
70    /// protect against different things, and only one of them survives the
71    /// disk. Empty when nothing has ever written the stamp, which is also
72    /// what "no off-box backup is configured" looks like — the two are
73    /// indistinguishable from here, and neither is an error.
74    #[serde(default, skip_serializing_if = "String::is_empty")]
75    pub last_offbox_at: String,
76    /// Whether semantic merge is working, or silently degraded.
77    pub merge: MergeStatus,
78}