Skip to main content

omgbase_surface/
version.rs

1//! The `version` tool (`spec/surface` §4, 1.4): which engine and which
2//! versions — the serving binary's own, every component crate's, the spec
3//! versions they were built against, the database schema, the MCP protocol,
4//! the runtime, the build's commit and time.
5//!
6//! Who knows what. This crate knows the components (every `omgbase-*` crate
7//! and `oqx` are its dependencies, each with a `VERSION` and a
8//! `SPEC_VERSION`), the schema (the open store) and the protocol revision the
9//! catalog is written against. It does **not** know the binary: the `omgbase`
10//! crate depends on this one, not the other way round, so the host passes a
11//! [`BuildInfo`] — its version, its `spec/cli` version, the build's commit
12//! and time (`build.rs`), the compiler. Without one (a library embedding)
13//! the tool answers with this crate's own version and nulls, which is the
14//! shape the fixture runner records. Port of `packages/core/src/version.ts`.
15
16use omgbase_store::Store;
17use serde_json::{Map, Value as Json, json};
18
19/// The MCP protocol revision the catalog (§4) is written against and the
20/// `omgbase` binary serves by default when a client names none (the
21/// reference's SDK `LATEST_PROTOCOL_VERSION`). A client's own revision is
22/// echoed at `initialize`; the four methods the server speaks are identical
23/// across revisions.
24pub const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
25
26/// What the serving binary knows about itself.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct BuildInfo {
29    /// The binary's own version (the `omgbase` crate's; what `--version` prints).
30    pub version: String,
31    /// The `spec/cli/VERSION` the binary renders (`specs.cli`); `None` for a
32    /// host without a CLI (`null` on the wire).
33    pub cli_spec: Option<String>,
34    /// The build's short git revision; `None` when unknown.
35    pub commit: Option<String>,
36    /// The build's RFC 3339 time; `None` when unknown.
37    pub built: Option<String>,
38    /// `rustc --version` at build time; `None` → `"rustc"`.
39    pub rustc: Option<String>,
40}
41
42impl Default for BuildInfo {
43    /// The library embedding: this crate's own version, no CLI, no build.
44    fn default() -> Self {
45        Self {
46            version: crate::VERSION.to_owned(),
47            cli_spec: None,
48            commit: None,
49            built: None,
50            rustc: None,
51        }
52    }
53}
54
55/// Every omgbase crate the binary is built from, with its version — the
56/// binary's own first (its version is the host's), then the dependency
57/// crates; keys sorted bytewise.
58fn components(build: &BuildInfo) -> Json {
59    let mut m: Vec<(&str, &str)> = vec![
60        ("omgbase", build.version.as_str()),
61        ("omgbase-format", omgbase_format::VERSION),
62        ("omgbase-graph", omgbase_graph::VERSION),
63        ("omgbase-mutate", omgbase_mutate::VERSION),
64        ("omgbase-properties", omgbase_properties::VERSION),
65        ("omgbase-reconcile", omgbase_reconcile::VERSION),
66        ("omgbase-search", omgbase_search::VERSION),
67        ("omgbase-store", omgbase_store::VERSION),
68        ("omgbase-surface", crate::VERSION),
69        ("omgbase-sync", omgbase_sync::VERSION),
70        ("oqx", oqx::VERSION),
71    ];
72    m.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
73    Json::Object(
74        m.into_iter()
75            .map(|(k, v)| (k.to_owned(), Json::String(v.to_owned())))
76            .collect::<Map<_, _>>(),
77    )
78}
79
80/// The `spec/<x>/VERSION` each crate implements, in the order §4 lists them.
81fn specs(build: &BuildInfo) -> Json {
82    json!({
83        "oqx": oqx::LANGUAGE_VERSION,
84        "format": omgbase_format::SPEC_VERSION,
85        "reconcile": omgbase_reconcile::SPEC_VERSION,
86        "store": omgbase_store::SPEC_VERSION,
87        "properties": omgbase_properties::SPEC_VERSION,
88        "graph": omgbase_graph::SPEC_VERSION,
89        "search": omgbase_search::SPEC_VERSION,
90        "mutate": omgbase_mutate::SPEC_VERSION,
91        "sync": omgbase_sync::SPEC_VERSION,
92        "surface": crate::SPEC_VERSION,
93        "cli": build.cli_spec,
94    })
95}
96
97/// The `version` result: `{ engine, version, components, specs, schema,
98/// mcp, runtime, commit, built }`. `store` supplies `schema` (its
99/// `PRAGMA user_version`; `null` without one).
100pub fn version_info(store: Option<&Store>, build: &BuildInfo) -> crate::Result<Json> {
101    let schema = match store {
102        Some(s) => Json::from(s.user_version()?),
103        None => Json::Null,
104    };
105    let runtime = build.rustc.clone().unwrap_or_else(|| "rustc".to_owned());
106    Ok(json!({
107        "engine": "rust",
108        "version": build.version,
109        "components": components(build),
110        "specs": specs(build),
111        "schema": schema,
112        "mcp": { "protocol": MCP_PROTOCOL_VERSION },
113        "runtime": runtime,
114        "commit": build.commit,
115        "built": build.built,
116    }))
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn library_embedding_answers_with_its_own_version_and_nulls() {
125        let store = Store::open_in_memory().unwrap();
126        let v = version_info(Some(&store), &BuildInfo::default()).unwrap();
127        assert_eq!(v["engine"], "rust");
128        assert_eq!(v["version"], crate::VERSION);
129        assert_eq!(v["components"]["omgbase-surface"], crate::VERSION);
130        assert_eq!(v["components"]["omgbase"], crate::VERSION);
131        assert_eq!(v["components"]["oqx"], oqx::VERSION);
132        let keys: Vec<&String> = v["components"].as_object().unwrap().keys().collect();
133        let mut sorted = keys.clone();
134        sorted.sort_by(|a, b| a.as_bytes().cmp(b.as_bytes()));
135        assert_eq!(keys, sorted);
136        assert_eq!(keys.len(), 11);
137        assert_eq!(v["specs"]["surface"], crate::SPEC_VERSION);
138        assert_eq!(v["specs"]["store"], omgbase_store::SPEC_VERSION);
139        assert_eq!(v["specs"]["cli"], Json::Null);
140        assert_eq!(
141            v["specs"].as_object().unwrap().keys().collect::<Vec<_>>(),
142            [
143                "oqx",
144                "format",
145                "reconcile",
146                "store",
147                "properties",
148                "graph",
149                "search",
150                "mutate",
151                "sync",
152                "surface",
153                "cli"
154            ]
155        );
156        assert_eq!(v["schema"], omgbase_store::schema::SCHEMA_VERSION);
157        assert_eq!(v["mcp"], json!({ "protocol": MCP_PROTOCOL_VERSION }));
158        assert_eq!(v["runtime"], "rustc");
159        assert_eq!(v["commit"], Json::Null);
160        assert_eq!(v["built"], Json::Null);
161        assert_eq!(
162            v.as_object().unwrap().keys().collect::<Vec<_>>(),
163            [
164                "engine",
165                "version",
166                "components",
167                "specs",
168                "schema",
169                "mcp",
170                "runtime",
171                "commit",
172                "built"
173            ]
174        );
175        let none = version_info(None, &BuildInfo::default()).unwrap();
176        assert_eq!(none["schema"], Json::Null);
177    }
178
179    #[test]
180    fn a_host_supplies_its_own_identity() {
181        let build = BuildInfo {
182            version: "9.9.9".to_owned(),
183            cli_spec: Some("1.1".to_owned()),
184            commit: Some("abc1234".to_owned()),
185            built: Some("2026-09-27T00:00:00Z".to_owned()),
186            rustc: Some("rustc 1.97.0".to_owned()),
187        };
188        let v = version_info(None, &build).unwrap();
189        assert_eq!(v["version"], "9.9.9");
190        assert_eq!(v["components"]["omgbase"], "9.9.9");
191        assert_eq!(v["specs"]["cli"], "1.1");
192        assert_eq!(v["runtime"], "rustc 1.97.0");
193        assert_eq!(v["commit"], "abc1234");
194        assert_eq!(v["built"], "2026-09-27T00:00:00Z");
195    }
196}