Skip to main content

flux_verify_api/api/
routes.rs

1use 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    // Parse the claim into a constraint problem
58    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    // Compile to FLUX bytecodes
67    let bytecodes = compiler::compile(&problem);
68
69    // Execute on the VM
70    let mut vm = FluxVm::new();
71    let trace = vm.execute(&bytecodes);
72
73    // Determine verdict from the trace
74    let (verdict, confidence, counterexample) = vm.evaluate(&trace, &problem);
75
76    // Merkle hash the trace
77    let proof_hash = merkle::hash_trace(&trace);
78
79    // Optionally submit to PLATO
80    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    // Update stats
106    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 // Both proven and disproven are 200 — the status field tells you
122    };
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}