Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ if curl -fsSL "$CHECKSUMS_URL" -o "${TMPDIR}/checksums.txt" 2>/dev/null; then
fi
fi
else
echo "No checksums.txt available, skipping verification."
echo "WARNING: No checksums.txt available, binary integrity could not be verified." >&2
fi

tar -xzf "${TMPDIR}/${TARBALL}" -C "$TMPDIR"
Expand Down
25 changes: 17 additions & 8 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,11 @@ impl Database {
// Convert map to sorted vec
let mut search_results: Vec<SearchResult> =
results_map.into_values().map(|(r, _)| r).collect();
search_results.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap());
search_results.sort_by(|a, b| {
b.similarity
.partial_cmp(&a.similarity)
.unwrap_or(std::cmp::Ordering::Equal)
});
search_results.truncate(limit);

Ok(search_results)
Expand Down Expand Up @@ -598,7 +602,11 @@ impl Database {
}

// Sort by similarity descending
conflicts.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap());
conflicts.sort_by(|a, b| {
b.similarity
.partial_cmp(&a.similarity)
.unwrap_or(std::cmp::Ordering::Equal)
});

Ok(conflicts)
}
Expand Down Expand Up @@ -753,12 +761,7 @@ impl Database {
item.embedding = self.embedder.embed(&embedding_text)?;
item.expires_at = Some(expires_at);

// Delete then re-insert with new expiration
table
.delete(&format!("id = '{}'", sanitize_sql_string(id)))
.await
.map_err(|e| SedimentError::Database(format!("Delete for expire failed: {}", e)))?;

// Insert-before-delete to avoid data loss on crash
let batch = item_to_batch(&item)?;
let batches = RecordBatchIterator::new(vec![Ok(batch)], Arc::new(item_schema()));
table
Expand All @@ -767,6 +770,11 @@ impl Database {
.await
.map_err(|e| SedimentError::Database(format!("Re-insert for expire failed: {}", e)))?;

table
.delete(&format!("id = '{}'", sanitize_sql_string(id)))
.await
.map_err(|e| SedimentError::Database(format!("Delete for expire failed: {}", e)))?;

Ok(())
}

Expand Down Expand Up @@ -823,6 +831,7 @@ impl Database {
};

let now = Utc::now().timestamp();
// now is a system-generated i64 timestamp, no string sanitization needed
let filter = format!("expires_at IS NOT NULL AND expires_at < {}", now);

// Count how many will be deleted
Expand Down
2 changes: 2 additions & 0 deletions src/embedder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ fn download_model(model_id: &str) -> Result<(PathBuf, PathBuf, PathBuf)> {

// TOFU: verify model integrity
verify_tofu_hash(&model_path, "model.safetensors")?;
verify_tofu_hash(&tokenizer_path, "tokenizer.json")?;
verify_tofu_hash(&config_path, "config.json")?;

Ok((model_path, tokenizer_path, config_path))
}
Expand Down
10 changes: 9 additions & 1 deletion src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ impl GraphStore {
return Ok(());
}

let item_ids = if item_ids.len() > 3 {
&item_ids[..3]
} else {
item_ids
};

let now = chrono::Utc::now().timestamp();

for i in 0..item_ids.len() {
Expand Down Expand Up @@ -336,7 +342,9 @@ impl GraphStore {

// Create edges on the new node
for (neighbor, strength, rel_type, _) in &edges {
let _ = self.add_related_edge(to_id, neighbor, *strength, rel_type);
if let Err(e) = self.add_related_edge(to_id, neighbor, *strength, rel_type) {
tracing::warn!("transfer edge to {} failed: {}", neighbor, e);
}
}

Ok(())
Expand Down
15 changes: 10 additions & 5 deletions src/mcp/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,18 @@ fn handle_call_tool(
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let window = ctx.rate_limit_window.load(Ordering::Relaxed);
let window = ctx.rate_limit_window.load(Ordering::SeqCst);
if now_ms - window > 60_000 {
// Reset window
ctx.rate_limit_window.store(now_ms, Ordering::Relaxed);
ctx.rate_limit_count.store(1, Ordering::Relaxed);
// Reset window (use compare_exchange to avoid racing with another reset)
if ctx
.rate_limit_window
.compare_exchange(window, now_ms, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
ctx.rate_limit_count.store(1, Ordering::SeqCst);
}
} else {
let count = ctx.rate_limit_count.fetch_add(1, Ordering::Relaxed) + 1;
let count = ctx.rate_limit_count.fetch_add(1, Ordering::SeqCst) + 1;
if count > MAX_CALLS_PER_MINUTE {
let result =
super::protocol::CallToolResult::error("Rate limit exceeded, try again later");
Expand Down
8 changes: 6 additions & 2 deletions src/mcp/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn get_tools() -> Vec<Tool> {
},
"replace": {
"type": "string",
"description": "ID of an existing item to replace (atomically delete before storing)"
"description": "ID of an existing item to replace (stores new item first, then deletes old)"
},
"related": {
"type": "array",
Expand Down Expand Up @@ -594,7 +594,11 @@ pub async fn recall_pipeline(
result.similarity = (base_score * trust_bonus).min(1.0);
}

results.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap());
results.sort_by(|a, b| {
b.similarity
.partial_cmp(&a.similarity)
.unwrap_or(std::cmp::Ordering::Equal)
});
}

// Record access
Expand Down