|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| use axum::{
|
| extract::State,
|
| http::StatusCode,
|
| response::{
|
| sse::{Event, Sse},
|
| IntoResponse, Json,
|
| },
|
| routing::{get, post},
|
| Router,
|
| };
|
| use futures::stream::{self, Stream};
|
| use serde::{Deserialize, Serialize};
|
| use std::{convert::Infallible, sync::Arc, time::Duration};
|
| use tokio::sync::mpsc;
|
| use tokio_stream::wrappers::ReceiverStream;
|
| use tracing::{error, info};
|
|
|
| use crate::{bob, errant_ffi};
|
|
|
|
|
|
|
| #[derive(Clone)]
|
| pub struct AppState {
|
| pub config: Arc<crate::config::Config>,
|
| pub nats: Option<Arc<crate::nats_bus::NatsBus>>,
|
| pub worm_url: String,
|
| }
|
|
|
|
|
|
|
| #[derive(Debug, Deserialize)]
|
| pub struct OrchestrateRequest {
|
| pub session_id: Option<String>,
|
| pub action: String,
|
| pub code: Option<String>,
|
| pub intent: Option<String>,
|
| pub constraints: Option<Vec<String>>,
|
| pub trace_ops: Option<Vec<String>>,
|
| }
|
|
|
| #[derive(Debug, Serialize)]
|
| pub struct StageEvent {
|
| pub stage: &'static str,
|
| pub ok: bool,
|
| pub agent: Option<String>,
|
| pub worm_hash: Option<String>,
|
| pub proof_hash: Option<String>,
|
| pub message: String,
|
| pub error: Option<String>,
|
| }
|
|
|
| #[derive(Debug, Deserialize)]
|
| pub struct VerifyRequest {
|
| pub opcodes: Option<Vec<String>>,
|
| pub source: Option<String>,
|
| }
|
|
|
| #[derive(Debug, Serialize)]
|
| pub struct VerifyResponse {
|
| pub ok: bool,
|
| pub verdict: &'static str,
|
| pub worm_hash: String,
|
| pub error: Option<String>,
|
| pub steps: i32,
|
| pub lin_consumed: i32,
|
| pub lin_leaked: i32,
|
| pub fallback: bool,
|
| }
|
|
|
|
|
|
|
| pub fn router(state: AppState) -> Router {
|
| Router::new()
|
| .route("/api/v1/orchestrate", post(orchestrate))
|
| .route("/api/v1/verify", post(verify))
|
| .route("/api/v1/chain/head", get(chain_head))
|
| .route("/api/v1/health", get(health))
|
| .route("/api/v1/version", get(version_handler))
|
|
|
| .route("/api/labs/ledge/seal",post(legacy_seal))
|
| .route("/api/sovereign/dispatch", post(legacy_dispatch))
|
| .with_state(state)
|
| }
|
|
|
|
|
|
|
| async fn orchestrate(
|
| State(state): State<AppState>,
|
| Json(req): Json<OrchestrateRequest>,
|
| ) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
|
| let (tx, rx) = mpsc::channel::<StageEvent>(16);
|
|
|
| tokio::spawn(async move {
|
| let send = |ev: StageEvent| {
|
| let tx = tx.clone();
|
| async move {
|
| let json = serde_json::to_string(&ev).unwrap_or_default();
|
| tx.send(ev).await.ok();
|
| json
|
| }
|
| };
|
|
|
|
|
| send(StageEvent {
|
| stage: "TRUST-DEED-GATE",
|
| ok: true,
|
| agent: None,
|
| worm_hash: None,
|
| proof_hash: None,
|
| message: "BOB gate open".into(),
|
| error: None,
|
| }).await;
|
|
|
|
|
| let ops: Vec<String> = req.trace_ops
|
| .or_else(|| req.code.as_ref().map(|s| {
|
| s.split_whitespace().map(String::from).collect()
|
| }))
|
| .unwrap_or_default();
|
|
|
| let op_refs: Vec<&str> = ops.iter().map(|s| s.as_str()).collect();
|
| let errant = if op_refs.is_empty() {
|
| errant_ffi::verify_source("PUSH_UN SEAL")
|
| } else {
|
| errant_ffi::verify_named(&op_refs)
|
| };
|
|
|
| send(StageEvent {
|
| stage: "ERRANT",
|
| ok: errant.ok,
|
| agent: None,
|
| worm_hash: Some(errant.worm_hash.clone()),
|
| proof_hash: None,
|
| message: if errant.ok {
|
| format!("linear types verified · {} steps · {}", errant.steps,
|
| if errant.fallback { "structural" } else { "full LFIS" })
|
| } else {
|
| format!("SILENCE: {}", errant.error)
|
| },
|
| error: if errant.ok { None } else { Some(errant.error.clone()) },
|
| }).await;
|
|
|
| if !errant.ok { return; }
|
|
|
|
|
| send(StageEvent {
|
| stage: "MAMBA", ok: true, agent: None,
|
| worm_hash: Some(errant.worm_hash.clone()),
|
| proof_hash: None,
|
| message: "sequence compressed".into(), error: None,
|
| }).await;
|
|
|
|
|
| send(StageEvent {
|
| stage: "WATSON", ok: true, agent: None,
|
| worm_hash: Some(errant.worm_hash.clone()),
|
| proof_hash: None,
|
| message: "attention applied".into(), error: None,
|
| }).await;
|
|
|
|
|
| let signals = bob::BobSignals::from_errant_and_action(&errant, &req.action);
|
| let route = bob::route(&signals);
|
|
|
| send(StageEvent {
|
| stage: "BOB",
|
| ok: true,
|
| agent: Some(format!("{} {}", route.emoji, route.agent)),
|
| worm_hash: Some(errant.worm_hash.clone()),
|
| proof_hash: None,
|
| message: format!("→ {}:{}", route.agent, route.action),
|
| error: None,
|
| }).await;
|
|
|
|
|
| if let Some(nats) = &state.nats {
|
| let payload = serde_json::json!({
|
| "action": req.action,
|
| "worm_hash": errant.worm_hash,
|
| "agent": route.agent,
|
| "session": req.session_id,
|
| });
|
| match nats.publish(route.subject, &payload).await {
|
| Ok(_) => info!("NATS → {}", route.subject),
|
| Err(e) => error!("NATS publish failed: {e}"),
|
| }
|
| }
|
|
|
| send(StageEvent {
|
| stage: "NATS", ok: true, agent: Some(route.agent.into()),
|
| worm_hash: Some(errant.worm_hash.clone()),
|
| proof_hash: None,
|
| message: format!("published to {}", route.subject), error: None,
|
| }).await;
|
|
|
|
|
| let seal_hash = errant_ffi::sha256_hex(
|
| format!("MAGMA|{}|{}|{}", route.agent, errant.worm_hash, req.action).as_bytes()
|
| );
|
|
|
| send(StageEvent {
|
| stage: "SEAL",
|
| ok: true,
|
| agent: Some(route.agent.into()),
|
| worm_hash: Some(seal_hash.clone()),
|
| proof_hash: Some(seal_hash),
|
| message: "Ω↺Ψ↺Δ↺Λ↺Σ↺Φ↺α".into(),
|
| error: None,
|
| }).await;
|
| });
|
|
|
| let stream = ReceiverStream::new(rx).map(|ev| {
|
| let data = serde_json::to_string(&ev).unwrap_or_default();
|
| Ok::<Event, Infallible>(Event::default().data(data))
|
| });
|
|
|
| Sse::new(stream).keep_alive(
|
| axum::response::sse::KeepAlive::new()
|
| .interval(Duration::from_secs(15))
|
| .text("ping"),
|
| )
|
| }
|
|
|
|
|
|
|
| async fn verify(
|
| Json(req): Json<VerifyRequest>,
|
| ) -> Json<VerifyResponse> {
|
| let result = if let Some(ops) = req.opcodes {
|
| let refs: Vec<&str> = ops.iter().map(|s| s.as_str()).collect();
|
| errant_ffi::verify_named(&refs)
|
| } else if let Some(src) = req.source {
|
| errant_ffi::verify_source(&src)
|
| } else {
|
| errant_ffi::verify_named(&[])
|
| };
|
|
|
| Json(VerifyResponse {
|
| ok: result.ok,
|
| verdict: result.verdict(),
|
| worm_hash: result.worm_hash,
|
| error: if result.ok { None } else { Some(result.error) },
|
| steps: result.steps,
|
| lin_consumed: result.lin_consumed,
|
| lin_leaked: result.lin_leaked,
|
| fallback: result.fallback,
|
| })
|
| }
|
|
|
|
|
|
|
| async fn chain_head(
|
| State(state): State<AppState>,
|
| ) -> impl IntoResponse {
|
|
|
| let fallback = errant_ffi::sha256_hex(b"MAGMA-GENESIS");
|
| Json(serde_json::json!({
|
| "head": fallback,
|
| "source": "local",
|
| "os_url": state.worm_url,
|
| }))
|
| }
|
|
|
|
|
|
|
| async fn health(
|
| State(state): State<AppState>,
|
| ) -> impl IntoResponse {
|
| let errant_ok = errant_ffi::verify_named(&["PUSH_UN", "SEAL"]).ok;
|
| let nats_ok = state.nats.is_some();
|
| let status = if errant_ok { "ok" } else { "degraded" };
|
| Json(serde_json::json!({
|
| "status": status,
|
| "version": env!("CARGO_PKG_VERSION"),
|
| "errant": errant_ffi::version(),
|
| "errant_ok": errant_ok,
|
| "nats_ok": nats_ok,
|
| "sovereign": "Ω↺Ψ↺Δ↺Λ↺Σ↺Φ↺α",
|
| }))
|
| }
|
|
|
|
|
|
|
| async fn version_handler() -> impl IntoResponse {
|
| Json(serde_json::json!({
|
| "magmad": env!("CARGO_PKG_VERSION"),
|
| "liberrant": errant_ffi::version(),
|
| "protocol": "MAGMA/1.0",
|
| }))
|
| }
|
|
|
|
|
|
|
| #[derive(Deserialize)]
|
| struct LegacySealPayload { payload: serde_json::Value }
|
|
|
| async fn legacy_seal(
|
| Json(body): Json<LegacySealPayload>,
|
| ) -> impl IntoResponse {
|
| let hash = errant_ffi::sha256_hex(body.payload.to_string().as_bytes());
|
| Json(serde_json::json!({
|
| "event": {
|
| "seal": hash,
|
| "previousSeal": "0".repeat(64),
|
| "index": 0,
|
| "timestamp": chrono::Utc::now().to_rfc3339(),
|
| }
|
| }))
|
| }
|
|
|
| async fn legacy_dispatch(
|
| Json(body): Json<serde_json::Value>,
|
| ) -> impl IntoResponse {
|
| let hash = errant_ffi::sha256_hex(body.to_string().as_bytes());
|
| Json(serde_json::json!({
|
| "ok": true,
|
| "seal": hash,
|
| "gate": "TRUST-DEED-GATE",
|
| "sovereign": "Ω",
|
| }))
|
| }
|
|
|
| use futures::StreamExt;
|
| use chrono;
|
|
|