harness-codex 0.1.1

Codex harness integration for the SteadyMux local continuity router.
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later

#![forbid(unsafe_code)]

use harness_codex::{AuthCommand, ConfigPreview, UserConfigTarget, restore_from_receipt};
use serde::Serialize;
use std::env;
use std::path::{Component, Path, PathBuf};
use std::process::ExitCode;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Action {
    Preview,
    Apply,
    Restore,
}

struct Arguments {
    action: Action,
    config: PathBuf,
    port: Option<u16>,
    auth_command: Option<PathBuf>,
    auth_args: Vec<String>,
    confirmation: Option<String>,
    preview_digest: Option<String>,
}

#[derive(Serialize)]
struct PreviewOutput<'a> {
    status: &'static str,
    human_diff: &'a str,
    machine_plan: &'a harness_codex::MachinePlan,
    apply_required: bool,
    auth_command: &'static str,
}

#[derive(Serialize)]
struct StateOutput<'a> {
    status: &'static str,
    target: &'a Path,
}

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(message) => {
            eprintln!("codex-config-plan: {message}");
            ExitCode::from(2)
        }
    }
}

fn run() -> Result<(), &'static str> {
    let arguments = parse_arguments()?;
    let target = validate_target(&arguments.config)?;
    let target_path = target.path().to_path_buf();

    match arguments.action {
        Action::Restore => {
            if arguments.confirmation.as_deref() != Some("RESTORE_USER_CONFIG") {
                return Err("restore requires --confirm RESTORE_USER_CONFIG");
            }
            restore_from_receipt(target)
                .map_err(|_| "restore refused; inspect recovery artifacts")?;
            print_json(&StateOutput {
                status: "restored",
                target: &target_path,
            })
        }
        Action::Preview | Action::Apply => {
            let port = arguments.port.ok_or("preview/apply requires --port")?;
            let command = arguments
                .auth_command
                .ok_or("preview/apply requires --auth-command")?;
            let auth_command = AuthCommand::new(command, arguments.auth_args)
                .map_err(|_| "authentication command was refused")?;
            let preview = ConfigPreview::build(target, port, &auth_command)
                .map_err(|_| "configuration plan was refused")?;
            print_json(&PreviewOutput {
                status: "preview",
                human_diff: &preview.human_diff,
                machine_plan: &preview.machine_plan,
                apply_required: true,
                auth_command: "[REDACTED]",
            })?;
            if arguments.action == Action::Preview {
                return Ok(());
            }
            if arguments.confirmation.as_deref() != Some("APPLY_USER_CONFIG") {
                return Err("apply requires --confirm APPLY_USER_CONFIG");
            }
            let preview_digest = arguments
                .preview_digest
                .as_deref()
                .ok_or("apply requires --preview-digest")?;
            preview
                .confirm_digest(preview_digest)
                .map_err(|_| "configuration preview digest was refused")?
                .apply()
                .map_err(|_| "configuration apply was refused")?;
            print_json(&StateOutput {
                status: "applied",
                target: &target_path,
            })
        }
    }
}

fn print_json(value: &impl Serialize) -> Result<(), &'static str> {
    let encoded = serde_json::to_string(value).map_err(|_| "output encoding failed")?;
    println!("{encoded}");
    Ok(())
}

