Scaffold dd0c/run: Rust agent (classifier, executor, audit) + TypeScript SaaS

- Rust agent: clap CLI, command classifier (read-only/modifying/destructive), executor with approval gates, audit log entries
- Classifier: pattern-based safety classification for shell, AWS, kubectl, terraform/tofu commands
- 6 Rust tests: read-only, destructive, modifying, empty, terraform apply, tofu destroy
- SaaS backend: Fastify server, runbook CRUD API, approval API, Slack interactive handler
- Slack integration: signature verification, block_actions for approve/reject buttons
- PostgreSQL schema with RLS: runbooks, executions, audit_entries (append-only), agents
- Dual Dockerfiles: Rust multi-stage (agent), Node multi-stage (SaaS)
- Gitea Actions CI: Rust test+clippy, Node typecheck+test
- Fly.io config for SaaS
This commit is contained in:
2026-03-01 03:03:29 +00:00
parent 6f692fc5ef
commit 57e7083986
18 changed files with 1046 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
use clap::{Parser, Subcommand};
use tracing::info;
mod parser;
mod classifier;
mod executor;
mod audit;
#[derive(Parser)]
#[command(name = "dd0c-run", version, about = "Runbook automation agent")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Execute a runbook
Run {
/// Path to runbook file (YAML/Markdown)
#[arg(short, long)]
runbook: String,
/// dd0c SaaS endpoint
#[arg(long, default_value = "https://api.dd0c.dev")]
endpoint: String,
/// API key
#[arg(long, env = "DD0C_API_KEY")]
api_key: String,
/// Dry run (classify only, don't execute)
#[arg(long)]
dry_run: bool,
},
/// Classify a single command
Classify {
/// Command to classify
command: String,
},
/// Verify agent binary signature
Verify {
/// Path to signature file
#[arg(short, long)]
sig: String,
/// Path to public key
#[arg(short, long)]
pubkey: String,
},
/// Print version
Version,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "dd0c_run=info".into()),
)
.json()
.init();
let cli = Cli::parse();
match cli.command {
Commands::Run { runbook, endpoint, api_key, dry_run } => {
info!(runbook = %runbook, dry_run, "Starting runbook execution");
// TODO: Parse runbook → classify steps → execute with approval gates
}
Commands::Classify { command } => {
let result = classifier::classify(&command);
println!("{}", serde_json::to_string_pretty(&result)?);
}
Commands::Verify { sig, pubkey } => {
// TODO: Ed25519 signature verification
println!("Signature verification not yet implemented");
}
Commands::Version => {
println!("dd0c/run agent v{}", env!("CARGO_PKG_VERSION"));
}
}
Ok(())
}