aion_server/build_identity.rs
1//! What code is this server (#123).
2//!
3//! # The gap this closes
4//!
5//! A running server could not be asked what code it was. The only build
6//! identity anywhere was a startup log line carrying `CARGO_PKG_VERSION` — a
7//! crate version, which cannot distinguish two builds from different commits of
8//! the same version, and which is gone from the terminal by the time anyone
9//! needs it.
10//!
11//! That was measured, not supposed. On 2026-07-31 a live server's running image
12//! was found to differ from every preserved copy of "the same" binary — a
13//! different inode and a different size — and there was no way to establish
14//! which revision was actually serving. The mistake available at that moment
15//! was to restart it and call the result a restoration. **An artefact's
16//! identity is its content, never its path**; a path is a label recording where
17//! a file used to be. This is the one identity a server can state about itself.
18//!
19//! # 🔴 A STATUS CODE CANNOT VERIFY THIS ENDPOINT EXISTS
20//!
21//! The server mounts an ops-console SPA whose catch-all serves the app shell for
22//! any unmatched path, so that client-side routing works. A consequence, found
23//! the hard way on a live probe: **`GET /version` returns `200` on a server that
24//! has no such route** — the fallback answered, with `text/html`.
25//!
26//! So a probe that checks only the status code would report this endpoint
27//! present on every image ever built, including the ones it exists to
28//! distinguish. **An instrument that cannot fail is not an instrument.**
29//!
30//! Any probe for build identity must therefore assert on the BODY:
31//!
32//! ```text
33//! curl -s http://host/build | jq -e .commit # fails on an image without it
34//! ```
35//!
36//! not on `%{http_code}`. [`BuildIdentity`] is `application/json` with a
37//! required `commit` field precisely so that check is available and cheap. The
38//! same discipline as grepping an artifact for its own verdict rather than
39//! trusting a summarised exit.
40
41use serde::Serialize;
42
43/// The stamped absence, and the one word every unestablished field carries.
44///
45/// A word rather than an empty string or a `null`: an empty value reads as a
46/// formatting bug at the far end, where "unknown" reads as the measurement it
47/// is. Nothing here is ever defaulted to something that looks like an answer.
48const UNKNOWN: &str = "unknown";
49
50/// The source revision this binary was built from, stamped by `build.rs`.
51#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
52pub struct BuildIdentity {
53 /// Crate version. Coarse on its own — retained because it is the version an
54 /// operator matches against a release, a changelog, and a crates.io page.
55 pub version: &'static str,
56 /// Full commit hash of the source tree, or `"unknown"` for a build with no
57 /// repository (a crates.io tarball, a vendored copy, an export). Its rerun
58 /// triggers are registered in the build script, so unlike [`Self::dirty`]
59 /// it carries no staleness caveat.
60 pub commit: &'static str,
61 /// Whether uncommitted changes were observed when the build script last
62 /// ran, or `"unknown"` when it could not be established.
63 ///
64 /// **`"true"` is trustworthy. `"false"` means "no uncommitted change was
65 /// observed", NOT "the tree was provably clean."** Cargo cannot watch a
66 /// working tree, so a tree dirtied in a different workspace crate after
67 /// this script ran is reported clean. Stated here rather than left for a
68 /// reader to assume away — a limitation named is worth more than a boolean
69 /// that is silently wrong in one direction.
70 pub dirty: &'static str,
71 /// When the build script ran, RFC 3339, or `"unknown"`. Honours
72 /// `SOURCE_DATE_EPOCH`, so a reproducible build gets a reproducible stamp
73 /// rather than this being the one field that defeats reproducibility.
74 pub built_at: String,
75}
76
77impl BuildIdentity {
78 /// The identity compiled into this binary.
79 #[must_use]
80 pub fn current() -> Self {
81 Self {
82 version: env!("CARGO_PKG_VERSION"),
83 commit: env!("AION_BUILD_COMMIT"),
84 dirty: env!("AION_BUILD_DIRTY"),
85 built_at: built_at(),
86 }
87 }
88
89 /// The one-line form for the startup banner, so the identity is also in the
90 /// log a crashed server leaves behind — not only on an endpoint that needs
91 /// the server alive to answer.
92 #[must_use]
93 pub fn line(&self) -> String {
94 let dirty = if self.dirty == "true" { "-dirty" } else { "" };
95 format!(
96 "{}+{}{} built {}",
97 self.version, self.commit, dirty, self.built_at
98 )
99 }
100}
101
102/// The stamped epoch rendered as RFC 3339, or `"unknown"`.
103///
104/// The build script carries an integer across the boundary rather than a
105/// formatted string, so it needs no date dependency of its own; this is where
106/// that integer becomes readable. A value that does not parse, or does not
107/// name a real instant, degrades to the same honest absence as never having
108/// been established — the three are indistinguishable to an operator and
109/// pretending otherwise would invent precision.
110fn built_at() -> String {
111 let Ok(seconds) = env!("AION_BUILD_EPOCH").parse::<i64>() else {
112 return UNKNOWN.to_owned();
113 };
114 chrono::DateTime::from_timestamp(seconds, 0)
115 .map_or_else(|| UNKNOWN.to_owned(), |stamp| stamp.to_rfc3339())
116}
117
118#[cfg(test)]
119#[path = "build_identity_tests.rs"]
120mod tests;