Skip to main content

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;