kindling_store/schema.rs
1//! Embedded copies of the cross-language schema contract.
2//!
3//! `schema/schema.sql` and `schema/version.json` at the repo root are the
4//! canonical contract shared with the TypeScript store. This crate vendors a
5//! COMMITTED copy of them under `crates/kindling-store/schema/` and embeds the
6//! vendored copy with `include_str!`. Vendoring keeps the files inside the
7//! crate directory so `cargo publish` packages them (the repo-root canonical
8//! source is outside the crate dir and would not be in the published tarball).
9//!
10//! The vendored copy is kept in lock-step with the canonical source by
11//! `scripts/sync-vendored-schema.sh`; a CI drift gate (the `vendored-schema`
12//! job in `.github/workflows/rust.yml`) re-runs the sync and fails on any
13//! uncommitted diff, so the crate can never silently drift from the contract.
14
15use std::sync::OnceLock;
16
17use serde::Deserialize;
18
19/// Canonical DDL — the state of the schema after all migrations.
20///
21/// Embedded from the vendored copy (`crates/kindling-store/schema/schema.sql`),
22/// kept in sync with the repo-root canonical `schema/schema.sql`.
23pub const SCHEMA_SQL: &str = include_str!("../schema/schema.sql");
24
25const VERSION_JSON: &str = include_str!("../schema/version.json");
26
27/// Machine-readable schema version metadata from `schema/version.json`.
28#[derive(Debug, Clone, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct SchemaVersion {
31 /// Current schema version; matches `PRAGMA user_version` in a migrated DB.
32 pub version: i64,
33 /// Oldest schema version this implementation can safely read.
34 pub min_compatible: i64,
35 /// Pinned FTS5 tokenizer — changing it is a breaking change.
36 pub fts_tokenizer: String,
37}
38
39/// The schema version this crate was compiled against.
40pub fn schema_version() -> &'static SchemaVersion {
41 static VERSION: OnceLock<SchemaVersion> = OnceLock::new();
42 VERSION.get_or_init(|| {
43 serde_json::from_str(VERSION_JSON).expect("schema/version.json is valid JSON")
44 })
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn version_json_parses() {
53 let v = schema_version();
54 assert!(v.version >= v.min_compatible);
55 assert_eq!(v.fts_tokenizer, "porter unicode61");
56 }
57
58 #[test]
59 fn schema_sql_pins_user_version() {
60 let v = schema_version();
61 let pragma = format!("PRAGMA user_version = {};", v.version);
62 assert!(
63 SCHEMA_SQL.contains(&pragma),
64 "schema.sql must set PRAGMA user_version to match version.json"
65 );
66 }
67
68 #[test]
69 fn schema_sql_pins_tokenizer() {
70 let v = schema_version();
71 let tokenize = format!("tokenize='{}'", v.fts_tokenizer);
72 assert!(
73 SCHEMA_SQL.contains(&tokenize),
74 "schema.sql FTS tables must use the tokenizer pinned in version.json"
75 );
76 }
77}