acativa 0.3.0

Multi-Agent Collaboration Strategy Compiler — generic runtime, state machine and durable strategy store
Documentation
use std::ffi::OsString;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::ExitCode;

use acativa::SchemaId;
use acativa::runtime::protocol::serve;
use acativa::store::{StoreDialect, StrategyStore, schema_sql};

const HELP: &str = "\
Acativa private runtime

Usage:
  acativa schema
  acativa serve --database PATH --workflow-schema ID --projection-schema ID --ephemeral-prefix PREFIX
  acativa --help

schema exports the canonical empty-schema SQL; the host owns migration.
serve opens an existing migrated database and uses private NDJSON stdin/stdout.
All serve options are required. No effects are executed by this process.
";

enum Command {
    Help,
    Schema,
    Serve {
        database: PathBuf,
        dialect: StoreDialect,
    },
}

fn text(value: OsString) -> Result<String, ()> {
    value.into_string().map_err(|_| ())
}

fn parse(mut args: impl Iterator<Item = OsString>) -> Result<Command, ()> {
    let command = text(args.next().ok_or(())?)?;
    match command.as_str() {
        "--help" | "-h" | "help" if args.next().is_none() => Ok(Command::Help),
        "schema" if args.next().is_none() => Ok(Command::Schema),
        "serve" => {
            let mut database = None;
            let mut workflow_schema = None;
            let mut projection_schema = None;
            let mut ephemeral_prefix = None;
            while let Some(option) = args.next() {
                let option = text(option)?;
                let value = args.next().filter(|value| !value.is_empty()).ok_or(())?;
                match option.as_str() {
                    "--database" if database.is_none() => database = Some(PathBuf::from(value)),
                    "--workflow-schema" if workflow_schema.is_none() => {
                        workflow_schema = Some(SchemaId::new(text(value)?));
                    }
                    "--projection-schema" if projection_schema.is_none() => {
                        projection_schema = Some(SchemaId::new(text(value)?));
                    }
                    "--ephemeral-prefix" if ephemeral_prefix.is_none() => {
                        ephemeral_prefix = Some(text(value)?);
                    }
                    _ => return Err(()),
                }
            }
            Ok(Command::Serve {
                database: database.ok_or(())?,
                dialect: StoreDialect {
                    state_root: String::new(),
                    workflow_schema: workflow_schema.ok_or(())?,
                    projection_schema: projection_schema.ok_or(())?,
                    ephemeral_definition_prefix: Some(ephemeral_prefix.ok_or(())?),
                },
            })
        }
        _ => Err(()),
    }
}

fn run(command: Command) -> Result<(), &'static str> {
    match command {
        Command::Help => io::stdout()
            .lock()
            .write_all(HELP.as_bytes())
            .map_err(|_| "acativa: output unavailable\n"),
        Command::Schema => {
            let sql = schema_sql().map_err(|_| "acativa: runtime refused\n")?;
            io::stdout()
                .lock()
                .write_all(sql.as_bytes())
                .map_err(|_| "acativa: output unavailable\n")
        }
        Command::Serve { database, dialect } => {
            let store = StrategyStore::open_existing_database(&database, &dialect)
                .map_err(|_| "acativa: runtime refused\n")?;
            store.suppress_transition_intents();
            serve(io::stdin().lock(), io::stdout().lock(), store)
                .map_err(|_| "acativa: pipe unavailable\n")
        }
    }
}

fn main() -> ExitCode {
    let result = parse(std::env::args_os().skip(1))
        .map_err(|_| "acativa: invalid usage; use --help\n")
        .and_then(run);
    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(message) => {
            let _ = io::stderr().lock().write_all(message.as_bytes());
            ExitCode::FAILURE
        }
    }
}