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 /// The worker that merges queued jobs. Omitted while none is enrolled,
49 /// which is also when merges still run inside the server.
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub worker: Option<WorkerStatus>,
52 /// The merge queue. Omitted while no worker is enrolled.
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub queue: Option<QueueStatus>,
55}
56
57/// The `worker` object inside [`MergeStatus`].
58#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
59pub struct WorkerStatus {
60 /// When a worker last asked for a job, since this server started.
61 pub last_claim_at: Option<String>,
62 /// Its `User-Agent`, such as `recall-worker/0.4.2 (linux-x86_64)`.
63 pub agent: String,
64}
65
66/// The `queue` object inside [`MergeStatus`]: what is waiting, so a worker
67/// that stopped is noticed rather than silently leaving merges undone.
68#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
69pub struct QueueStatus {
70 /// Jobs waiting for a worker.
71 pub queued: u64,
72 /// Jobs a worker holds now.
73 pub leased: u64,
74 /// Jobs out of attempts, kept until retried.
75 pub failed: u64,
76 /// When the oldest waiting job was queued; `null` when none waits.
77 pub oldest_queued_at: Option<String>,
78}
79
80/// Body returned by `GET /health`.
81#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
82pub struct Health {
83 /// `"ok"` whenever the server can answer at all.
84 pub status: String,
85 /// The commit the running binary was built from, so a deploy can be
86 /// confirmed from outside.
87 pub git_commit: String,
88 /// When this process started, in the crate's frozen timestamp format.
89 pub started_at: String,
90 /// When any project last synced.
91 #[serde(default, skip_serializing_if = "String::is_empty")]
92 pub last_sync_at: String,
93 /// When the database was last backed up.
94 #[serde(default, skip_serializing_if = "String::is_empty")]
95 pub last_backup_at: String,
96 /// When a copy last reached somewhere the loss of this machine does not
97 /// reach, per the stamp `deploy/backup-offbox.sh` leaves behind.
98 ///
99 /// Reported separately from [`Health::last_backup_at`] because they
100 /// protect against different things, and only one of them survives the
101 /// disk. Empty when nothing has ever written the stamp, which is also
102 /// what "no off-box backup is configured" looks like — the two are
103 /// indistinguishable from here, and neither is an error.
104 #[serde(default, skip_serializing_if = "String::is_empty")]
105 pub last_offbox_at: String,
106 /// Whether semantic merge is working, or silently degraded.
107 pub merge: MergeStatus,
108}