AkurAI Build
Menu

AkurAI-Build

public

Latest change 960d5f3748a4a8b805a38052d2486c1d0c0d4fdf - fix(code-tree): mark directory sizes not applicable (AKURAI-BUILD-21) by Ólafur Búi Ólafsson

use std::{
    io::Write,
    net::SocketAddr,
    path::{Path as FsPath, PathBuf},
    process::{Command, Stdio},
    sync::Arc,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use anyhow::{Context, Result, ensure};
use axum::{
    Json, Router,
    body::{Body, Bytes, to_bytes},
    extract::{DefaultBodyLimit, Path, Query, State},
    http::{HeaderMap, HeaderName, HeaderValue, Request, StatusCode, header},
    middleware::{self, Next},
    response::{Html, IntoResponse, Redirect, Response},
    routing::{any, get, post},
};
use hmac::{Hmac, Mac};
use minijinja::{AutoEscape, Environment, context};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::Sha256;
use subtle::ConstantTimeEq;
use tokio::io::AsyncWriteExt;
use tokio_util::io::ReaderStream;
use tower::limit::GlobalConcurrencyLimitLayer;
use tower_http::{compression::CompressionLayer, timeout::TimeoutLayer, trace::TraceLayer};
use tracing::{info, warn};

use crate::auth::{IdpConfig, UserInfo};
use crate::{config, db::Database, git_process, runner::Runner, tree::RepoTree};

const MAX_BODY: usize = 1024 * 1024;
const MAX_GIT_BODY: usize = 512 * 1024 * 1024;
const MAX_CONNECTIONS: usize = 1024;
const MAX_BLOCKING_GIT: usize = 4;
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
const LANDING: &str = include_str!("../app/pages/landing.html");
const DOCS: &str = include_str!("../app/pages/docs/index.html");
const DASHBOARD: &str = include_str!("../app/pages/index.html");
const REPOSITORIES: &str = include_str!("../app/pages/repositories.html");
const REPOSITORY_APP: &str = include_str!("../app/pages/repository-app.html");
const RUNS: &str = include_str!("../app/pages/runs.html");
const WORKERS: &str = include_str!("../app/pages/workers.html");
const REPOSITORY: &str = include_str!("../app/pages/repository.html");
const CODE: &str = include_str!("../app/pages/repository-code.html");
const RUN: &str = include_str!("../app/pages/run.html");
const BASE: &str = include_str!("../app/templates/layouts/base.html");
const DOCS_LAYOUT: &str = include_str!("../app/templates/layouts/docs.html");
const DOCS_NAV: &str = include_str!("../app/templates/partials/docs-nav.html");
const PUBLIC_WORDMARK: &str = include_str!("../app/templates/partials/public-wordmark.html");
const PUBLIC_HEADER: &str = include_str!("../app/templates/partials/public-header.html");
const PUBLIC_FOOTER: &str = include_str!("../app/templates/partials/public-footer.html");
const CODE_CONTENT: &str = include_str!("../app/templates/partials/code-content.html");
const NOT_FOUND: &str = include_str!("../app/pages/_404.html");
const CSS: &str = include_str!("../public/app.css");
const JS: &str = include_str!("../public/app.js");

type HmacSha256 = Hmac<Sha256>;

#[derive(Clone)]
pub struct ServerOptions {
    pub address: SocketAddr,
    pub token: String,
    pub worker_count: usize,
    pub webhook_secret: String,
    pub public_origin: String,
    pub data_root: PathBuf,
    pub allow_native: bool,
}

#[derive(Clone)]
struct AppState {
    database: Database,
    runner: Runner,
    tree: RepoTree,
    hosted_root: PathBuf,
    blocking_git: BlockingGit,
    token: Arc<str>,
    webhook_secret: Arc<str>,
    public_origin: Arc<str>,
    templates: Arc<Environment<'static>>,
}

#[derive(Serialize)]
struct Envelope<T: Serialize> {
    ok: bool,
    data: T,
}

#[derive(Serialize)]
struct ErrorEnvelope {
    ok: bool,
    error: ErrorMessage,
}

#[derive(Serialize)]
struct ErrorMessage {
    code: &'static str,
    message: String,
}

struct ApiError {
    status: StatusCode,
    code: &'static str,
    message: String,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct TriggerRequest {
    git_ref: Option<String>,
    commit: Option<String>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RepositoryRequest {
    name: String,
    url: String,
    branch: String,
}

#[derive(Serialize)]
struct RunProgress {
    run_id: i64,
    repository: String,
    git_ref: String,
    commit_sha: Option<String>,
    status: String,
    stage: String,
    progress: u8,
    jobs: Vec<ProgressJob>,
    deployments: Vec<ProgressDeployment>,
    error: Option<String>,
}

#[derive(Serialize)]
struct ProgressJob {
    id: i64,
    name: String,
    status: String,
    environment: Option<String>,
    approval_required: bool,
}

#[derive(Serialize)]
struct ProgressDeployment {
    id: i64,
    environment: String,
    status: String,
}

#[derive(Deserialize, Default)]
struct StateQuery {
    repo: Option<String>,
    status: Option<String>,
    git_ref: Option<String>,
    trigger: Option<String>,
    q: Option<String>,
    before: Option<i64>,
    limit: Option<usize>,
    offset: Option<usize>,
}

#[derive(Deserialize, Default)]
struct DeliveryMetricsQuery {
    repo: Option<String>,
    environment: Option<String>,
    window: Option<i64>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct VisibilityRequest {
    visibility: String,
}

#[derive(Serialize)]
struct RepositoryView {
    id: i64,
    name: String,
    url: String,
    default_branch: String,
    visibility: String,
    created_at: i64,
}

fn repository_client_url(
    repository_name: &str,
    repository_url: &str,
    data_root: &FsPath,
    public_origin: &str,
) -> String {
    let repository_path = FsPath::new(repository_url);
    let hosted_root = data_root.join("hosted");
    if repository_path.starts_with(&hosted_root)
        && repository_path
            .parent()
            .and_then(FsPath::file_name)
            .is_some_and(|name| name == "hosted")
        && repository_path
            .extension()
            .is_some_and(|extension| extension == "git")
    {
        return format!(
            "{}/git/{repository_name}.git",
            public_origin.trim_end_matches('/')
        );
    }
    repository_url.to_owned()
}

fn repository_view(
    repository: crate::db::Repository,
    data_root: &FsPath,
    public_origin: &str,
) -> RepositoryView {
    let url = repository_client_url(&repository.name, &repository.url, data_root, public_origin);
    RepositoryView {
        id: repository.id,
        name: repository.name,
        url,
        default_branch: repository.default_branch,
        visibility: repository.visibility,
        created_at: repository.created_at,
    }
}

#[derive(Serialize)]
struct StateResponse {
    repositories: Vec<RepositoryView>,
    runs: Vec<crate::db::Run>,
    workers: Vec<crate::db::Worker>,
    queued_runs: i64,
    run_cursor: Option<i64>,
    run_total: i64,
}

#[derive(Serialize)]
struct CodeEntryView {
    name: String,
    kind: String,
    size: u64,
    commit_message: Option<String>,
    href: String,
}

#[derive(Serialize)]
struct CodeBreadcrumb {
    label: String,
    href: String,
}

#[derive(Serialize)]
struct CodePage {
    repository: RepositoryView,
    reference: String,
    path: String,
    entries: Vec<CodeEntryView>,
    breadcrumbs: Vec<CodeBreadcrumb>,
    source: Option<String>,
    is_file: bool,
    parent_href: String,
    latest_commit: Option<crate::tree::LatestCommit>,
    commit_href: Option<String>,
    public: bool,
}

#[derive(Serialize)]
struct IdResponse {
    id: i64,
}

#[derive(Serialize)]
struct LandingRepository {
    name: String,
    default_branch: String,
    owner: String,
    run_count: i64,
    successful_runs: i64,
}

#[derive(Serialize)]
struct PublicBuilder {
    name: String,
    initial: String,
    repository_count: usize,
}

#[derive(Deserialize)]
struct CallbackQuery {
    code: Option<String>,
    state: Option<String>,
    error: Option<String>,
}

#[derive(Deserialize)]
struct TokenResponse {
    access_token: String,
}

impl ApiError {
    fn unauthorized() -> Self {
        Self {
            status: StatusCode::UNAUTHORIZED,
            code: "unauthorized",
            message: "valid bearer token required".into(),
        }
    }
    fn bad(error: impl std::fmt::Display) -> Self {
        Self {
            status: StatusCode::BAD_REQUEST,
            code: "invalid_request",
            message: error.to_string(),
        }
    }
    fn not_found(message: impl Into<String>) -> Self {
        Self {
            status: StatusCode::NOT_FOUND,
            code: "not_found",
            message: message.into(),
        }
    }
    fn internal(error: impl std::fmt::Display) -> Self {
        tracing::error!(error = %error, "request failed");
        Self {
            status: StatusCode::INTERNAL_SERVER_ERROR,
            code: "internal",
            message: "request failed".into(),
        }
    }
}

#[derive(Clone)]
struct BlockingGit {
    permits: Arc<tokio::sync::Semaphore>,
}

impl BlockingGit {
    fn new(limit: usize) -> Self {
        Self {
            permits: Arc::new(tokio::sync::Semaphore::new(limit)),
        }
    }

    async fn run<T, F>(&self, operation: F) -> Result<T, ApiError>
    where
        T: Send + 'static,
        F: FnOnce() -> Result<T, ApiError> + Send + 'static,
    {
        let permit = self
            .permits
            .clone()
            .acquire_owned()
            .await
            .map_err(ApiError::internal)?;
        tokio::task::spawn_blocking(move || {
            let _permit = permit;
            operation()
        })
        .await
        .map_err(ApiError::internal)?
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        (
            self.status,
            Json(ErrorEnvelope {
                ok: false,
                error: ErrorMessage {
                    code: self.code,
                    message: self.message,
                },
            }),
        )
            .into_response()
    }
}

fn template_environment() -> Result<Environment<'static>> {
    let mut templates = Environment::new();
    templates.set_auto_escape_callback(|name| {
        if name.ends_with(".html") {
            AutoEscape::Html
        } else {
            AutoEscape::None
        }
    });
    templates.add_template("layouts/base.html", BASE)?;
    templates.add_template("layouts/docs.html", DOCS_LAYOUT)?;
    templates.add_template("partials/docs-nav.html", DOCS_NAV)?;
    templates.add_template("partials/public-wordmark.html", PUBLIC_WORDMARK)?;
    templates.add_template("partials/public-header.html", PUBLIC_HEADER)?;
    templates.add_template("partials/public-footer.html", PUBLIC_FOOTER)?;
    templates.add_template("partials/code-content.html", CODE_CONTENT)?;
    templates.add_template("landing.html", LANDING)?;
    templates.add_template("docs.html", DOCS)?;
    templates.add_template("dashboard.html", DASHBOARD)?;
    templates.add_template("repositories.html", REPOSITORIES)?;
    templates.add_template("repository-app.html", REPOSITORY_APP)?;
    templates.add_template("runs.html", RUNS)?;
    templates.add_template("workers.html", WORKERS)?;
    templates.add_template("run.html", RUN)?;
    templates.add_template("repository.html", REPOSITORY)?;
    templates.add_template("repository-code.html", CODE)?;
    templates.add_template("not-found.html", NOT_FOUND)?;
    Ok(templates)
}

fn request_prefers_html(headers: &HeaderMap) -> bool {
    let Some(accept) = headers
        .get(header::ACCEPT)
        .and_then(|value| value.to_str().ok())
    else {
        return false;
    };
    let media_types = accept
        .split(',')
        .map(|value| value.split(';').next().unwrap_or_default().trim());
    let mut html = false;
    for media_type in media_types {
        if media_type == "application/json" || media_type.ends_with("+json") {
            return false;
        }
        html |= media_type == "text/html" || media_type == "application/xhtml+xml";
    }
    html
}

fn public_error_response(
    templates: &Environment<'_>,
    headers: &HeaderMap,
    error: ApiError,
    title: &str,
    heading: &str,
    description: &str,
) -> Result<Response, ApiError> {
    if !request_prefers_html(headers) {
        return Ok(error.into_response());
    }
    let status = error.status;
    let page = templates
        .get_template("not-found.html")
        .map_err(ApiError::internal)?
        .render(context! {
            title => title,
            heading => heading,
            description => description
        })
        .map_err(ApiError::internal)?;
    Ok((status, Html(page)).into_response())
}

async fn public_not_found(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Response, ApiError> {
    public_error_response(
        &state.templates,
        &headers,
        ApiError::not_found("route not found"),
        "Page not found",
        "This page could not be found.",
        "The address may be outdated or mistyped. Return home or open the documentation.",
    )
}

pub async fn serve(database: Database, options: ServerOptions) -> Result<()> {
    ensure!(!options.token.is_empty(), "API token must not be empty");
    ensure!(
        !options.webhook_secret.is_empty(),
        "webhook secret must not be empty"
    );
    ensure!(
        options.worker_count <= 16,
        "worker count must be 0..=16 (0 = serve-only, no execution)"
    );
    database.validate_schema()?;
    if options.worker_count > 0 {
        // Recovery belongs to the executor role: only the worker instance may
        // mark orphaned runs interrupted and reset worker rows. A serve-only
        // restart must never clobber a live worker's in-flight run.
        let recovered = database.recover_interrupted()?;
        if recovered > 0 {
            warn!(recovered, "marked interrupted work after restart");
        }
        database.reset_workers()?;
        let configured: Vec<String> = (0..options.worker_count)
            .map(|index| format!("titan-{}", index + 1))
            .collect();
        let pruned = database.prune_workers_except(&configured)?;
        if pruned > 0 {
            warn!(
                pruned,
                "removed stale worker rows beyond the configured count"
            );
        }
    }
    let templates = template_environment()?;
    let hosted_root = options.data_root.clone();
    let tree = RepoTree::new(&options.data_root)?;
    let runner = Runner::new(database.clone(), options.data_root, options.allow_native)?;
    let state = AppState {
        database: database.clone(),
        runner: runner.clone(),
        tree,
        blocking_git: BlockingGit::new(MAX_BLOCKING_GIT),
        hosted_root,
        token: options.token.into(),
        webhook_secret: options.webhook_secret.into(),
        public_origin: options.public_origin.into(),
        templates: Arc::new(templates),
    };
    for index in 0..options.worker_count {
        let worker_id = format!("titan-{}", index + 1);
        database.register_worker(&worker_id, "titan", "docker,native", 1)?;
        tokio::spawn(worker(database.clone(), runner.clone(), worker_id));
    }

    let app = Router::new()
        .route("/", get(index))
        .route("/docs", get(documentation))
        .route("/docs/", get(documentation))
        .route("/repos/{repository}", get(public_repository))
        .route(
            "/repos/{repository}/tree/{reference}",
            get(public_code_root),
        )
        .route(
            "/repos/{repository}/tree/{reference}/{*path}",
            get(public_code_path),
        )
        .route(
            "/app/repositories/{repository}/tree/{reference}",
            get(app_code_root),
        )
        .route(
            "/app/repositories/{repository}/tree/{reference}/{*path}",
            get(app_code_path),
        )
        .route("/app", get(dashboard))
        .route("/app/repositories", get(repositories_page))
        .route("/app/repositories/{repository}", get(repository_dashboard))
        .route("/app/run/{id}", get(run_page))
        .route("/app/runs", get(runs_page))
        .route("/app/workers", get(workers_page))
        .route("/login", get(login))
        .route("/auth/callback", get(auth_callback))
        .route("/assets/app.css", get(css))
        .route("/assets/app.js", get(js))
        .route("/git/{*path}", any(git_http))
        .route("/api/health", get(health))
        .route("/api/state", get(api_state))
        .route("/api/repos", post(create_repository))
        .route("/api/runs/{id}", get(run_detail))
        .route("/api/metrics/delivery", get(delivery_metrics))
        .route("/api/runs/{id}/events", get(run_events))
        .route("/api/runs/{id}/retry", post(retry))
        .route("/api/workers", get(api_workers))
        .route("/api/runs/{id}/promote/{environment}", post(promote))
        .route("/api/repos/{repository}/runs", post(trigger))
        .route(
            "/api/repos/{repository}/visibility",
            post(change_repository_visibility),
        )
        .route("/api/hooks/{repository}", post(webhook))
        .route("/api/artifacts/{id}", get(artifact))
        .route("/api/repos/{repository}/branches", get(branches))
        .route("/api/repos/{repository}/tree", get(repo_tree))
        .fallback(public_not_found)
        .with_state(state)
        .layer(DefaultBodyLimit::max(MAX_BODY))
        .layer(CompressionLayer::new())
        .layer(TimeoutLayer::with_status_code(
            StatusCode::REQUEST_TIMEOUT,
            REQUEST_TIMEOUT,
        ))
        .layer(TraceLayer::new_for_http())
        .layer(middleware::from_fn(security_headers))
        .layer(GlobalConcurrencyLimitLayer::new(MAX_CONNECTIONS));
    let listener = tokio::net::TcpListener::bind(options.address).await?;
    serve_bounded(listener, app).await
}

async fn worker(database: Database, runner: Runner, worker_id: String) {
    if let Err(error) = database.heartbeat_worker(&worker_id, "idle", None, false) {
        warn!(%worker_id, ?error, "worker registration heartbeat failed");
    }
    loop {
        match database.claim_next_queued_run() {
            Ok(Some(id)) => {
                if let Err(error) =
                    database.heartbeat_worker(&worker_id, "running", Some(id), false)
                {
                    warn!(%worker_id, ?error, "worker heartbeat failed");
                }
                let heartbeat_database = database.clone();
                let heartbeat_worker_id = worker_id.clone();
                let heartbeat = tokio::spawn(async move {
                    let mut ticker = tokio::time::interval(Duration::from_secs(5));
                    loop {
                        ticker.tick().await;
                        if let Err(error) = heartbeat_database.heartbeat_worker(
                            &heartbeat_worker_id,
                            "running",
                            Some(id),
                            false,
                        ) {
                            warn!(worker_id = %heartbeat_worker_id, ?error, "worker heartbeat failed");
                        }
                    }
                });
                let process_runner = runner.clone();
                let run =
                    tokio::task::spawn_blocking(move || process_runner.process_claimed_run(id))
                        .await;
                heartbeat.abort();
                let completed = match run {
                    Ok(Ok(detail)) => matches!(
                        detail.run.status.as_str(),
                        "waiting" | "succeeded" | "failed" | "canceled" | "interrupted"
                    ),
                    Ok(Err(error)) => {
                        tracing::error!(run_id = id, ?error, "run failed");
                        false
                    }
                    Err(error) => {
                        tracing::error!(run_id = id, ?error, "run worker panicked");
                        false
                    }
                };
                if let Err(error) = database.heartbeat_worker(&worker_id, "idle", None, completed) {
                    warn!(%worker_id, ?error, "worker idle heartbeat failed");
                }
            }
            Ok(None) => {
                if let Err(error) = database.heartbeat_worker(&worker_id, "idle", None, false) {
                    warn!(%worker_id, ?error, "worker heartbeat failed");
                }
                tokio::time::sleep(Duration::from_millis(500)).await;
            }
            Err(error) => {
                tracing::error!(?error, "queue poll failed");
                tokio::time::sleep(Duration::from_secs(2)).await;
            }
        }
    }
}

async fn index(State(state): State<AppState>) -> Result<Html<String>, ApiError> {
    let repositories = state
        .database
        .public_repositories()
        .map_err(ApiError::internal)?
        .into_iter()
        .map(|repository| LandingRepository {
            owner: repository_owner(&repository.url),
            name: repository.name,
            default_branch: repository.default_branch,
            run_count: repository.run_count,
            successful_runs: repository.successful_runs,
        })
        .collect::<Vec<_>>();
    let mut builder_counts = std::collections::BTreeMap::new();
    for repository in &repositories {
        *builder_counts.entry(repository.owner.clone()).or_insert(0) += 1;
    }
    let mut builders = builder_counts
        .into_iter()
        .map(|(name, repository_count)| PublicBuilder {
            initial: name.chars().next().unwrap_or('A').to_uppercase().collect(),
            name,
            repository_count,
        })
        .collect::<Vec<_>>();
    builders.sort_by(|left, right| {
        right
            .repository_count
            .cmp(&left.repository_count)
            .then_with(|| left.name.cmp(&right.name))
    });
    let page = state
        .templates
        .get_template("landing.html")
        .map_err(ApiError::internal)?
        .render(context! {
            version => env!("CARGO_PKG_VERSION"),
            repositories => repositories,
            builders => builders
        })
        .map_err(ApiError::internal)?;
    Ok(Html(page))
}

fn encode_code_segment(value: &str) -> String {
    value
        .bytes()
        .map(|byte| {
            if byte.is_ascii_alphanumeric() || b"-._~".contains(&byte) {
                (byte as char).to_string()
            } else {
                format!("%{byte:02X}")
            }
        })
        .collect()
}

fn code_base(public: bool, repository: &str, reference: &str) -> String {
    let prefix = if public {
        "/repos"
    } else {
        "/app/repositories"
    };
    format!(
        "{prefix}/{}/tree/{}",
        encode_code_segment(repository),
        encode_code_segment(reference),
    )
}

fn code_path_href(base: &str, path: &str) -> String {
    if path.is_empty() {
        return base.to_owned();
    }
    format!(
        "{base}/{}",
        path.split('/')
            .filter(|segment| !segment.is_empty())
            .map(encode_code_segment)
            .collect::<Vec<_>>()
            .join("/")
    )
}

fn code_breadcrumbs(
    public: bool,
    repository: &str,
    reference: &str,
    path: &str,
) -> Vec<CodeBreadcrumb> {
    let base = code_base(public, repository, reference);
    let mut breadcrumbs = vec![CodeBreadcrumb {
        label: repository.to_owned(),
        href: base.clone(),
    }];
    let mut current = String::new();
    for segment in path.split('/').filter(|segment| !segment.is_empty()) {
        if !current.is_empty() {
            current.push('/');
        }
        current.push_str(segment);
        breadcrumbs.push(CodeBreadcrumb {
            label: segment.to_owned(),
            href: code_path_href(&base, &current),
        });
    }
    breadcrumbs
}

async fn render_code(
    state: &AppState,
    repository_name: String,
    reference: String,
    path: String,
    public: bool,
) -> Result<Html<String>, ApiError> {
    let repository = state
        .database
        .repository(&repository_name)
        .map_err(|_| ApiError::not_found(format!("unknown repository {repository_name}")))?;
    if public && repository.visibility != "public" {
        return Err(ApiError::not_found("repository not found"));
    }
    let view = repository_view(repository.clone(), &state.hosted_root, &state.public_origin);
    let base = code_base(public, &repository_name, &reference);
    enum Lookup {
        Tree(crate::tree::TreeResponse),
        Blob(crate::tree::BlobResponse),
    }
    let tree = state.tree.clone();
    let name = repository.name.clone();
    let url = repository.url.clone();
    let lookup_reference = reference.clone();
    let lookup_path = path.clone();
    let lookup = state
        .blocking_git
        .run(
            move || match tree.tree(&name, &url, Some(&lookup_reference), &lookup_path) {
                Ok(response) => Ok(Lookup::Tree(response)),
                Err(_) => tree
                    .blob(&name, &url, &lookup_reference, &lookup_path)
                    .map(Lookup::Blob)
                    .map_err(|_| ApiError::not_found("repository ref or path not found")),
            },
        )
        .await?;
    let (entries, source, is_file, latest_commit) = match lookup {
        Lookup::Tree(tree) => (
            tree.entries
                .into_iter()
                .map(|entry| CodeEntryView {
                    href: code_path_href(
                        &base,
                        &if path.is_empty() {
                            entry.name.clone()
                        } else {
                            format!("{path}/{}", entry.name)
                        },
                    ),
                    name: entry.name,
                    kind: entry.kind,
                    size: entry.size,
                    commit_message: entry.commit_message,
                })
                .collect(),
            None,
            false,
            tree.latest_commit,
        ),
        Lookup::Blob(blob) => (Vec::new(), Some(blob.content), true, blob.latest_commit),
    };
    let commit_href = latest_commit
        .as_ref()
        .map(|commit| code_base(public, &repository_name, &commit.sha));
    let parent_href = if path.is_empty() {
        if public {
            format!("/repos/{}", encode_code_segment(&repository_name))
        } else {
            format!(
                "/app/repositories/{}",
                encode_code_segment(&repository_name)
            )
        }
    } else {
        let parent = path.rsplit_once('/').map_or("", |(parent, _)| parent);
        code_path_href(&base, parent)
    };
    let page = CodePage {
        repository: view,
        reference: reference.clone(),
        path: path.clone(),
        entries,
        breadcrumbs: code_breadcrumbs(public, &repository_name, &reference, &path),
        source,
        is_file,
        parent_href,
        latest_commit,
        commit_href,
        public,
    };
    let rendered = state
        .templates
        .get_template("repository-code.html")
        .map_err(ApiError::internal)?
        .render(context! { page => page })
        .map_err(ApiError::internal)?;
    Ok(Html(rendered))
}

async fn public_code_root(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path((repository, reference)): Path<(String, String)>,
) -> Result<Response, ApiError> {
    match render_code(&state, repository, reference, String::new(), true).await {
        Ok(page) => Ok(page.into_response()),
        Err(error) if error.status == StatusCode::NOT_FOUND => public_error_response(
            &state.templates,
            &headers,
            error,
            "Repository content not found",
            "Repository content could not be found.",
            "The repository, ref, or path may have moved. Return to the site or open the documentation.",
        ),
        Err(error) => Err(error),
    }
}

async fn public_code_path(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path((repository, reference, path)): Path<(String, String, String)>,
) -> Result<Response, ApiError> {
    match render_code(&state, repository, reference, path, true).await {
        Ok(page) => Ok(page.into_response()),
        Err(error) if error.status == StatusCode::NOT_FOUND => public_error_response(
            &state.templates,
            &headers,
            error,
            "Repository content not found",
            "Repository content could not be found.",
            "The repository, ref, or path may have moved. Return to the site or open the documentation.",
        ),
        Err(error) => Err(error),
    }
}

async fn app_code_root(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path((repository, reference)): Path<(String, String)>,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    match render_code(&state, repository, reference, String::new(), false).await {
        Ok(page) => Ok(page.into_response()),
        Err(error) if error.status == StatusCode::NOT_FOUND => public_error_response(
            &state.templates,
            &headers,
            error,
            "Repository content not found",
            "Repository content could not be found.",
            "The repository, ref, or path may have moved. Return to the site or open the documentation.",
        ),
        Err(error) => Err(error),
    }
}

async fn app_code_path(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path((repository, reference, path)): Path<(String, String, String)>,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    match render_code(&state, repository, reference, path, false).await {
        Ok(page) => Ok(page.into_response()),
        Err(error) if error.status == StatusCode::NOT_FOUND => public_error_response(
            &state.templates,
            &headers,
            error,
            "Repository content not found",
            "Repository content could not be found.",
            "The repository, ref, or path may have moved. Return to the site or open the documentation.",
        ),
        Err(error) => Err(error),
    }
}

async fn documentation(State(state): State<AppState>) -> Result<Html<String>, ApiError> {
    let page = state
        .templates
        .get_template("docs.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION") })
        .map_err(ApiError::internal)?;
    Ok(Html(page))
}

fn repository_owner(url: &str) -> String {
    let (host, path) = if let Some((_, remote)) = url.split_once("://") {
        remote
            .split_once('/')
            .map(|(host, path)| (host.rsplit('@').next().unwrap_or(host), path))
            .unwrap_or((remote, ""))
    } else if let Some((_, remote)) = url.split_once('@') {
        remote.split_once(':').unwrap_or((remote, ""))
    } else {
        return "AkurAI Build".into();
    };
    let mut segments = path.split('/').filter(|segment| !segment.is_empty());
    match (segments.next(), segments.next()) {
        (Some("git"), Some(_)) => "AkurAI Build".into(),
        (Some(owner), Some(_)) => owner.into(),
        _ => host.into(),
    }
}

async fn public_repository(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository_name): Path<String>,
) -> Result<Response, ApiError> {
    let repository = match state.database.repository(&repository_name) {
        Ok(repository) if repository.visibility == "public" => repository,
        _ => {
            return public_error_response(
                &state.templates,
                &headers,
                ApiError::not_found(format!("unknown repository {repository_name}")),
                "Repository not found",
                "This repository could not be found.",
                "The repository may be private, renamed, or unavailable. Explore public work or open the documentation.",
            );
        }
    };
    let code_href = code_base(true, &repository.name, &repository.default_branch);
    let repository = repository_view(repository, &state.hosted_root, &state.public_origin);
    let page = state
        .templates
        .get_template("repository.html")
        .map_err(ApiError::internal)?
        .render(context! { repository => repository, code_href => code_href })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn git_http(
    State(state): State<AppState>,
    Path(path): Path<String>,
    request: Request<Body>,
) -> Result<Response, ApiError> {
    authorize(request.headers(), &state)?;
    validate_git_path(&path).map_err(ApiError::bad)?;
    let (parts, body) = request.into_parts();
    let body = to_bytes(body, MAX_GIT_BODY).await.map_err(ApiError::bad)?;
    let root = state.hosted_root.join("hosted");
    tokio::task::spawn_blocking(move || run_git_http(root, path, parts, body))
        .await
        .map_err(ApiError::internal)?
        .map_err(ApiError::internal)
}

fn validate_git_path(path: &str) -> Result<()> {
    ensure!(!path.is_empty() && path.len() <= 512, "invalid Git path");
    let repository = path.split('/').next().unwrap_or_default();
    ensure!(repository.ends_with(".git"), "invalid Git repository path");
    ensure!(
        path.split('/').all(|component| {
            !component.is_empty()
                && component
                    .bytes()
                    .all(|byte| byte.is_ascii_alphanumeric() || b"._-".contains(&byte))
        }),
        "invalid Git path"
    );
    Ok(())
}

fn run_git_http(
    root: PathBuf,
    path: String,
    parts: axum::http::request::Parts,
    body: Bytes,
) -> Result<Response> {
    let mut command = Command::new("git");
    command
        .arg("http-backend")
        .env("GIT_PROJECT_ROOT", root)
        .env("GIT_HTTP_EXPORT_ALL", "1")
        .env("PATH_INFO", format!("/{path}"))
        .env("REQUEST_METHOD", parts.method.as_str())
        .env("QUERY_STRING", parts.uri.query().unwrap_or_default())
        .env("CONTENT_LENGTH", body.len().to_string())
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    if let Some(value) = parts.headers.get(header::CONTENT_TYPE) {
        command.env("CONTENT_TYPE", value.to_str()?);
    }
    if let Some(value) = parts.headers.get("git-protocol") {
        command.env("HTTP_GIT_PROTOCOL", value.to_str()?);
    }
    let mut child = command
        .spawn()
        .context("failed to start git http-backend")?;
    child
        .stdin
        .take()
        .context("git http-backend stdin is unavailable")?
        .write_all(&body)?;
    let output = child.wait_with_output()?;
    ensure!(
        output.status.success(),
        "git http-backend failed: {}",
        String::from_utf8_lossy(&output.stderr).trim()
    );
    git_cgi_response(&output.stdout)
}

fn git_cgi_response(output: &[u8]) -> Result<Response> {
    let (header_bytes, body) = output
        .windows(4)
        .position(|window| window == b"\r\n\r\n")
        .map(|index| (&output[..index], &output[index + 4..]))
        .or_else(|| {
            output
                .windows(2)
                .position(|window| window == b"\n\n")
                .map(|index| (&output[..index], &output[index + 2..]))
        })
        .context("git http-backend returned malformed CGI output")?;
    let mut status = StatusCode::OK;
    let mut response = Response::builder();
    for line in String::from_utf8_lossy(header_bytes).lines() {
        let (name, value) = line.split_once(':').context("invalid Git CGI header")?;
        if name.eq_ignore_ascii_case("status") {
            let code = value
                .split_whitespace()
                .next()
                .context("missing Git CGI status")?
                .parse::<u16>()?;
            status = StatusCode::from_u16(code)?;
        } else {
            response = response.header(
                HeaderName::from_bytes(name.trim().as_bytes())?,
                HeaderValue::from_str(value.trim())?,
            );
        }
    }
    Ok(response.status(status).body(Body::from(body.to_vec()))?)
}

async fn dashboard(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let page = state
        .templates
        .get_template("dashboard.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION") })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn repositories_page(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let page = state
        .templates
        .get_template("repositories.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION") })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn repository_dashboard(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let repository = state
        .database
        .repository(&repository)
        .map_err(ApiError::bad)?;
    let repository = repository_view(repository, &state.hosted_root, &state.public_origin);
    let page = state
        .templates
        .get_template("repository-app.html")
        .map_err(ApiError::internal)?
        .render(context! {
            version => env!("CARGO_PKG_VERSION"),
            repository => repository
        })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn runs_page(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let page = state
        .templates
        .get_template("runs.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION") })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn run_page(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(id): Path<i64>,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let page = state
        .templates
        .get_template("run.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION"), run_id => id })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn workers_page(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Response, ApiError> {
    if !valid_session(&headers, &state) {
        return Ok(Redirect::to("/login").into_response());
    }
    let page = state
        .templates
        .get_template("workers.html")
        .map_err(ApiError::internal)?
        .render(context! { version => env!("CARGO_PKG_VERSION") })
        .map_err(ApiError::internal)?;
    Ok(Html(page).into_response())
}

async fn login(State(state): State<AppState>) -> Result<Response, ApiError> {
    let config = IdpConfig::from_env(&state.public_origin);
    if config.client_id.is_empty() || config.client_secret.is_empty() {
        return Err(ApiError::internal("AkurAI Auth client is not configured"));
    }
    let login_state = login_state(&state)?;
    let mut response = Redirect::to(&config.authorize_url(&login_state)).into_response();
    response.headers_mut().insert(
        header::SET_COOKIE,
        HeaderValue::from_str(&format!(
            "ab_oidc_state={login_state}; Path=/auth/callback; HttpOnly; Secure; SameSite=Lax; Max-Age=300"
        ))
        .map_err(ApiError::internal)?,
    );
    Ok(response)
}

async fn auth_callback(
    State(state): State<AppState>,
    headers: HeaderMap,
    Query(query): Query<CallbackQuery>,
) -> Result<Response, ApiError> {
    let code = query.code.filter(|code| !code.is_empty()).ok_or_else(|| {
        ApiError::bad(
            query
                .error
                .unwrap_or_else(|| "authentication failed".into()),
        )
    })?;
    let returned_state = query
        .state
        .ok_or_else(|| ApiError::bad("missing authentication state"))?;
    let expected_state = request_cookie(&headers, "ab_oidc_state")
        .ok_or_else(|| ApiError::bad("missing authentication state"))?;
    if !constant_time_equal(returned_state.as_bytes(), expected_state.as_bytes())
        || !valid_login_state(expected_state, &state)
    {
        return Err(ApiError::bad("invalid authentication state"));
    }

    let config = IdpConfig::from_env(&state.public_origin);
    if config.client_id.is_empty() || config.client_secret.is_empty() {
        return Err(ApiError::internal("AkurAI Auth client is not configured"));
    }
    let user = idp_user(config.clone(), code).await?;
    let role = if config.admin_emails.iter().any(|email| email == &user.email) {
        "admin"
    } else {
        "member"
    };
    let mut response = Redirect::to("/app").into_response();
    response.headers_mut().append(
        header::SET_COOKIE,
        HeaderValue::from_str(&session_cookie(&user, role, &state)?).map_err(ApiError::internal)?,
    );
    response.headers_mut().append(
        header::SET_COOKIE,
        HeaderValue::from_static(
            "ab_oidc_state=; Path=/auth/callback; HttpOnly; Secure; SameSite=Lax; Max-Age=0",
        ),
    );
    Ok(response)
}

async fn idp_user(config: IdpConfig, code: String) -> Result<UserInfo, ApiError> {
    tokio::task::spawn_blocking(move || idp_user_blocking(config, code))
        .await
        .map_err(ApiError::internal)?
}

fn idp_user_blocking(config: IdpConfig, code: String) -> Result<UserInfo, ApiError> {
    let body = serde_json::to_vec(&serde_json::json!({
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": config.redirect_uri,
        "client_id": config.client_id,
        "client_secret": config.client_secret,
    }))
    .map_err(ApiError::internal)?;
    let token: TokenResponse = serde_json::from_slice(&curl_with_stdin(
        &[
            "--fail",
            "--silent",
            "--show-error",
            "--max-time",
            "15",
            "--header",
            "Content-Type: application/json",
            "--data-binary",
            "@-",
            &config.token_url(),
        ],
        &body,
    )?)
    .map_err(ApiError::internal)?;
    let userinfo_config = format!(
        "url = \"{}\"\nheader = \"Authorization: Bearer {}\"\n",
        config.userinfo_url(),
        token.access_token
    );
    serde_json::from_slice(&curl_with_stdin(
        &[
            "--fail",
            "--silent",
            "--show-error",
            "--max-time",
            "15",
            "--config",
            "-",
        ],
        userinfo_config.as_bytes(),
    )?)
    .map_err(ApiError::internal)
}

fn curl_with_stdin(arguments: &[&str], stdin: &[u8]) -> Result<Vec<u8>, ApiError> {
    let mut child = Command::new("curl")
        .args(arguments)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
        .map_err(ApiError::internal)?;
    child
        .stdin
        .take()
        .ok_or_else(|| ApiError::internal("curl stdin unavailable"))?
        .write_all(stdin)
        .map_err(ApiError::internal)?;
    let output = child.wait_with_output().map_err(ApiError::internal)?;
    if output.status.success() {
        Ok(output.stdout)
    } else {
        Err(ApiError::bad("identity provider request failed"))
    }
}

async fn css() -> Response {
    asset(CSS, "text/css; charset=utf-8")
}
async fn js() -> Response {
    asset(JS, "text/javascript; charset=utf-8")
}
fn asset(body: &'static str, content_type: &'static str) -> Response {
    let mut response = Response::new(Body::from(body));
    response
        .headers_mut()
        .insert(header::CONTENT_TYPE, HeaderValue::from_static(content_type));
    response.headers_mut().insert(
        header::CACHE_CONTROL,
        HeaderValue::from_static("public, max-age=300"),
    );
    response
}

async fn health(State(state): State<AppState>) -> Result<Json<Envelope<Value>>, ApiError> {
    state.database.check_ready().map_err(ApiError::internal)?;
    let workers = state.database.workers().map_err(ApiError::internal)?;
    let active_workers = workers
        .iter()
        .filter(|worker| worker.status == "running")
        .count();
    let queued_runs = state.database.queued_runs().map_err(ApiError::internal)?;
    Ok(ok(serde_json::json!({
        "service": "akurai-build",
        "status": "ok",
        "version": env!("CARGO_PKG_VERSION"),
        "workers": workers.len(),
        "active_workers": active_workers,
        "queued_runs": queued_runs,
    })))
}

async fn api_state(
    State(state): State<AppState>,
    headers: HeaderMap,
    Query(query): Query<StateQuery>,
) -> Result<Json<Envelope<StateResponse>>, ApiError> {
    authorize(&headers, &state)?;
    let admin = valid_admin_session(&headers, &state) || valid_bearer(&headers, &state);
    let all_repositories = state.database.repositories().map_err(ApiError::internal)?;
    let visible_repositories = all_repositories
        .iter()
        .filter(|repository| admin || repository.visibility == "public")
        .collect::<Vec<_>>();
    let repository_filter = if let Some(requested) = query.repo.as_deref() {
        let requested = requested
            .split(',')
            .map(str::trim)
            .filter(|name| !name.is_empty())
            .collect::<Vec<_>>();
        if requested.is_empty() {
            return Err(ApiError::bad("repository filter is empty"));
        }
        if !requested.iter().all(|name| {
            visible_repositories
                .iter()
                .any(|repository| repository.name == *name)
        }) {
            return Err(ApiError::not_found("repository not found"));
        }
        Some(requested.join(","))
    } else {
        let filter = visible_repositories
            .iter()
            .map(|repository| repository.name.as_str())
            .collect::<Vec<_>>()
            .join(",");
        Some(if filter.is_empty() {
            "__no_visible_repository__".to_owned()
        } else {
            filter
        })
    };
    let repositories = visible_repositories
        .into_iter()
        .cloned()
        .map(|repository| repository_view(repository, &state.hosted_root, &state.public_origin))
        .collect();
    let mut run_query = crate::db::RunQuery {
        repository: repository_filter,
        statuses: list_filter(query.status),
        git_ref: query.git_ref,
        triggers: list_filter(query.trigger),
        search: query.q,
        before_id: query.before,
        limit: query.limit.unwrap_or(50),
        offset: query.offset.unwrap_or(0),
    };
    let run_cursor = match run_query.before_id {
        Some(cursor) => Some(cursor),
        None => state
            .database
            .max_run_id(&run_query)
            .map_err(ApiError::bad)?,
    };
    run_query.before_id = run_cursor;
    let run_total = state
        .database
        .count_runs(&run_query)
        .map_err(ApiError::bad)?;
    let runs = state
        .database
        .query_runs(&run_query)
        .map_err(ApiError::bad)?;
    let workers = state.database.workers().map_err(ApiError::internal)?;
    let queued_runs = state.database.queued_runs().map_err(ApiError::internal)?;
    Ok(ok(StateResponse {
        repositories,
        runs,
        workers,
        queued_runs,
        run_total,
        run_cursor,
    }))
}

async fn delivery_metrics(
    State(state): State<AppState>,
    headers: HeaderMap,
    Query(query): Query<DeliveryMetricsQuery>,
) -> Result<Json<Envelope<crate::db::DeliveryMetrics>>, ApiError> {
    authorize(&headers, &state)?;
    let repository_name = query
        .repo
        .as_deref()
        .ok_or_else(|| ApiError::bad("repo is required for repository-scoped metrics"))?;
    let repository = state
        .database
        .repository(repository_name)
        .map_err(|_| ApiError::not_found("repository not found"))?;
    if repository.visibility != "public"
        && !valid_admin_session(&headers, &state)
        && !valid_bearer(&headers, &state)
    {
        return Err(ApiError::not_found("repository not found"));
    }
    let window = query.window.unwrap_or(7 * 86_400);
    Ok(ok(state
        .database
        .delivery_metrics(Some(repository_name), query.environment.as_deref(), window)
        .map_err(ApiError::bad)?))
}

async fn api_workers(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Json<Envelope<Vec<crate::db::Worker>>>, ApiError> {
    authorize(&headers, &state)?;
    Ok(ok(state.database.workers().map_err(ApiError::internal)?))
}

fn visible_run_detail(
    state: &AppState,
    headers: &HeaderMap,
    id: i64,
) -> Result<crate::db::RunDetail, ApiError> {
    let mut detail = state.database.detail(id).map_err(ApiError::bad)?;
    let repository = state
        .database
        .repository(&detail.run.repository)
        .map_err(ApiError::bad)?;
    let privileged = valid_admin_session(headers, state) || valid_bearer(headers, state);
    if repository.visibility != "public" && !privileged {
        return Err(ApiError::not_found("run not found"));
    }
    if !privileged {
        for deployment in &mut detail.deployments {
            deployment.organization_id = None;
            deployment.workspace_id = None;
            deployment.installation_id = None;
            deployment.app_id = None;
            deployment.installation_revision = None;
            deployment.commit_sha = None;
            deployment.artifact_digest = None;
            deployment.result = None;
            deployment.rollback_of_deployment_id = None;
            deployment.recovery_of_deployment_id = None;
        }
    }
    Ok(detail)
}
async fn run_detail(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(id): Path<i64>,
) -> Result<Json<Envelope<crate::db::RunDetail>>, ApiError> {
    authorize(&headers, &state)?;
    Ok(ok(visible_run_detail(&state, &headers, id)?))
}

async fn run_events(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(id): Path<i64>,
) -> Result<Response, ApiError> {
    authorize(&headers, &state)?;
    visible_run_detail(&state, &headers, id)?;

    let database = state.database.clone();
    let (reader, mut writer) = tokio::io::duplex(64 * 1024);
    tokio::spawn(async move {
        let mut previous = String::new();
        let mut ticker = tokio::time::interval(Duration::from_millis(500));
        loop {
            ticker.tick().await;
            let detail = match database.detail(id) {
                Ok(detail) => detail,
                Err(error) => {
                    let payload = serde_json::json!({"run_id": id, "status": "failed", "error": error.to_string()});
                    let _ = writer
                        .write_all(format!("event: error\ndata: {payload}\n\n").as_bytes())
                        .await;
                    break;
                }
            };
            let terminal = is_terminal_run(&detail.run.status);
            let payload = match serde_json::to_string(&run_progress(detail)) {
                Ok(payload) => payload,
                Err(_) => break,
            };
            if payload != previous {
                let frame = format!("event: progress\ndata: {payload}\n\n");
                if writer.write_all(frame.as_bytes()).await.is_err() {
                    break;
                }
                previous = payload;
            } else if writer.write_all(b": keepalive\n\n").await.is_err() {
                break;
            }
            if terminal {
                break;
            }
        }
    });

    let mut response = Response::new(Body::from_stream(ReaderStream::new(reader)));
    response.headers_mut().insert(
        header::CONTENT_TYPE,
        HeaderValue::from_static("text/event-stream"),
    );
    response.headers_mut().insert(
        header::CACHE_CONTROL,
        HeaderValue::from_static("no-cache, no-transform"),
    );
    response.headers_mut().insert(
        header::CONTENT_ENCODING,
        HeaderValue::from_static("identity"),
    );
    response.headers_mut().insert(
        HeaderName::from_static("x-accel-buffering"),
        HeaderValue::from_static("no"),
    );
    Ok(response)
}

fn run_progress(detail: crate::db::RunDetail) -> RunProgress {
    let job_count = detail.jobs.len() as u32;
    let job_units = detail
        .jobs
        .iter()
        .map(|job| match job.status.as_str() {
            "succeeded" | "failed" | "canceled" | "skipped" => 4,
            "waiting" => 3,
            "running" => 2,
            _ => 0,
        })
        .sum::<u32>();
    let progress = match detail.run.status.as_str() {
        "succeeded" => 100,
        "queued" => 2,
        "waiting" => 85,
        _ if job_count > 0 => (5 + (80 * job_units / (job_count * 4))).min(95) as u8,
        _ => 5,
    };
    let stage = if detail.run.status == "succeeded" {
        "succeeded".to_string()
    } else if is_terminal_run(&detail.run.status) {
        detail.run.status.clone()
    } else if detail.run.status == "waiting"
        || detail
            .jobs
            .iter()
            .any(|job| job.status == "waiting" && job.approval_required)
    {
        "waiting_for_approval".to_string()
    } else if let Some(deployment) = detail
        .deployments
        .iter()
        .rev()
        .find(|deployment| deployment.status != "succeeded")
    {
        format!("deploying:{}", deployment.environment)
    } else if let Some(job) = detail.jobs.iter().find(|job| job.status == "running") {
        format!("running:{}", job.name)
    } else {
        detail.run.status.clone()
    };

    RunProgress {
        run_id: detail.run.id,
        repository: detail.run.repository,
        git_ref: detail.run.git_ref,
        commit_sha: detail.run.commit_sha,
        status: detail.run.status,
        stage,
        progress,
        jobs: detail
            .jobs
            .into_iter()
            .map(|job| ProgressJob {
                id: job.id,
                name: job.name,
                status: job.status,
                environment: job.environment,
                approval_required: job.approval_required,
            })
            .collect(),
        deployments: detail
            .deployments
            .into_iter()
            .map(|deployment| ProgressDeployment {
                id: deployment.id,
                environment: deployment.environment,
                status: deployment.status,
            })
            .collect(),
        error: detail.run.error,
    }
}

fn is_terminal_run(status: &str) -> bool {
    matches!(status, "succeeded" | "failed" | "canceled" | "interrupted")
}

async fn create_repository(
    State(state): State<AppState>,
    headers: HeaderMap,
    Json(request): Json<RepositoryRequest>,
) -> Result<Json<Envelope<RepositoryView>>, ApiError> {
    authorize_mutation(&headers, &state)?;
    config::validate_repository(&request.name, &request.url, &request.branch)
        .map_err(ApiError::bad)?;
    let repository = state
        .database
        .add_repository(&request.name, &request.url, &request.branch)
        .map_err(ApiError::bad)?;
    Ok(ok(repository_view(
        repository,
        &state.hosted_root,
        &state.public_origin,
    )))
}

async fn change_repository_visibility(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
    Json(request): Json<VisibilityRequest>,
) -> Result<Json<Envelope<RepositoryView>>, ApiError> {
    authorize_mutation(&headers, &state)?;
    let repository = state
        .database
        .set_repository_visibility(&repository, &request.visibility)
        .map_err(ApiError::bad)?;
    Ok(ok(repository_view(
        repository,
        &state.hosted_root,
        &state.public_origin,
    )))
}

async fn trigger(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
    Json(request): Json<TriggerRequest>,
) -> Result<Json<Envelope<IdResponse>>, ApiError> {
    authorize_mutation(&headers, &state)?;
    let id = state
        .runner
        .queue(
            &repository,
            request.git_ref.as_deref(),
            request.commit.as_deref(),
            "manual",
        )
        .map_err(ApiError::bad)?;
    Ok(ok(IdResponse { id }))
}

async fn branches(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
) -> Result<Json<Envelope<Vec<git_process::RemoteBranch>>>, ApiError> {
    authorize(&headers, &state)?;
    let repo = state
        .database
        .repository(&repository)
        .map_err(|_| ApiError::bad("repository not found"))?;
    let default_branch = repo.default_branch;
    let url = repo.url;
    let branches = state
        .blocking_git
        .run(move || git_process::remote_branches(&url, &default_branch).map_err(ApiError::bad))
        .await?;
    Ok(ok(branches))
}

#[derive(Deserialize)]
struct TreeQuery {
    #[serde(rename = "ref")]
    reference: Option<String>,
    path: Option<String>,
}

async fn repo_tree(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
    Query(query): Query<TreeQuery>,
) -> Result<Json<Envelope<crate::tree::TreeResponse>>, ApiError> {
    authorize(&headers, &state)?;
    let repo = state
        .database
        .repository(&repository)
        .map_err(|_| ApiError::bad("repository not found"))?;
    let sub_path = query.path.unwrap_or_default();
    let tree = state.tree.clone();
    let name = repo.name;
    let url = repo.url;
    let reference = query.reference;
    let response = state
        .blocking_git
        .run(move || {
            tree.tree(&name, &url, reference.as_deref(), &sub_path)
                .map_err(ApiError::bad)
        })
        .await?;
    Ok(ok(response))
}

async fn retry(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(id): Path<i64>,
) -> Result<Json<Envelope<IdResponse>>, ApiError> {
    authorize_mutation(&headers, &state)?;
    let id = state.database.retry(id).map_err(ApiError::bad)?;
    Ok(ok(IdResponse { id }))
}

async fn promote(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path((id, environment)): Path<(i64, String)>,
) -> Result<Json<Envelope<Value>>, ApiError> {
    authorize_mutation(&headers, &state)?;
    let jobs = state
        .database
        .approve_environment(id, &environment)
        .map_err(ApiError::bad)?;
    Ok(ok(
        serde_json::json!({"run_id":id,"environment":environment,"jobs":jobs}),
    ))
}

async fn artifact(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(id): Path<i64>,
) -> Result<Response, ApiError> {
    authorize(&headers, &state)?;
    let artifact = state.database.artifact(id).map_err(ApiError::bad)?;
    let path = state
        .runner
        .artifact_path(&artifact)
        .map_err(ApiError::internal)?;
    let file = tokio::fs::File::open(path)
        .await
        .map_err(ApiError::internal)?;
    let mut response = Response::new(Body::from_stream(ReaderStream::new(file)));
    response.headers_mut().insert(
        header::CONTENT_TYPE,
        HeaderValue::from_static("application/octet-stream"),
    );
    response.headers_mut().insert(
        header::CONTENT_LENGTH,
        HeaderValue::from_str(&artifact.bytes.to_string()).map_err(ApiError::internal)?,
    );
    let filename = artifact
        .name
        .rsplit('/')
        .next()
        .unwrap_or("artifact")
        .replace(['"', '\r', '\n'], "_");
    response.headers_mut().insert(
        header::CONTENT_DISPOSITION,
        HeaderValue::from_str(&format!("attachment; filename=\"{filename}\""))
            .map_err(ApiError::internal)?,
    );
    Ok(response)
}

async fn webhook(
    State(state): State<AppState>,
    headers: HeaderMap,
    Path(repository): Path<String>,
    body: Bytes,
) -> Result<Json<Envelope<IdResponse>>, ApiError> {
    authorize_webhook(&headers, &state, &body)?;
    let payload: Value = serde_json::from_slice(&body).map_err(ApiError::bad)?;
    let git_ref = payload
        .get("ref")
        .and_then(Value::as_str)
        .context("webhook has no ref")
        .map_err(ApiError::bad)?;
    let commit = payload
        .get("after")
        .or_else(|| payload.get("checkout_sha"))
        .and_then(Value::as_str)
        .or_else(|| {
            payload
                .get("head_commit")
                .and_then(|value| value.get("id"))
                .and_then(Value::as_str)
        });
    let commit = commit.filter(|value| value.bytes().any(|byte| byte != b'0'));
    let id = state
        .runner
        .queue(
            &repository,
            Some(git_ref.strip_prefix("refs/heads/").unwrap_or(git_ref)),
            commit,
            "webhook",
        )
        .map_err(ApiError::bad)?;
    Ok(ok(IdResponse { id }))
}

fn login_state(state: &AppState) -> Result<String, ApiError> {
    let mut nonce = [0_u8; 16];
    getrandom::fill(&mut nonce).map_err(ApiError::internal)?;
    let payload = format!("{}:{}", encode_hex(&nonce), unix_time()?);
    Ok(format!("{payload}.{}", sign(&payload, state)?))
}
fn list_filter(value: Option<String>) -> Vec<String> {
    value
        .into_iter()
        .flat_map(|value| {
            value
                .split(',')
                .map(str::trim)
                .filter(|item| !item.is_empty())
                .map(str::to_owned)
                .collect::<Vec<_>>()
        })
        .collect()
}

fn valid_login_state(value: &str, state: &AppState) -> bool {
    let Some((payload, supplied)) = value.rsplit_once('.') else {
        return false;
    };
    let Some((_, created)) = payload.rsplit_once(':') else {
        return false;
    };
    let Ok(created) = created.parse::<u64>() else {
        return false;
    };
    unix_time().is_ok_and(|now| now.saturating_sub(created) <= 300)
        && sign(payload, state)
            .is_ok_and(|expected| constant_time_equal(supplied.as_bytes(), expected.as_bytes()))
}

fn session_cookie(user: &UserInfo, role: &str, state: &AppState) -> Result<String, ApiError> {
    let payload = format!(
        "{}:{}:{}:{}",
        unix_time()? + 3600,
        user.sub,
        user.email,
        role
    );
    let value = format!(
        "{}.{}",
        encode_hex(payload.as_bytes()),
        sign(&payload, state)?
    );
    Ok(format!(
        "ab_session={value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600"
    ))
}

fn request_cookie<'a>(headers: &'a HeaderMap, name: &str) -> Option<&'a str> {
    headers
        .get(header::COOKIE)?
        .to_str()
        .ok()?
        .split(';')
        .map(str::trim)
        .find_map(|pair| pair.strip_prefix(name)?.strip_prefix('='))
}

