coreason-runtime 0.1.0

Kinetic Plane execution engine for the CoReason Tripartite Cybernetic Manifold
Documentation
// Copyright (c) 2026 CoReason, Inc.
// All rights reserved.

//! State management API routes.
//!
//! Replaces `coreason_runtime/api/state_router.py`.
//! Handles CRDT state sync, state reads, and manifest execution.

use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

use crate::GatewayState;

#[derive(Serialize, Deserialize, Debug)]
struct StateSyncResponse {
    status: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct StateReadResponse {
    workflow_id: String,
    state: serde_json::Value,
}

#[derive(Serialize, Deserialize, Debug)]
struct ExecuteManifestResponse {
    status: String,
    result: serde_json::Value,
}

/// POST /api/v1/state/sync/:workflow_id
///
/// Sync state by signaling the workflow with a CRDT delta.
/// Proxies to Temporal sidecar for workflow signal delivery.
async fn sync_state(
    State(state): State<Arc<GatewayState>>,
    Path(workflow_id): Path<String>,
    Json(payload): Json<serde_json::Value>,
) -> Response {
    // Validate payload size (256 KiB limit)
    let raw = serde_json::to_vec(&payload).unwrap_or_default();
    if raw.len() > 256 * 1024 {
        return (
            StatusCode::PAYLOAD_TOO_LARGE,
            Json(serde_json::json!({"detail": "Payload Too Large"})),
        )
            .into_response();
    }

    let url = format!("{}/api/v1/state/sync/{}", state.sidecar_url, workflow_id);
    match ureq::post(&url).send_json(&payload) {
        Ok(res) => {
            let status = StatusCode::from_u16(res.status()).unwrap_or(StatusCode::OK);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(ureq::Error::Status(code, res)) => {
            let status = StatusCode::from_u16(code).unwrap_or(StatusCode::BAD_REQUEST);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(serde_json::json!({
                "status": "error",
                "message": format!("Failed to proxy state delta sync: {}", e),
            })),
        )
            .into_response(),
    }
}

/// GET /api/v1/state/sync/:workflow_id
///
/// Read the current state of a workflow by querying Temporal.
async fn read_state(
    State(state): State<Arc<GatewayState>>,
    Path(workflow_id): Path<String>,
) -> Response {
    let url = format!("{}/api/v1/state/sync/{}", state.sidecar_url, workflow_id);
    match ureq::get(&url).call() {
        Ok(res) => {
            let status = StatusCode::from_u16(res.status()).unwrap_or(StatusCode::OK);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(ureq::Error::Status(code, res)) => {
            let status = StatusCode::from_u16(code).unwrap_or(StatusCode::NOT_FOUND);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(serde_json::json!({
                "status": "error",
                "message": format!("Failed to query state from sidecar: {}", e),
            })),
        )
            .into_response(),
    }
}

/// POST /api/v1/state/execute
///
/// Execute a complete Cognitive Topology manifest via the sidecar.
async fn execute_manifest(
    State(state): State<Arc<GatewayState>>,
    Json(payload): Json<serde_json::Value>,
) -> Response {
    let url = format!("{}/api/v1/state/execute", state.sidecar_url);
    match ureq::post(&url).send_json(&payload) {
        Ok(res) => {
            let status = StatusCode::from_u16(res.status()).unwrap_or(StatusCode::OK);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(ureq::Error::Status(code, res)) => {
            let status = StatusCode::from_u16(code).unwrap_or(StatusCode::BAD_REQUEST);
            let body: serde_json::Value = res.into_json().unwrap_or(serde_json::Value::Null);
            (status, Json(body)).into_response()
        }
        Err(e) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(serde_json::json!({
                "status": "error",
                "message": format!("Failed to proxy manifest execution: {}", e),
            })),
        )
            .into_response(),
    }
}

/// Build the state management router.
pub fn router() -> Router<Arc<GatewayState>> {
    Router::new()
        .route(
            "/api/v1/state/sync/:workflow_id",
            post(sync_state).get(read_state),
        )
        .route("/api/v1/state/execute", post(execute_manifest))
}