Skip to content

Commit 244b7fd

Browse files
tcconnallyclaude
andcommitted
fix: repair rotted grpc feature caught by the new CI lane (#354)
The grpc feature did not compile at all: prost 0.14 vs tonic 0.13 Message mismatch, tokio-stream missing from the feature list, and src/grpc.rs drifted behind the Database/models APIs (Entity/RecallParams fields, state_set signature, Stats field names, health_check, private get_entity_by_id, moved-value in recall, and remember inserting an empty id). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 7b7a67d commit 244b7fd

3 files changed

Lines changed: 57 additions & 54 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,14 @@ ort = { version = "2.0.0-rc.12", optional = true, features = ["download-binaries
4545
# defaults) so tokenizer behavior is otherwise unchanged. (#222)
4646
tokenizers = { version = "0.23", optional = true, default-features = false, features = ["onig", "progressbar"] }
4747
ndarray = { version = "0.17", optional = true } # MUST match the ndarray version ort rc.12 depends on, else our arrays are a different crate-version type and TensorArrayData (from_array_view) won't accept them (#212)
48+
# prost/prost-types MUST stay on the same minor as tonic's own prost dep
49+
# (tonic 0.13 -> prost 0.13): tonic-build's generated code derives OUR prost's
50+
# `Message`, but tonic's codec requires ITS prost's `Message` — mixing 0.14
51+
# with tonic 0.13 fails every RPC with "trait bound `X: prost::message::Message`
52+
# is not satisfied". Caught by the grpc CI lane (#354).
4853
tonic = { version = "0.13", optional = true, features = ["transport"] }
49-
prost = { version = "0.14", optional = true }
50-
prost-types = { version = "0.14", optional = true }
54+
prost = { version = "0.13", optional = true }
55+
prost-types = { version = "0.13", optional = true }
5156
tokio-stream = { version = "0.1", optional = true }
5257

5358
# Optional: local multimodal document text extraction (#236). DOCX via `zip` +
@@ -64,7 +69,11 @@ pdf-extract = { version = "0.7", optional = true }
6469
# Build a lean binary without it via `cargo build --no-default-features`.
6570
default = ["bundled-embeddings"]
6671
bundled-embeddings = ["ort", "tokenizers", "ndarray"]
67-
grpc = ["tonic", "prost", "prost-types", "tonic-build"]
72+
# tokio-stream must be in this list: src/grpc.rs names it for the server
73+
# streaming types (WatchJournalStream/StreamContextStream). It was optional
74+
# but never wired to the feature, so the grpc build failed with
75+
# "unresolved module or unlinked crate `tokio_stream`" (#354).
76+
grpc = ["tonic", "prost", "prost-types", "tokio-stream", "tonic-build"]
6877
# Local document text extraction for mimir_ingest_file (DOCX/PDF). Opt-in.
6978
multimodal = ["zip", "pdf-extract"]
7079

src/grpc.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub mod grpc {
1212
tonic::include_proto!("mneme.v1");
1313

1414
use std::sync::{Arc, Mutex};
15-
use tonic::{Request, Response, Status, Streaming};
15+
use tonic::{Request, Response, Status};
1616

1717
use crate::db::Database;
1818
use crate::models;
@@ -62,8 +62,13 @@ pub mod grpc {
6262
async fn remember(&self, req: Request<RememberRequest>) -> Result<Response<RememberResponse>, Status> {
6363
let r = req.into_inner();
6464
with_db(self, |db| {
65+
// Same id convention as the MCP surface (handle_remember):
66+
// db.remember does NOT generate ids — an empty id here would be
67+
// inserted verbatim, producing an entity unreachable by id.
68+
let raw_id = uuid::Uuid::new_v4().to_string().replace('-', "");
69+
let id = format!("mem-{}", &raw_id[..12.min(raw_id.len())]);
6570
let entity = models::Entity {
66-
id: String::new(),
71+
id,
6772
category: r.category,
6873
key: r.key,
6974
body_json: r.body_json,
@@ -86,6 +91,10 @@ pub mod grpc {
8691
visibility: r.visibility,
8792
created_at_unix_ms: crate::db::now_ms(),
8893
last_accessed_unix_ms: crate::db::now_ms(),
94+
follow_count: 0,
95+
miss_count: 0,
96+
follow_rate: 0.0,
97+
efficacy_status: "unverified".to_string(),
8998
embedding: None,
9099
};
91100
let (id, action) = db.remember(&entity)?;
@@ -111,22 +120,28 @@ pub mod grpc {
111120
preview_cap: r.preview_cap,
112121
always_on: r.always_on,
113122
content_weight: r.content_weight,
123+
trust_weight: 0.0,
114124
diversity_halving: r.diversity_halving,
115125
diversity_per_query_share: 0.0,
126+
recency_half_life_secs: None,
116127
workspace_hash: r.workspace_hash,
117128
agent_id: r.agent_id,
118129
visibility: r.visibility,
130+
layer: None,
131+
reinforce: false,
119132
};
120133
let entities = db.recall(&params)?;
121-
let items = entities.into_iter().map(|e| entity_to_proto(&e)).collect();
122-
Ok(Response::new(RecallResponse { items, total: items.len() as i64 }))
134+
let items: Vec<EntityMessage> =
135+
entities.iter().map(entity_to_proto).collect();
136+
let total = items.len() as i64;
137+
Ok(Response::new(RecallResponse { items, total }))
123138
})
124139
}
125140

126141
async fn get_entity(&self, req: Request<GetEntityRequest>) -> Result<Response<EntityMessage>, Status> {
127142
let r = req.into_inner();
128143
with_db(self, |db| {
129-
let entity = db.get_entity_by_id(&r.id)
144+
let entity = db.get_entity_by_id_public(&r.id)
130145
.map_err(|_| Status::not_found("entity not found"))?
131146
.ok_or_else(|| Status::not_found("entity not found"))?;
132147
Ok(Response::new(entity_to_proto(&entity)))
@@ -181,7 +196,15 @@ pub mod grpc {
181196
async fn state_set(&self, req: Request<StateSetRequest>) -> Result<Response<StateSetResponse>, Status> {
182197
let r = req.into_inner();
183198
with_db(self, |db| {
184-
db.state_set(&r.key, &r.value_json, r.ttl_seconds.map(|t| t as i64))?;
199+
let now = crate::db::now_ms();
200+
let entry = models::StateEntry {
201+
key: r.key,
202+
value_json: r.value_json,
203+
// Same TTL convention as the MCP surface (handle_state_set).
204+
expires_at_unix_ms: r.ttl_seconds.map(|ttl| now + (ttl as i64) * 1000),
205+
created_at_unix_ms: now,
206+
};
207+
db.state_set(&entry)?;
185208
Ok(Response::new(StateSetResponse { ok: true }))
186209
})
187210
}
@@ -198,18 +221,17 @@ pub mod grpc {
198221
// ── Ops ──
199222
async fn health(&self, _req: Request<HealthRequest>) -> Result<Response<HealthResponse>, Status> {
200223
with_db(self, |db| {
201-
db.health()?;
202-
Ok(Response::new(HealthResponse { healthy: true }))
224+
Ok(Response::new(HealthResponse { healthy: db.health_check() }))
203225
})
204226
}
205227
async fn stats(&self, _req: Request<StatsRequest>) -> Result<Response<StatsResponse>, Status> {
206228
with_db(self, |db| {
207229
let s = db.stats()?;
208230
Ok(Response::new(StatsResponse {
209231
total_entities: s.total_entities,
210-
total_journal: s.total_journal,
211-
total_state: s.total_state,
212-
db_size_bytes: s.db_size_bytes,
232+
total_journal: s.total_journal_events,
233+
total_state: s.total_state_entries,
234+
db_size_bytes: s.db_file_size_bytes as i64,
213235
}))
214236
})
215237
}
@@ -378,6 +400,10 @@ pub mod grpc {
378400
visibility: "workspace".to_string(),
379401
created_at_unix_ms: 1,
380402
last_accessed_unix_ms: 2,
403+
follow_count: 0,
404+
miss_count: 0,
405+
follow_rate: 0.0,
406+
efficacy_status: "unverified".to_string(),
381407
embedding: None,
382408
};
383409
let p = entity_to_proto(&e);

0 commit comments

Comments
 (0)