fn unix_time() -> Result<u64, ApiError> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_secs())
        .map_err(ApiError::internal)
}
fn valid_bearer(headers: &HeaderMap, state: &AppState) -> bool {
    headers
        .get(header::AUTHORIZATION)
        .and_then(|value| value.to_str().ok())
        .and_then(|value| value.strip_prefix("Bearer "))
        .is_some_and(|supplied| constant_time_equal(supplied.as_bytes(), state.token.as_bytes()))
}

fn sign(value: &str, state: &AppState) -> Result<String, ApiError> {
    sign_with_secret(value, &state.token)
}

fn sign_with_secret(value: &str, secret: &str) -> Result<String, ApiError> {
    let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).map_err(ApiError::internal)?;
    mac.update(value.as_bytes());
    Ok(encode_hex(&mac.finalize().into_bytes()))
}

fn encode_hex(value: &[u8]) -> String {
    value.iter().map(|byte| format!("{byte:02x}")).collect()
}
fn authorize(headers: &HeaderMap, state: &AppState) -> Result<(), ApiError> {
    if let Some(supplied) = headers
        .get(header::AUTHORIZATION)
        .and_then(|value| value.to_str().ok())
        .and_then(|value| value.strip_prefix("Bearer "))
    {
        return constant_time_equal(supplied.as_bytes(), state.token.as_bytes())
            .then_some(())
            .ok_or_else(ApiError::unauthorized);
    }
    valid_session(headers, state)
        .then_some(())
        .ok_or_else(ApiError::unauthorized)
}

