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>
136 lines
3.8 KiB
Rust
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(())
|
|
}
|
|
}
|