khive_runtime/
embedding.rs1use khive_storage::{SqlRow, SqlValue};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct EmbeddingModelRecord {
13 pub id: Uuid,
14 pub engine_name: String,
15 pub model_id: String,
16 pub key_version: String,
17 pub dim: u32,
18 pub output_dim: Option<u32>,
19 pub status: EmbeddingModelStatus,
20 pub activated_at: Option<i64>,
21 pub superseded_at: Option<i64>,
22 pub superseded_by: Option<Uuid>,
23 pub canonical_key: Vec<u8>,
24 pub created_at: i64,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum EmbeddingModelStatus {
31 Pending,
32 Active,
33 Superseded,
34 Archived,
35}
36
37impl EmbeddingModelStatus {
38 pub fn as_str(&self) -> &'static str {
40 match self {
41 Self::Pending => "pending",
42 Self::Active => "active",
43 Self::Superseded => "superseded",
44 Self::Archived => "archived",
45 }
46 }
47}
48
49impl EmbeddingModelRecord {
50 pub(crate) fn from_sql_row(row: &SqlRow) -> Option<Self> {
55 let required_text = |column: &str| match row.get(column) {
56 Some(SqlValue::Text(value)) => Some(value.clone()),
57 other => {
58 tracing::warn!(target: "khive_runtime::runtime", column, value = ?other, "skipping registry row: unexpected type");
59 None
60 }
61 };
62 let required_blob = |column: &str| match row.get(column) {
63 Some(SqlValue::Blob(value)) => Some(value.as_slice()),
64 _ => {
65 tracing::warn!(target: "khive_runtime::runtime", column, "skipping registry row: expected blob");
66 None
67 }
68 };
69 let uuid = |column: &str, bytes: &[u8]| match Uuid::from_slice(bytes) {
70 Ok(value) => Some(value),
71 Err(_) => {
72 tracing::warn!(target: "khive_runtime::runtime", column, "skipping registry row: invalid UUID bytes");
73 None
74 }
75 };
76 let dimension = |column: &str, value: i64| match u32::try_from(value) {
77 Ok(value) => Some(value),
78 Err(_) => {
79 tracing::warn!(target: "khive_runtime::runtime", column, dim = value, "skipping registry row: dim out of u32 range");
80 None
81 }
82 };
83
84 let engine_name = required_text("engine_name")?;
85 let model_id = required_text("model_id")?;
86 let key_version = required_text("key_version")?;
87 let dim = match row.get("dim") {
88 Some(SqlValue::Integer(value)) => dimension("dim", *value)?,
89 other => {
90 tracing::warn!(target: "khive_runtime::runtime", column = "dim", value = ?other, "skipping registry row: unexpected type");
91 return None;
92 }
93 };
94 let status = match required_text("status")?.as_str() {
95 "pending" => EmbeddingModelStatus::Pending,
96 "active" => EmbeddingModelStatus::Active,
97 "superseded" => EmbeddingModelStatus::Superseded,
98 "archived" => EmbeddingModelStatus::Archived,
99 _ => {
100 tracing::warn!(target: "khive_runtime::runtime", "skipping registry row: unknown lifecycle status");
101 return None;
102 }
103 };
104 let id = uuid("id", required_blob("id")?)?;
105 let canonical_key = required_blob("canonical_key")?.to_vec();
106 let created_at = match row.get("created_at") {
107 Some(SqlValue::Integer(value)) => *value,
108 _ => {
109 tracing::warn!(target: "khive_runtime::runtime", column = "created_at", "skipping registry row: expected integer");
110 return None;
111 }
112 };
113 let output_dim = match row.get("output_dim") {
114 Some(SqlValue::Null) => None,
115 Some(SqlValue::Integer(value)) => Some(dimension("output_dim", *value)?),
116 _ => {
117 tracing::warn!(target: "khive_runtime::runtime", column = "output_dim", "skipping registry row: expected nullable dimension");
118 return None;
119 }
120 };
121 let superseded_by = match row.get("superseded_by") {
122 Some(SqlValue::Null) => None,
123 Some(SqlValue::Blob(value)) => Some(uuid("superseded_by", value)?),
124 _ => {
125 tracing::warn!(target: "khive_runtime::runtime", column = "superseded_by", "skipping registry row: expected nullable UUID bytes");
126 return None;
127 }
128 };
129 let optional_timestamp = |column: &str| match row.get(column) {
130 Some(SqlValue::Integer(value)) => Some(*value),
131 _ => None,
132 };
133
134 Some(Self {
135 id,
136 engine_name,
137 model_id,
138 key_version,
139 dim,
140 output_dim,
141 status,
142 activated_at: optional_timestamp("activated_at"),
143 superseded_at: optional_timestamp("superseded_at"),
144 superseded_by,
145 canonical_key,
146 created_at,
147 })
148 }
149}