fn valid_session(headers: &HeaderMap, state: &AppState) -> bool {
    request_cookie(headers, "ab_session")
        .is_some_and(|cookie| valid_session_cookie(cookie, &state.token))
}

fn valid_session_cookie(cookie: &str, secret: &str) -> bool {
    let Some((encoded_payload, supplied_signature)) = cookie.rsplit_once('.') else {
        return false;
    };
    let Some(payload) = hex_to_text(encoded_payload) else {
        return false;
    };
    let Ok(expected_signature) = sign_with_secret(&payload, secret) else {
        return false;
    };
    if !constant_time_equal(supplied_signature.as_bytes(), expected_signature.as_bytes()) {
        return false;
    }
    let mut fields = payload.splitn(4, ':');
    let Some(expires_at) = fields.next().and_then(|value| value.parse::<u64>().ok()) else {
        return false;
    };
    unix_time().is_ok_and(|now| expires_at >= now)
        && fields.next().is_some_and(|value| !value.is_empty())
        && fields.next().is_some_and(|value| !value.is_empty())
        && fields
            .next()
            .is_some_and(|value| matches!(value, "admin" | "member"))
}
fn valid_admin_session(headers: &HeaderMap, state: &AppState) -> bool {
    let Some(cookie) = request_cookie(headers, "ab_session") else {
        return false;
    };
    let Some((encoded_payload, supplied_signature)) = cookie.rsplit_once('.') else {
        return false;
    };
    let Some(payload) = hex_to_text(encoded_payload) else {
        return false;
    };
    let Ok(expected_signature) = sign_with_secret(&payload, &state.token) else {
        return false;
    };
    if !constant_time_equal(supplied_signature.as_bytes(), expected_signature.as_bytes()) {
        return false;
    }
    let mut fields = payload.splitn(4, ':');
    let Some(expires_at) = fields.next().and_then(|value| value.parse::<u64>().ok()) else {
        return false;
    };
    unix_time().is_ok_and(|now| expires_at >= now)
        && fields.next().is_some_and(|value| !value.is_empty())
        && fields.next().is_some_and(|value| !value.is_empty())
        && fields.next().is_some_and(|value| value == "admin")
}

