AkurAI Build
Menu

Public repository / main

AkurAI-Router / src/routing.rs

Latest change 16baa88e7934816edd8435507847f72fc196163f - OLI-301: claude-cli runner backend + static routes.conf (stale window 0) by Olafur Bui

use std::fs::{self, OpenOptions};
use std::io::Write;
use std::os::unix::fs::PermissionsExt;

use crate::auth::ApiActor;
use crate::config::{
    Config, PROVIDER_EMBEDDINGS, canonical_provider_id, load_embedding_config, load_providers,
};
use crate::http::Request;
use crate::json::Json;
use crate::util::{now_secs, random_hex};

#[derive(Clone, Debug)]
pub struct Route {
    pub installation_id: String,
    pub organization_id: String,
    pub workspace_id: String,
    pub app_id: String,
    pub key_id: String,
    pub provider_id: String,
    pub installed: bool,
    pub entitled: bool,
}

#[derive(Clone, Debug)]
pub struct ControlPlane {
    pub revision: String,
    pub status: &'static str,
    routes: Vec<Route>,
}

pub enum ResolveError {
    Unavailable,
    Denied,
}

pub fn ensure_route_file(cfg: &Config) -> Result<(), String> {
    let path = cfg.data_dir.join("routes.conf");
    if path.exists() {
        return Ok(());
    }
    atomic_write(
        &path,
        "# revision=unconfigured\n# updated_at=0\n# installation_id|organization_id|workspace_id|app_id|key_id|provider_id|installed|entitled\n",
    )
}

/// A stale window of 0 marks routes.conf as static (hand-managed), so it never expires.
fn is_fresh(updated_at: u64, now: u64, stale_secs: u64) -> bool {
    stale_secs == 0 || now.saturating_sub(updated_at) <= stale_secs
}

pub fn load(cfg: &Config) -> ControlPlane {
    let path = cfg.data_dir.join("routes.conf");
    let Ok(text) = fs::read_to_string(path) else {
        return unknown();
    };
    let mut revision = None;
    let mut updated_at = None;
    let mut routes = Vec::new();
    for line in text.lines().map(str::trim) {
        if let Some(value) = line.strip_prefix("# revision=") {
            revision = valid_id(value).then(|| value.to_string());
            continue;
        }
        if let Some(value) = line.strip_prefix("# updated_at=") {
            updated_at = value.parse::<u64>().ok();
            continue;
        }
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let fields: Vec<_> = line.split('|').collect();
        if fields.len() != 8 || fields.iter().take(6).any(|field| !valid_id(field)) {
            return unknown();
        }
        let (Some(installed), Some(entitled)) = (parse_bool(fields[6]), parse_bool(fields[7]))
        else {
            return unknown();
        };
        let provider_id = canonical_provider_id(fields[5]);
        if !["codex", "claude", "opencode-go", PROVIDER_EMBEDDINGS].contains(&provider_id.as_str())
        {
            return unknown();
        }
        let route = Route {
            installation_id: fields[0].to_string(),
            organization_id: fields[1].to_string(),
            workspace_id: fields[2].to_string(),
            app_id: fields[3].to_string(),
            key_id: fields[4].to_string(),
            provider_id,
            installed,
            entitled,
        };
        if routes.iter().any(|existing: &Route| {
            existing.organization_id == route.organization_id
                && existing.workspace_id == route.workspace_id
                && existing.app_id == route.app_id
                && existing.key_id == route.key_id
        }) {
            return unknown();
        }
        routes.push(route);
    }
    let Some(revision) = revision else {
        return unknown();
    };
    let Some(updated_at) = updated_at else {
        return unknown();
    };
    if !is_fresh(updated_at, now_secs(), cfg.control_plane_stale_secs) {
        return unknown();
    }
    ControlPlane {
        revision,
        status: if routes.iter().all(|route| route.installed && route.entitled) {
            "ready"
        } else {
            "degraded"
        },
        routes,
    }
}

pub fn resolve(
    plane: &ControlPlane,
    req: &Request,
    actor: &ApiActor,
) -> Result<Route, ResolveError> {
    if plane.status == "unknown" {
        return Err(ResolveError::Unavailable);
    }
    let (Some(organization_id), Some(workspace_id), Some(app_id)) = (
        req.header("x-akurai-organization"),
        req.header("x-akurai-workspace"),
        req.header("x-akurai-app"),
    ) else {
        return Err(ResolveError::Denied);
    };
    plane
        .routes
        .iter()
        .find(|route| {
            route.organization_id == organization_id
                && route.workspace_id == workspace_id
                && route.app_id == app_id
                && route.key_id == actor.key_id
                && route.installed
                && route.entitled
        })
        .cloned()
        .ok_or(ResolveError::Denied)
}

pub fn health_json(plane: &ControlPlane) -> String {
    let mut body = Json::object();
    body.set("app", Json::String("akurai-router".to_string()));
    body.set("status", Json::String(plane.status.to_string()));
    body.set(
        "configuration_revision",
        Json::String(plane.revision.clone()),
    );
    body.stringify()
}

