stemedb/applications/disputed/app/src-tauri/src/stemedb.rs
jordan 02ecac9a07 fix: merge upstream 10 commits, fix DashMap deadlock, deterministic sim ingestion
Merged 10 upstream commits (MemTable, read-your-writes tests, feed endpoint,
security hardening, signed assertions, source registry, dashboard enhancements)
and fixed all test failures across the full workspace (2656/2656 passing).

Key fixes:
- fix(cluster): DashMap deadlock in swim.rs suspect_node/fail_node/alive_node
  - DashMap::get_mut RefMut + iter() on same map = non-reentrant write lock deadlock
  - Fix: extract clone in scoped block to drop RefMut before calling update_node_gauges()
  - 6 previously-hanging SWIM tests now pass in <2s
- fix(sim): replace background-task+polling ingestion with synchronous process_pending()
  - smoke_high_volume_simulation was CPU-starved under 2656 parallel tests
  - Removed ingestor.start() + wait_until_ingested() pattern throughout sim
  - All arena functions now call ingestor.process_pending() directly (deterministic)
- fix(test): v2 signature helper used wrong hash (rkyv vs canonical compute_content_hash_v2)
- fix(test): quota test signed "test" but v1 requires "subject:predicate" format
- fix(test): http_validation now accepts 400 for valid-format-but-invalid-crypto hex
- fix(test): scale_adaptive micro tier assertions updated (auto_promote upstream change)
- config: add nextest.toml with slow-timeout for background-task-tests group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 20:27:32 -07:00

136 lines
3.8 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::types::{Claim, ClaimCheck, ClaimStatus, RelatedClaim};
#[derive(Debug, Deserialize)]
pub struct QueryResponse {
pub assertions: Vec<AssertionResponse>,
pub conflict_score: Option<f32>,
}
#[derive(Debug, Deserialize)]
pub struct AssertionResponse {
pub subject: String,
pub predicate: String,
pub object: ObjectValue,
pub confidence: f32,
pub source_class: String,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ObjectValue {
Text(String),
Number(f64),
Boolean(bool),
Link(String),
Image(String),
}
impl ToString for ObjectValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ObjectValue::Text(s) => write!(f, "{}", s),
ObjectValue::Number(n) => write!(f, "{}", n),
ObjectValue::Boolean(b) => write!(f, "{}", b),
ObjectValue::Link(s) => write!(f, "{}", s),
ObjectValue::Image(s) => write!(f, "[Image: {}]", s),
}
}
}
pub struct Client {
url: String,
http: reqwest::Client,
}
impl Client {
pub fn new(url: String) -> Self {
Self {
url: url.trim_end_matches('/').to_string(),
http: reqwest::Client::new(),
}
}
pub async fn check_claim(&self, claim: &Claim) -> Result<ClaimCheck, String> {
let url = format!("{}/v1/query", self.url);
// Query using Skeptic lens to see conflicts
let response = self.http.get(&url)
.query(&[
("subject", &claim.subject),
("predicate", &claim.predicate),
("lens", &"Skeptic".to_string()),
])
.send()
.await
.map_err(|e| format!("Request failed: {}", e))?;
if !response.status().is_success() {
return Err(format!("API error: {}", response.status()));
}
let data: QueryResponse = response.json().await
.map_err(|e| format!("Parse error: {}", e))?;
let status = if data.assertions.is_empty() {
ClaimStatus::New
} else if let Some(score) = data.conflict_score {
if score > 0.5 {
ClaimStatus::Contradicts
} else {
ClaimStatus::Matches
}
} else {
ClaimStatus::Matches
};
let related = data.assertions.into_iter().map(|a| {
RelatedClaim {
claim: Claim {
subject: a.subject,
predicate: a.predicate,
object: a.object.to_string(),
confidence: a.confidence,
quote: "".to_string(),
source: Some(a.source_class),
},
relationship: "existing".to_string(),
source: "stemedb".to_string(),
}
}).collect();
Ok(ClaimCheck {
claim: claim.clone(),
status,
related,
})
}
pub async fn save_claim(&self, claim: &Claim) -> Result<(), String> {
let url = format!("{}/v1/assert", self.url);
let body = serde_json::json!({
"subject": claim.subject,
"predicate": claim.predicate,
"object": {
"type": "Text",
"value": claim.object
},
"confidence": claim.confidence,
"source_class": "Anecdotal", // Default for Disputed
});
let response = self.http.post(&url)
.json(&body)
.send()
.await
.map_err(|e| format!("Request failed: {}", e))?;
if !response.status().is_success() {
return Err(format!("API error: {}", response.status()));
}
Ok(())
}
}