fn hex_to_text(value: &str) -> Option<String> {
    if value.len() > 1024 || !value.len().is_multiple_of(2) {
        return None;
    }
    value
        .as_bytes()
        .chunks_exact(2)
        .map(|pair| {
            std::str::from_utf8(pair)
                .ok()
                .and_then(|pair| u8::from_str_radix(pair, 16).ok())
        })
        .collect::<Option<Vec<_>>>()
        .and_then(|bytes| String::from_utf8(bytes).ok())
}

fn authorize_mutation(headers: &HeaderMap, state: &AppState) -> Result<(), ApiError> {
    authorize(headers, state)?;
    if let Some(origin) = headers
        .get(header::ORIGIN)
        .and_then(|value| value.to_str().ok())
        && origin != state.public_origin.as_ref()
    {
        return Err(ApiError::bad("origin is not allowed"));
    }
    Ok(())
}

fn authorize_webhook(headers: &HeaderMap, state: &AppState, body: &[u8]) -> Result<(), ApiError> {
    if let Some(token) = headers
        .get("x-gitlab-token")
        .and_then(|value| value.to_str().ok())
        && constant_time_equal(token.as_bytes(), state.webhook_secret.as_bytes())
    {
        return Ok(());
    }
    for name in ["x-hub-signature-256", "x-gitea-signature"] {
        if let Some(signature) = headers.get(name).and_then(|value| value.to_str().ok()) {
            let signature = signature.strip_prefix("sha256=").unwrap_or(signature);
            let supplied = decode_hex(signature).map_err(ApiError::bad)?;
            let mut mac = HmacSha256::new_from_slice(state.webhook_secret.as_bytes())
                .map_err(ApiError::internal)?;
            mac.update(body);
            if constant_time_equal(&supplied, &mac.finalize().into_bytes()) {
                return Ok(());
            }
        }
    }
    Err(ApiError::unauthorized())
}

