Skip to content

Commit 7d1c4ba

Browse files
hyperpolymathclaude
andcommitted
feat(vql-ut): Add TypeLL pre-flight validation before query execution
QueryExecutor now calls TypeLL /api/v1/vql-ut/check before executing queries, validating that the query meets its declared safety level. Falls back gracefully if TypeLL is unreachable (3s timeout). Configurable via TYPELL_URL env var. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bd6c758 commit 7d1c4ba

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

Cargo.lock

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

src/rust/vql_ut.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,118 @@ impl QueryExecutor {
364364
}
365365
}
366366

367+
/// Validate a query against TypeLL's VQL-UT 10-level type checker.
368+
///
369+
/// Calls the TypeLL server at localhost:7800 to verify that the query
370+
/// meets its declared safety level. Returns the verified level and any
371+
/// diagnostics. Falls back gracefully if TypeLL is unreachable.
372+
async fn validate_with_typell(&self, query: &ProofQuery) -> Result<TypeLevel> {
373+
let typell_url = std::env::var("TYPELL_URL")
374+
.unwrap_or_else(|_| "http://localhost:7800".to_string());
375+
376+
let check_body = serde_json::json!({
377+
"query": serde_json::to_string(query).unwrap_or_default(),
378+
"modalities": ["Semantic", "Document", "Graph", "Provenance", "Temporal"],
379+
"result_fields": query.theorem_name.as_deref().map(|t| vec![t]).unwrap_or_default(),
380+
});
381+
382+
let client = reqwest::Client::builder()
383+
.timeout(std::time::Duration::from_secs(5))
384+
.build()
385+
.context("Failed to create HTTP client for TypeLL")?;
386+
387+
match client
388+
.post(format!("{}/api/v1/vql-ut/check", typell_url))
389+
.json(&check_body)
390+
.send()
391+
.await
392+
{
393+
Ok(resp) if resp.status().is_success() => {
394+
if let Ok(body) = resp.json::<serde_json::Value>().await {
395+
let max_level = body
396+
.get("safety_report")
397+
.and_then(|sr| sr.get("max_level"))
398+
.and_then(|ml| ml.as_u64())
399+
.unwrap_or(0);
400+
401+
let valid = body
402+
.get("valid")
403+
.and_then(|v| v.as_bool())
404+
.unwrap_or(true);
405+
406+
if !valid {
407+
let errors = body
408+
.get("rule_errors")
409+
.and_then(|e| e.as_array())
410+
.map(|arr| {
411+
arr.iter()
412+
.filter_map(|v| v.as_str())
413+
.collect::<Vec<_>>()
414+
.join("; ")
415+
})
416+
.unwrap_or_default();
417+
anyhow::bail!("TypeLL VQL-UT validation failed: {}", errors);
418+
}
419+
420+
info!(
421+
"TypeLL verified VQL-UT level {}/10 for {:?}",
422+
max_level, query.operation,
423+
);
424+
425+
// Map TypeLL's 1-based SafetyLevel to our 0-10 TypeLevel
426+
Ok(match max_level {
427+
0 | 1 => TypeLevel::Parse,
428+
2 => TypeLevel::Schema,
429+
3 => TypeLevel::TypeCompat,
430+
4 => TypeLevel::NullSafe,
431+
5 => TypeLevel::InjectionProof,
432+
6 => TypeLevel::ResultType,
433+
7 => TypeLevel::Cardinality,
434+
8 => TypeLevel::Effect,
435+
9 => TypeLevel::Temporal,
436+
10 => TypeLevel::Linear,
437+
_ => TypeLevel::Linear,
438+
})
439+
} else {
440+
debug!("TypeLL returned non-JSON response, falling back");
441+
Ok(query.level)
442+
}
443+
}
444+
Ok(resp) => {
445+
debug!(
446+
"TypeLL returned status {}, falling back to local validation",
447+
resp.status()
448+
);
449+
Ok(query.level)
450+
}
451+
Err(e) => {
452+
debug!("TypeLL unreachable ({}), falling back to local validation", e);
453+
Ok(query.level)
454+
}
455+
}
456+
}
457+
367458
/// Execute a typed query and return results.
459+
///
460+
/// Validates the query against TypeLL's 10-level type checker before
461+
/// execution. Falls back to local validation if TypeLL is unreachable.
368462
pub async fn execute(&self, query: &ProofQuery) -> Result<QueryResult> {
369463
info!(
370464
"Executing VQL-UT query: {:?} at level {:?}",
371465
query.operation, query.level,
372466
);
373467

468+
// Pre-flight: validate with TypeLL if available
469+
let verified_level = self.validate_with_typell(query).await.unwrap_or(query.level);
470+
471+
if (verified_level as u8) < (query.level as u8) {
472+
anyhow::bail!(
473+
"TypeLL verified level {:?} is below requested level {:?}",
474+
verified_level,
475+
query.level,
476+
);
477+
}
478+
374479
match query.operation {
375480
QueryOp::FindProof => self.execute_find_proof(query).await,
376481
QueryOp::FindSimilar => self.execute_find_similar(query).await,

0 commit comments

Comments
 (0)