fn parse_arguments() -> Result<Arguments, &'static str> {
    let mut action = Action::Preview;
    let mut config = None;
    let mut port = None;
    let mut auth_command = None;
    let mut auth_args = Vec::new();
    let mut confirmation = None;
    let mut preview_digest = None;
    let mut arguments = env::args().skip(1);

    while let Some(argument) = arguments.next() {
        match argument.as_str() {
            "--config" => config = Some(PathBuf::from(next_value(&mut arguments)?)),
            "--port" => {
                port = Some(
                    next_value(&mut arguments)?
                        .parse()
                        .map_err(|_| "--port must be a valid u16")?,
                );
            }
            "--auth-command" => auth_command = Some(PathBuf::from(next_value(&mut arguments)?)),
            "--auth-arg" => auth_args.push(next_value(&mut arguments)?),
            "--confirm" => confirmation = Some(next_value(&mut arguments)?),
            "--preview-digest" => preview_digest = Some(next_value(&mut arguments)?),
            "--apply" if action == Action::Preview => action = Action::Apply,
            "--restore" if action == Action::Preview => action = Action::Restore,
            "--apply" | "--restore" => return Err("choose only one of --apply or --restore"),
            "--help" | "-h" => {
                println!(
                    "codex-config-plan --config ABSOLUTE_CONFIG_TOML [--port PORT --auth-command ABSOLUTE_EXECUTABLE [--auth-arg ARG]...] [--apply --preview-digest SHA256 --confirm APPLY_USER_CONFIG | --restore --confirm RESTORE_USER_CONFIG]"
                );
                std::process::exit(0);
            }
            _ => return Err("unknown argument; use --help"),
        }
    }

    Ok(Arguments {
        action,
        config: config.ok_or("--config is required")?,
        port,
        auth_command,
        auth_args,
        confirmation,
        preview_digest,
    })
}

fn next_value(arguments: &mut impl Iterator<Item = String>) -> Result<String, &'static str> {
    arguments.next().ok_or("argument value is missing")
}

fn validate_target(path: &Path) -> Result<UserConfigTarget, &'static str> {
    if !path.is_absolute() || path.components().any(|part| part == Component::ParentDir) {
        return Err("--config must be an absolute normalized path");
    }
    if path.file_name().and_then(|name| name.to_str()) != Some("config.toml") {
        return Err("configuration file must be named config.toml");
    }
    let current = env::current_dir().map_err(|_| "current directory is unavailable")?;
    let lexical_project_target = current.join(".codex/config.toml");
    if path == lexical_project_target
        || (path.starts_with(&current) && has_codex_config_suffix(path))
    {
        return Err("project .codex/config.toml targets are prohibited");
    }
    let config_root = path.parent().ok_or("configuration path has no parent")?;
    let target = UserConfigTarget::under_config_root(config_root)
        .map_err(|_| "configuration root must be an existing non-symlink directory")?;
    let approved_root = env::var_os("CODEX_HOME")
        .map(PathBuf::from)
        .or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".codex")))
        .ok_or("CODEX_HOME or HOME is required to establish the user config root")?;
    let approved_target = UserConfigTarget::under_config_root(&approved_root)
        .map_err(|_| "approved user configuration root must exist and must not be a symlink")?;
    if target.path() != approved_target.path() {
        return Err("configuration target must be contained by the approved user config root");
    }
    if fs_target_is_symlink(target.path()) {
        return Err("configuration file must not be a symbolic link");
    }
    let canonical_current =
        std::fs::canonicalize(&current).map_err(|_| "current directory is unavailable")?;
    let project_target = canonical_current.join(".codex/config.toml");
    if target.path() == project_target
        || (target.path().starts_with(&canonical_current) && has_codex_config_suffix(target.path()))
    {
        return Err("project .codex/config.toml targets are prohibited");
    }
    let mut ancestor = target.path().parent().and_then(Path::parent);
    while let Some(directory) = ancestor {
        if target.path() == directory.join(".codex/config.toml")
            && (directory.join(".git").exists()
                || directory.join("Cargo.toml").exists()
                || directory.join("package.json").exists())
        {
            return Err("project .codex/config.toml targets are prohibited");
        }
        ancestor = directory.parent();
    }
    Ok(target)
}

fn fs_target_is_symlink(path: &Path) -> bool {
    std::fs::symlink_metadata(path).is_ok_and(|metadata| metadata.file_type().is_symlink())
}

fn has_codex_config_suffix(path: &Path) -> bool {
    path.parent()
        .and_then(Path::file_name)
        .and_then(|name| name.to_str())
        == Some(".codex")
}