fn decode_hex(value: &str) -> Result<Vec<u8>> {
    ensure!(
        value.len().is_multiple_of(2) && value.len() <= 128,
        "invalid signature"
    );
    value
        .as_bytes()
        .chunks_exact(2)
        .map(|pair| {
            let text = std::str::from_utf8(pair)?;
            Ok(u8::from_str_radix(text, 16)?)
        })
        .collect()
}

fn constant_time_equal(left: &[u8], right: &[u8]) -> bool {
    left.len() == right.len() && left.ct_eq(right).into()
}

fn ok<T: Serialize>(data: T) -> Json<Envelope<T>> {
    Json(Envelope { ok: true, data })
}

async fn security_headers(request: Request<Body>, next: Next) -> Response {
    let mut response = next.run(request).await;
    let is_json = response
        .headers()
        .get(header::CONTENT_TYPE)
        .and_then(|value| value.to_str().ok())
        .is_some_and(|value| value.contains("json"));
    let headers = response.headers_mut();
    headers.insert(
        header::X_CONTENT_TYPE_OPTIONS,
        HeaderValue::from_static("nosniff"),
    );
    headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
    headers.insert(
        header::REFERRER_POLICY,
        HeaderValue::from_static("no-referrer"),
    );
    headers.insert(header::CONTENT_SECURITY_POLICY, HeaderValue::from_static("default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self'; connect-src 'self'"));
    headers.insert(
        HeaderName::from_static("permissions-policy"),
        HeaderValue::from_static("camera=(), geolocation=(), microphone=()"),
    );
    headers.insert(
        HeaderName::from_static("cross-origin-opener-policy"),
        HeaderValue::from_static("same-origin"),
    );
    headers.insert(
        HeaderName::from_static("cross-origin-resource-policy"),
        HeaderValue::from_static("same-origin"),
    );
    if is_json {
        headers.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
    }
    response
}