pub fn status_json(plane: &ControlPlane, cfg: &Config, actor: &ApiActor) -> String {
    let providers = load_providers(cfg);
    let embedding = load_embedding_config(cfg);
    let mut routes = Vec::new();
    for route in plane
        .routes
        .iter()
        .filter(|route| route.key_id == actor.key_id)
    {
        let provider_ready = if route.provider_id == PROVIDER_EMBEDDINGS {
            embedding.enabled && !embedding.upstream_url.trim().is_empty()
        } else {
            providers.iter().any(|provider| {
                canonical_provider_id(&provider.id) == route.provider_id
                    && provider.enabled
                    && provider.auth_path.exists()
            })
        };
        let mut item = Json::object();
        item.set(
            "installation_id",
            Json::String(route.installation_id.clone()),
        );
        item.set(
            "organization_id",
            Json::String(route.organization_id.clone()),
        );
        item.set("workspace_id", Json::String(route.workspace_id.clone()));
        item.set("app_id", Json::String(route.app_id.clone()));
        item.set(
            "status",
            Json::String(
                if plane.status == "unknown" {
                    "unknown"
                } else if route.installed && route.entitled && provider_ready {
                    "ready"
                } else {
                    "degraded"
                }
                .to_string(),
            ),
        );
        routes.push(item);
    }
    let mut body = Json::object();
    body.set("status", Json::String(plane.status.to_string()));
    body.set(
        "configuration_revision",
        Json::String(plane.revision.clone()),
    );
    body.set("routes", Json::Array(routes));
    body.stringify()
}

pub fn grant(
    cfg: &Config,
    installation_id: &str,
    organization_id: &str,
    workspace_id: &str,
    app_id: &str,
    key_id: &str,
    provider_id: &str,
) -> Result<(), String> {
    let fields = [
        installation_id,
        organization_id,
        workspace_id,
        app_id,
        key_id,
        provider_id,
    ];
    if fields.iter().any(|field| !valid_id(field)) {
        return Err("route fields must contain only letters, digits, '.', '_', or '-'".to_string());
    }
    let provider_id = canonical_provider_id(provider_id);
    if !["codex", "claude", "opencode-go", PROVIDER_EMBEDDINGS].contains(&provider_id.as_str()) {
        return Err("route provider must be codex, claude, opencode-go, or embeddings".to_string());
    }
    let mut routes = load(cfg).routes;
    routes.retain(|route| {
        !(route.organization_id == organization_id
            && route.workspace_id == workspace_id
            && route.app_id == app_id
            && route.key_id == key_id)
    });
    routes.push(Route {
        installation_id: installation_id.to_string(),
        organization_id: organization_id.to_string(),
        workspace_id: workspace_id.to_string(),
        app_id: app_id.to_string(),
        key_id: key_id.to_string(),
        provider_id,
        installed: true,
        entitled: true,
    });
    save(cfg, &routes, "grant")
}

pub fn revoke(cfg: &Config, installation_id: &str) -> Result<(), String> {
    if !valid_id(installation_id) {
        return Err("installation id is invalid".to_string());
    }
    let mut routes = load(cfg).routes;
    let mut changed = false;
    for route in &mut routes {
        if route.installation_id == installation_id {
            route.entitled = false;
            changed = true;
        }
    }
    if !changed {
        return Err("installation route not found".to_string());
    }
    save(cfg, &routes, "revoke")
}

fn save(cfg: &Config, routes: &[Route], action: &str) -> Result<(), String> {
    let revision = format!("cfg_{}", random_hex(12));
    let updated_at = now_secs();
    let mut text = format!(
        "# revision={revision}\n# updated_at={updated_at}\n# installation_id|organization_id|workspace_id|app_id|key_id|provider_id|installed|entitled\n"
    );
    for route in routes {
        text.push_str(&format!(
            "{}|{}|{}|{}|{}|{}|{}|{}\n",
            route.installation_id,
            route.organization_id,
            route.workspace_id,
            route.app_id,
            route.key_id,
            route.provider_id,
            route.installed,
            route.entitled
        ));
    }
    atomic_write(&cfg.data_dir.join("routes.conf"), &text)?;
    let mut audit = OpenOptions::new()
        .create(true)
        .append(true)
        .open(cfg.data_dir.join("route-audit.tsv"))
        .map_err(|err| err.to_string())?;
    writeln!(
        audit,
        "{updated_at}\t{revision}\t{action}\t{}",
        routes.len()
    )
    .map_err(|err| err.to_string())?;
    let _ = fs::set_permissions(
        cfg.data_dir.join("route-audit.tsv"),
        fs::Permissions::from_mode(0o600),
    );
    Ok(())
}

fn atomic_write(path: &std::path::Path, text: &str) -> Result<(), String> {
    let temp = path.with_extension(format!("{}.tmp", random_hex(8)));
    fs::write(&temp, text).map_err(|err| err.to_string())?;
    fs::set_permissions(&temp, fs::Permissions::from_mode(0o600)).map_err(|err| err.to_string())?;
    fs::rename(temp, path).map_err(|err| err.to_string())
}

fn unknown() -> ControlPlane {
    ControlPlane {
        revision: "unknown".to_string(),
        status: "unknown",
        routes: Vec::new(),
    }
}

fn valid_id(value: &str) -> bool {
    !value.is_empty()
        && value.len() <= 128
        && value
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
}

fn parse_bool(value: &str) -> Option<bool> {
    match value {
        "true" => Some(true),
        "false" => Some(false),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::is_fresh;

    #[test]
    fn zero_stale_window_means_static_config() {
        assert!(is_fresh(0, 1_790_000_000, 0));
    }

    #[test]
    fn seed_file_is_stale_with_default_window() {
        assert!(!is_fresh(0, 1_790_000_000, 300));
        assert!(is_fresh(1_789_999_900, 1_790_000_000, 300));
    }
}