flux_verify_api/api/
routes.rs1use axum::{
2 extract::State,
3 http::StatusCode,
4 response::Json,
5 routing::{get, post},
6 Router,
7};
8use std::sync::Arc;
9use std::time::Instant;
10use tokio::sync::Mutex;
11use uuid::Uuid;
12
13use crate::api::request::VerifyRequest;
14use crate::api::response::{HealthResponse, StatusResponse, VerifyResponse};
15use crate::compiler;
16use crate::config::Config;
17use crate::engine::vm::FluxVm;
18use crate::provenance::merkle;
19use crate::plato::client::PlatoClient;
20
21#[derive(Debug)]
22pub struct AppState {
23 pub config: Config,
24 pub total: u64,
25 pub proven: u64,
26 pub disproven: u64,
27 pub unknown: u64,
28 pub total_latency_ms: f64,
29}
30
31impl AppState {
32 pub fn new(config: Config) -> Self {
33 Self {
34 config,
35 total: 0,
36 proven: 0,
37 disproven: 0,
38 unknown: 0,
39 total_latency_ms: 0.0,
40 }
41 }
42}
43
44pub fn router() -> Router<Arc<Mutex<AppState>>> {
45 Router::new()
46 .route("/verify", post(verify))
47 .route("/status", get(status))
48 .route("/health", get(health))
49}
50
51async fn verify(
52 State(state): State<Arc<Mutex<AppState>>>,
53 Json(req): Json<VerifyRequest>,
54) -> Result<(StatusCode, Json<VerifyResponse>), (StatusCode, Json<serde_json::Value>)> {
55 let start = Instant::now();
56
57 let problem = compiler::parse_claim(&req.claim, &req.domain)
59 .map_err(|e| {
60 (
61 StatusCode::UNPROCESSABLE_ENTITY,
62 Json(serde_json::json!({ "error": e })),
63 )
64 })?;
65
66 let bytecodes = compiler::compile(&problem);
68
69 let mut vm = FluxVm::new();
71 let trace = vm.execute(&bytecodes);
72
73 let (verdict, confidence, counterexample) = vm.evaluate(&trace, &problem);
75
76 let proof_hash = merkle::hash_trace(&trace);
78
79 let plato_tile_id = {
81 let state_guard = state.lock().await;
82 if state_guard.config.plato_url.is_some() {
83 let client = PlatoClient::new(
84 state_guard.config.plato_url.clone().unwrap(),
85 state_guard.config.plato_token.clone(),
86 );
87 drop(state_guard);
88 let tile_id = format!("verification-{}", Uuid::new_v4().as_simple());
89 let _ = client.submit(&proof_hash, &verdict, &req.claim).await;
90 Some(tile_id)
91 } else {
92 None
93 }
94 };
95
96 let response = VerifyResponse {
97 status: verdict.clone(),
98 confidence,
99 trace,
100 counterexample,
101 proof_hash: format!("sha256:{}", proof_hash),
102 plato_tile_id,
103 };
104
105 let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
107 {
108 let mut s = state.lock().await;
109 s.total += 1;
110 s.total_latency_ms += elapsed_ms;
111 match verdict.as_str() {
112 "PROVEN" => s.proven += 1,
113 "DISPROVEN" => s.disproven += 1,
114 _ => s.unknown += 1,
115 }
116 }
117
118 let status_code = if verdict == "PROVEN" {
119 StatusCode::OK
120 } else {
121 StatusCode::OK };
123
124 Ok((status_code, Json(response)))
125}
126
127async fn status(
128 State(state): State<Arc<Mutex<AppState>>>,
129) -> Json<StatusResponse> {
130 let s = state.lock().await;
131 let avg = if s.total > 0 {
132 s.total_latency_ms / s.total as f64
133 } else {
134 0.0
135 };
136 Json(StatusResponse {
137 total_verifications: s.total,
138 proven: s.proven,
139 disproven: s.disproven,
140 unknown: s.unknown,
141 avg_latency_ms: avg,
142 })
143}
144
145async fn health() -> Json<HealthResponse> {
146 Json(HealthResponse {
147 status: "ok".into(),
148 version: "0.1.0".into(),
149 })
150}