async fn serve_bounded(listener: tokio::net::TcpListener, app: Router) -> Result<()> {
    use hyper_util::{
        rt::{TokioExecutor, TokioIo, TokioTimer},
        server::{conn::auto::Builder, graceful::GracefulShutdown},
        service::TowerToHyperService,
    };
    info!(address = %listener.local_addr()?, "AkurAI Build listening");
    let connections = Arc::new(tokio::sync::Semaphore::new(MAX_CONNECTIONS));
    let graceful = GracefulShutdown::new();
    let mut shutdown = std::pin::pin!(shutdown_signal());
    loop {
        let permit = tokio::select! { () = &mut shutdown => break, permit = connections.clone().acquire_owned() => permit? };
        let (socket, _) = tokio::select! { () = &mut shutdown => break, accepted = listener.accept() => accepted? };
        let mut builder = Builder::new(TokioExecutor::new());
        builder
            .http1()
            .timer(TokioTimer::new())
            .header_read_timeout(REQUEST_TIMEOUT);
        let connection = graceful.watch(
            builder
                .serve_connection_with_upgrades(
                    TokioIo::new(socket),
                    TowerToHyperService::new(app.clone()),
                )
                .into_owned(),
        );
        tokio::spawn(async move {
            if let Err(error) = connection.await {
                tracing::debug!(?error, "connection closed");
            }
            drop(permit);
        });
    }
    drop(listener);
    tokio::select! { () = graceful.shutdown() => {}, () = tokio::time::sleep(REQUEST_TIMEOUT) => warn!("graceful shutdown timed out") }
    Ok(())
}

async fn shutdown_signal() {
    let ctrl_c = async {
        if let Err(error) = tokio::signal::ctrl_c().await {
            warn!(?error, "failed to install Ctrl-C handler");
        }
    };
    #[cfg(unix)]
    let terminate = async {
        match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
            Ok(mut signal) => {
                signal.recv().await;
            }
            Err(error) => warn!(?error, "failed to install terminate handler"),
        }
    };
    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();
    tokio::select! { () = ctrl_c => {}, () = terminate => {} }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tower::Service;
    #[test]
    fn templates_compile_and_render_without_inline_code() -> Result<()> {
        let environment = template_environment()?;
        let page = environment.get_template("landing.html")?.render(context! {
            version => "test",
            repositories => vec![LandingRepository {
                name: "AkurAI-Build".into(),
                default_branch: "main".into(),
                owner: "olibuijr".into(),
                run_count: 4,
                successful_runs: 3,
            }],
            builders => vec![PublicBuilder {
                name: "olibuijr".into(),
                initial: "O".into(),
                repository_count: 1,
            }]
        })?;
        let docs = environment
            .get_template("docs.html")?
            .render(context! { version => "test" })?;
        assert!(docs.contains("AkurAI Build documentation"));
        assert!(docs.contains("akurai_run_queue"));
        assert!(!docs.contains("Bunfork"));
        assert!(docs.contains("AkurAI <b>Build</b>"));
        assert!(!docs.contains("AkurAI <b>/Build</b>"));
        assert!(page.contains("olibuijr /</span> AkurAI-Build"));
        assert!(page.contains("Trending repositories"));
        assert!(page.contains("Public builders"));
        assert!(!page.contains("<script>"));
        Ok(())
    }

    #[tokio::test]
    async fn public_errors_negotiate_branded_html_and_json() -> Result<()> {
        let environment = template_environment()?;
        let mut html_headers = HeaderMap::new();
        html_headers.insert(
            header::ACCEPT,
            HeaderValue::from_static("text/html,application/xhtml+xml"),
        );
        let html = public_error_response(
            &environment,
            &html_headers,
            ApiError::not_found("missing"),
            "Page not found",
            "This page could not be found.",
            "Return home or open the documentation.",
        )
        .map_err(|error| anyhow::anyhow!(error.message))?;
        assert_eq!(html.status(), StatusCode::NOT_FOUND);
        assert_eq!(
            html.headers().get(header::CONTENT_TYPE),
            Some(&HeaderValue::from_static("text/html; charset=utf-8"))
        );
        let html_body = to_bytes(html.into_body(), MAX_BODY).await?;
        let html_body = String::from_utf8(html_body.to_vec())?;
        assert!(html_body.contains("AkurAI <b>Build</b>"));
        assert!(html_body.contains("Go home"));
        assert!(!html_body.contains("{\"ok\":false"));

        let mut json_headers = HeaderMap::new();
        json_headers.insert(header::ACCEPT, HeaderValue::from_static("application/json"));
        let json = public_error_response(
            &environment,
            &json_headers,
            ApiError::not_found("missing"),
            "Page not found",
            "This page could not be found.",
            "Return home or open the documentation.",
        )
        .map_err(|error| anyhow::anyhow!(error.message))?;
        assert_eq!(json.status(), StatusCode::NOT_FOUND);
        assert_eq!(
            json.headers().get(header::CONTENT_TYPE),
            Some(&HeaderValue::from_static("application/json"))
        );
        let json_body = to_bytes(json.into_body(), MAX_BODY).await?;
        let json_body: Value = serde_json::from_slice(&json_body)?;
        assert_eq!(json_body["error"]["code"], "not_found");
        assert_eq!(json_body["error"]["message"], "missing");
        Ok(())
    }

    fn authenticated_session_cookie() -> Result<String> {
        let payload = format!(
            "{}:test-user:test@example.com:member",
            unix_time()
                .map_err(|error| anyhow::anyhow!(error.message))?
                .saturating_add(3600)
        );
        let signature = sign_with_secret(&payload, "api-token-123")
            .map_err(|error| anyhow::anyhow!(error.message))?;
        Ok(format!(
            "ab_session={}.{}",
            encode_hex(payload.as_bytes()),
            signature
        ))
    }

    #[tokio::test]
    async fn not_found_handlers_negotiate_branded_html_and_json_end_to_end() -> Result<()> {
        let session = authenticated_session_cookie()?;
        let cases = [
            ("/missing", "This page could not be found.", None),
            (
                "/repos/missing",
                "This repository could not be found.",
                None,
            ),
            (
                "/repos/missing/tree/main",
                "Repository content could not be found.",
                None,
            ),
            (
                "/repos/missing/tree/main/src/missing.rs",
                "Repository content could not be found.",
                None,
            ),
            (
                "/app/repositories/missing/tree/main",
                "Repository content could not be found.",
                Some(session.as_str()),
            ),
            (
                "/app/repositories/missing/tree/main/src/missing.rs",
                "Repository content could not be found.",
                Some(session.as_str()),
            ),
        ];

        for (uri, heading, cookie) in cases {
            let mut request = Request::builder()
                .uri(uri)
                .header(header::ACCEPT, "text/html,application/xhtml+xml");
            if let Some(cookie) = cookie {
                request = request.header(header::COOKIE, cookie);
            }
            let mut app = test_router();
            let response = app.call(request.body(Body::empty())?).await?;
            assert_eq!(response.status(), StatusCode::NOT_FOUND, "{uri}");
            assert_eq!(
                response.headers().get(header::CONTENT_TYPE),
                Some(&HeaderValue::from_static("text/html; charset=utf-8")),
                "{uri}"
            );
            let body = to_bytes(response.into_body(), MAX_BODY).await?;
            let body = String::from_utf8(body.to_vec())?;
            assert!(body.contains("AkurAI <b>Build</b>"), "{uri}");
            assert!(body.contains(heading), "{uri}");
            assert!(!body.contains("{\"ok\":false"), "{uri}");

            let mut request = Request::builder()
                .uri(uri)
                .header(header::ACCEPT, "application/json");
            if let Some(cookie) = cookie {
                request = request.header(header::COOKIE, cookie);
            }
            let mut app = test_router();
            let response = app.call(request.body(Body::empty())?).await?;
            assert_eq!(response.status(), StatusCode::NOT_FOUND, "{uri}");
            assert_eq!(
                response.headers().get(header::CONTENT_TYPE),
                Some(&HeaderValue::from_static("application/json")),
                "{uri}"
            );
            let body = json_body(response).await;
            assert_eq!(body["ok"], false, "{uri}");
            assert_eq!(body["error"]["code"], "not_found", "{uri}");
        }
        Ok(())
    }

    #[test]
    fn directory_sizes_render_as_not_applicable() -> Result<()> {
        let environment = template_environment()?;
        let page = CodePage {
            repository: RepositoryView {
                id: 1,
                name: "AkurAI-Build".into(),
                url: "https://example.com/AkurAI-Build.git".into(),
                default_branch: "main".into(),
                visibility: "public".into(),
                created_at: 0,
            },
            reference: "main".into(),
            path: String::new(),
            entries: vec![
                CodeEntryView {
                    name: "src".into(),
                    kind: "tree".into(),
                    size: 0,
                    commit_message: None,
                    href: "/repos/AkurAI-Build/tree/main/src".into(),
                },
                CodeEntryView {
                    name: "Cargo.toml".into(),
                    kind: "blob".into(),
                    size: 42,
                    commit_message: None,
                    href: "/repos/AkurAI-Build/tree/main/Cargo.toml".into(),
                },
            ],
            breadcrumbs: vec![CodeBreadcrumb {
                label: "AkurAI-Build".into(),
                href: "/repos/AkurAI-Build/tree/main".into(),
            }],
            source: None,
            is_file: false,
            parent_href: "/repos/AkurAI-Build".into(),
            latest_commit: None,
            commit_href: None,
            public: true,
        };
        let rendered = environment
            .get_template("repository-code.html")?
            .render(context! { page => page })?;
        assert!(rendered.contains(
            "<td data-label=\"Size\"><span aria-label=\"Not applicable\">&mdash;</span></td>"
        ));
        assert!(rendered.contains("<td data-label=\"Size\">42</td>"));
        assert!(!rendered.contains("<td data-label=\"Size\">0</td>"));
        Ok(())
    }

    #[test]
    fn code_path_segments_encode_special_characters_and_separators() {
        assert_eq!(encode_code_segment("!*'()"), "%21%2A%27%28%29");
        assert_eq!(
            code_path_href(
                "/app/repositories/repo/tree/main",
                "bang!/star*/quote'/left(/right)"
            ),
            "/app/repositories/repo/tree/main/bang%21/star%2A/quote%27/left%28/right%29"
        );
        assert_eq!(encode_code_segment("../secret\\file"), "..%2Fsecret%5Cfile");
    }

    #[test]
    fn public_repository_owner_uses_remote_namespace() {
        assert_eq!(
            repository_owner("https://github.com/olibuijr/AkurAI-Build.git"),
            "olibuijr"
        );
        assert_eq!(
            repository_owner("git@gitlab.example.com:platform/service.git"),
            "platform"
        );
        assert_eq!(
            repository_owner("https://akurai-build.example.com/git/service.git"),
            "AkurAI Build"
        );
    }

    #[test]
    fn hosted_repository_url_uses_public_clone_route() {
        let root = FsPath::new("/home/olafurbui/.local/share/akurai-build");
        assert_eq!(
            repository_client_url(
                "AkurAI-Build",
                "/home/olafurbui/.local/share/akurai-build/hosted/AkurAI-Build.git",
                root,
                "https://akurai-build.olibuijr.com",
            ),
            "https://akurai-build.olibuijr.com/git/AkurAI-Build.git"
        );
        assert_eq!(
            repository_client_url(
                "AkurAI-Build",
                "https://github.com/olibuijr/AkurAI-Build.git",
                root,
                "https://akurai-build.olibuijr.com",
            ),
            "https://github.com/olibuijr/AkurAI-Build.git"
        );
    }

    #[test]
    fn authentication_helpers_fail_closed() {
        assert!(constant_time_equal(b"same", b"same"));
        assert!(!constant_time_equal(b"same", b"different"));
        assert!(decode_hex("not-hex").is_err());
    }

    #[test]
    fn signed_browser_session_authorizes_without_bearer_token() {
        let secret = "test-secret";
        let payload = "4102444800:user-1:olafurbui@example.com:member";
        let signature = match sign_with_secret(payload, secret) {
            Ok(signature) => signature,
            Err(_) => panic!("valid test signing key"),
        };
        let cookie = format!("{}.{}", encode_hex(payload.as_bytes()), signature);
        assert!(valid_session_cookie(&cookie, secret));
        assert!(!valid_session_cookie(&cookie, "other-secret"));
        assert!(!valid_session_cookie("00.invalid", secret));
    }

    #[test]
    fn git_smart_http_paths_and_cgi_responses_are_bounded() -> Result<()> {
        validate_git_path("tasks.git/info/refs")?;
        assert!(validate_git_path("../tasks.git/info/refs").is_err());
        assert!(validate_git_path("tasks/info/refs").is_err());

        let response = git_cgi_response(
            b"Status: 200 OK\r\nContent-Type: application/x-git-upload-pack-result\r\n\r\npack",
        )?;
        assert_eq!(response.status(), StatusCode::OK);
        assert_eq!(
            response.headers().get(header::CONTENT_TYPE),
            Some(&HeaderValue::from_static(
                "application/x-git-upload-pack-result"
            ))
        );
        Ok(())
    }

    // ── constant_time_equal boundaries ──

    #[test]
    fn constant_time_equal_rejects_empty_input() {
        assert!(!constant_time_equal(b"", b"token"));
        assert!(!constant_time_equal(b"token", b""));
    }

    #[test]
    fn constant_time_equal_rejects_wrong_token() {
        assert!(!constant_time_equal(b"correct", b"wrong-token"));
        assert!(!constant_time_equal(b"xyz", b"abc"));
    }

    #[test]
    fn constant_time_equal_rejects_too_short_token() {
        assert!(!constant_time_equal(b"secret", b"sec"));
    }

    #[test]
    fn constant_time_equal_rejects_correct_prefix_but_longer_token() {
        // "secret" is a prefix of "secret-extended" — must be rejected
        assert!(!constant_time_equal(b"secret", b"secret-extended"));
        assert!(!constant_time_equal(b"secret-extended", b"secret"));
    }

    #[test]
    fn constant_time_equal_accepts_exact_match_only() {
        assert!(constant_time_equal(
            b"exact-token-value",
            b"exact-token-value"
        ));
        assert!(constant_time_equal(b"a", b"a"));
    }

    // ── decode_hex boundaries ──

    #[test]
    fn decode_hex_rejects_odd_length() {
        assert!(decode_hex("abc").is_err());
    }

    #[test]
    fn decode_hex_rejects_non_hex_characters() {
        assert!(decode_hex("zz").is_err());
        assert!(decode_hex("gg12").is_err());
    }

    #[test]
    fn decode_hex_accepts_valid_hex() -> Result<()> {
        assert_eq!(decode_hex("48656c6c6f")?, b"Hello");
        assert_eq!(decode_hex("00ff")?, vec![0x00, 0xff]);
        assert_eq!(decode_hex("")?, Vec::<u8>::new());
        Ok(())
    }

    // ── authorize header parsing ──

    fn test_state(token: &str, webhook_secret: &str) -> AppState {
        const KEY: &str = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
        let temp = tempfile::tempdir().expect("tempdir");
        let database = Database::memory(KEY).expect("database");
        let runner = Runner::new(database.clone(), temp.path().to_owned(), false).expect("runner");
        let tree = RepoTree::new(temp.path()).expect("tree");
        AppState {
            database,
            runner,
            tree,
            blocking_git: BlockingGit::new(MAX_BLOCKING_GIT),
            hosted_root: temp.path().to_owned(),
            token: Arc::from(token),
            webhook_secret: Arc::from(webhook_secret),
            public_origin: Arc::from("https://build.example"),
            templates: Arc::new(template_environment().expect("templates")),
        }
    }

    #[test]
    fn authorize_rejects_missing_authorization_header() {
        let state = test_state("api-token-123", "wh-secret");
        let headers = HeaderMap::new();
        let result = authorize(&headers, &state);
        assert!(result.is_err());
        assert_eq!(
            result.expect_err("should be unauthorized").status,
            StatusCode::UNAUTHORIZED
        );
    }

    #[test]
    fn authorize_rejects_malformed_scheme_not_bearer() {
        let state = test_state("api-token-123", "wh-secret");
        let mut headers = HeaderMap::new();
        headers.insert(
            header::AUTHORIZATION,
            HeaderValue::from_static("Basic some-value"),
        );
        let result = authorize(&headers, &state);
        assert!(result.is_err());
    }

    #[test]
    fn authorize_rejects_bearer_with_wrong_token() {
        let state = test_state("api-token-123", "wh-secret");
        let mut headers = HeaderMap::new();
        headers.insert(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer wrong-value"),
        );
        let result = authorize(&headers, &state);
        assert!(result.is_err());
    }

    #[test]
    fn authorize_accepts_correct_bearer_token() {
        let state = test_state("api-token-123", "wh-secret");
        let mut headers = HeaderMap::new();
        headers.insert(
            header::AUTHORIZATION,
            HeaderValue::from_static("Bearer api-token-123"),
        );
        assert!(authorize(&headers, &state).is_ok());
    }

    #[test]
    fn authorize_webhook_rejects_missing_header() {
        let state = test_state("api-token", "webhook-secret");
        let headers = HeaderMap::new();
        let result = authorize_webhook(&headers, &state, b"{}");
        assert!(result.is_err());
        assert_eq!(
            result.expect_err("should be unauthorized").status,
            StatusCode::UNAUTHORIZED
        );
    }

    #[test]
    fn authorize_webhook_rejects_wrong_token() {
        let state = test_state("api-token", "webhook-secret");
        let mut headers = HeaderMap::new();
        headers.insert("x-gitlab-token", HeaderValue::from_static("wrong-token"));
        let result = authorize_webhook(&headers, &state, b"{}");
        assert!(result.is_err());
    }

    #[test]
    fn authorize_webhook_accepts_correct_x_gitlab_token() {
        let state = test_state("api-token", "webhook-secret");
        let mut headers = HeaderMap::new();
        headers.insert("x-gitlab-token", HeaderValue::from_static("webhook-secret"));
        assert!(authorize_webhook(&headers, &state, b"{}").is_ok());
    }

    // ── ApiError envelope shape ──

    #[tokio::test]
    async fn unauthorized_error_response_has_correct_envelope_shape() {
        let error = ApiError::unauthorized();
        let response = error.into_response();
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
        let bytes = axum::body::to_bytes(response.into_body(), 4096)
            .await
            .expect("read body");
        let body = String::from_utf8(bytes.to_vec()).expect("utf-8");
        let parsed: serde_json::Value = serde_json::from_str(&body).expect("json");
        assert_eq!(parsed["ok"], false);
        assert_eq!(parsed["error"]["code"], "unauthorized");
        assert!(
            parsed["error"]["message"]
                .as_str()
                .is_some_and(|m| !m.is_empty())
        );
    }

    // ── In-process app / router tests ──

    fn test_router() -> Router {
        let state = test_state("api-token-123", "webhook-secret");
        Router::new()
            .route("/repos/{repository}", get(public_repository))
            .route(
                "/repos/{repository}/tree/{reference}",
                get(public_code_root),
            )
            .route(
                "/repos/{repository}/tree/{reference}/{*path}",
                get(public_code_path),
            )
            .route(
                "/app/repositories/{repository}/tree/{reference}",
                get(app_code_root),
            )
            .route(
                "/app/repositories/{repository}/tree/{reference}/{*path}",
                get(app_code_path),
            )
            .route("/api/health", get(health))
            .route("/api/state", get(api_state))
            .route("/api/runs/{id}/promote/{environment}", post(promote))
            .route("/api/hooks/{repository}", post(webhook))
            .route("/api/repos", post(create_repository))
            .fallback(public_not_found)
            .with_state(state)
            .layer(DefaultBodyLimit::max(MAX_BODY))
            .layer(middleware::from_fn(security_headers))
    }

    async fn json_body(response: Response) -> serde_json::Value {
        let bytes = axum::body::to_bytes(response.into_body(), 64 * 1024)
            .await
            .expect("read body")
            .to_vec();
        serde_json::from_slice(&bytes).expect("json")
    }

    #[tokio::test]
    async fn app_unauthenticated_api_returns_401() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/state")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
        let body = json_body(response).await;
        assert_eq!(body["ok"], false);
        assert_eq!(body["error"]["code"], "unauthorized");
    }

    #[tokio::test]
    async fn app_health_is_public_200() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/health")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::OK);
        let body = json_body(response).await;
        assert_eq!(body["ok"], true);
    }

    #[tokio::test]
    async fn app_security_headers_are_present() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/health")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        let headers = response.headers();
        assert_eq!(
            headers
                .get(header::X_CONTENT_TYPE_OPTIONS)
                .and_then(|v| v.to_str().ok()),
            Some("nosniff")
        );
        assert_eq!(
            headers
                .get(header::X_FRAME_OPTIONS)
                .and_then(|v| v.to_str().ok()),
            Some("DENY")
        );
        let csp = headers
            .get(header::CONTENT_SECURITY_POLICY)
            .and_then(|v| v.to_str().ok())
            .expect("CSP header");
        assert!(
            csp.contains("script-src 'self'"),
            "CSP must include script-src 'self': {csp}"
        );
    }

    #[tokio::test]
    async fn app_webhook_rejects_bad_token() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/hooks/my-repo")
            .method("POST")
            .header("x-gitlab-token", "wrong-value")
            .header(header::CONTENT_TYPE, "application/json")
            .body(Body::from(r#"{"ref":"refs/heads/main","after":"abc123"}"#))
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }

    #[tokio::test]
    async fn app_promote_requires_authorization() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/runs/1/promote/staging")
            .method("POST")
            .header(header::CONTENT_TYPE, "application/json")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }

    #[tokio::test]
    async fn app_body_limit_rejects_oversized_request() {
        let state = test_state("api-token-123", "webhook-secret");
        let mut app = Router::new()
            .route("/api/repos", post(create_repository))
            .with_state(state)
            .layer(DefaultBodyLimit::max(8))
            .layer(middleware::from_fn(security_headers));
        let oversized = vec![b'a'; 64];
        let request = Request::builder()
            .uri("/api/repos")
            .method("POST")
            .header(header::CONTENT_TYPE, "application/json")
            .header(
                header::AUTHORIZATION,
                HeaderValue::from_static("Bearer api-token-123"),
            )
            .body(Body::from(oversized))
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);
    }

    #[tokio::test]
    async fn app_unknown_route_returns_404() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/nonexistent")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[tokio::test]
    async fn app_wrong_method_returns_405() {
        let mut app = test_router();
        // GET /api/repos is not defined, only POST
        let request = Request::builder()
            .uri("/api/repos")
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED);
    }

    #[tokio::test]
    async fn app_authorized_api_state_accepts_valid_token() {
        let mut app = test_router();
        let request = Request::builder()
            .uri("/api/state")
            .header(
                header::AUTHORIZATION,
                HeaderValue::from_static("Bearer api-token-123"),
            )
            .body(Body::empty())
            .expect("build request");
        let response = app.call(request).await.expect("call");
        // Should be OK even though DB queries might fail on empty tables;
        // the authorize gate passes so we either get 200 or a DB error,
        // but never 401.
        assert_ne!(response.status(), StatusCode::UNAUTHORIZED);
    }
    #[tokio::test]
    async fn blocking_git_limits_concurrency_and_preserves_api_errors() {
        use std::sync::atomic::{AtomicUsize, Ordering};

        let gate = BlockingGit::new(1);
        let active = Arc::new(AtomicUsize::new(0));
        let maximum = Arc::new(AtomicUsize::new(0));
        let operation = |active: Arc<AtomicUsize>, maximum: Arc<AtomicUsize>| {
            move || {
                let current = active.fetch_add(1, Ordering::SeqCst) + 1;
                maximum.fetch_max(current, Ordering::SeqCst);
                std::thread::sleep(Duration::from_millis(25));
                active.fetch_sub(1, Ordering::SeqCst);
                Ok(())
            }
        };
        let first = gate.run(operation(active.clone(), maximum.clone()));
        let second = gate.run(operation(active, maximum.clone()));
        let (first, second) = tokio::join!(first, second);
        assert!(first.is_ok());
        assert!(second.is_ok());
        assert_eq!(maximum.load(Ordering::SeqCst), 1);

        let error = gate
            .run(|| Err::<(), _>(ApiError::bad("specific git failure")))
            .await
            .expect_err("operation error");
        assert_eq!(error.status, StatusCode::BAD_REQUEST);
        assert_eq!(error.message, "specific git failure");
    }
}