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
2026-03-01 03:03:29 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
use anyhow::{Context, Result};
|
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
2026-03-01 03:03:29 +00:00
|
|
|
|
|
|
|
|
/// Parsed runbook step.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct RunbookStep {
|
|
|
|
|
pub index: usize,
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub command: String,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(default = "default_timeout")]
|
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
2026-03-01 03:03:29 +00:00
|
|
|
pub timeout_seconds: u64,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(default)]
|
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
2026-03-01 03:03:29 +00:00
|
|
|
pub on_failure: FailureAction,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
pub condition: Option<String>,
|
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
2026-03-01 03:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
fn default_timeout() -> u64 { 300 }
|
|
|
|
|
|
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
2026-03-01 03:03:29 +00:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(tag = "action", rename_all = "snake_case")]
|
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
2026-03-01 03:03:29 +00:00
|
|
|
pub enum FailureAction {
|
|
|
|
|
Abort,
|
|
|
|
|
Continue,
|
|
|
|
|
Retry { max_attempts: u32 },
|
|
|
|
|
}
|
|
|
|
|
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
impl Default for FailureAction {
|
|
|
|
|
fn default() -> Self { FailureAction::Abort }
|
|
|
|
|
}
|
|
|
|
|
|
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
2026-03-01 03:03:29 +00:00
|
|
|
/// Parsed runbook.
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct Runbook {
|
|
|
|
|
pub name: String,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(default)]
|
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
2026-03-01 03:03:29 +00:00
|
|
|
pub description: String,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(default = "default_version")]
|
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
2026-03-01 03:03:29 +00:00
|
|
|
pub version: String,
|
|
|
|
|
pub steps: Vec<RunbookStep>,
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub variables: std::collections::HashMap<String, VariableSpec>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_version() -> String { "0.1.0".into() }
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
|
pub struct VariableSpec {
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub default: Option<String>,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
pub required: bool,
|
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
2026-03-01 03:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a YAML runbook into structured steps.
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
///
|
|
|
|
|
/// Example YAML:
|
|
|
|
|
/// ```yaml
|
|
|
|
|
/// name: restart-service
|
|
|
|
|
/// description: Restart a stuck ECS service
|
|
|
|
|
/// variables:
|
|
|
|
|
/// cluster:
|
|
|
|
|
/// description: ECS cluster name
|
|
|
|
|
/// required: true
|
|
|
|
|
/// service:
|
|
|
|
|
/// description: ECS service name
|
|
|
|
|
/// required: true
|
|
|
|
|
/// steps:
|
|
|
|
|
/// - description: Check current task count
|
|
|
|
|
/// command: "aws ecs describe-services --cluster {{cluster}} --services {{service}}"
|
|
|
|
|
/// timeout_seconds: 30
|
|
|
|
|
/// - description: Force new deployment
|
|
|
|
|
/// command: "aws ecs update-service --cluster {{cluster}} --service {{service}} --force-new-deployment"
|
|
|
|
|
/// timeout_seconds: 60
|
|
|
|
|
/// on_failure:
|
|
|
|
|
/// action: abort
|
|
|
|
|
/// - description: Wait for stable
|
|
|
|
|
/// command: "aws ecs wait services-stable --cluster {{cluster}} --services {{service}}"
|
|
|
|
|
/// timeout_seconds: 300
|
|
|
|
|
/// on_failure:
|
|
|
|
|
/// action: retry
|
|
|
|
|
/// max_attempts: 3
|
|
|
|
|
/// ```
|
|
|
|
|
pub fn parse_yaml(content: &str) -> Result<Runbook> {
|
|
|
|
|
let mut runbook: Runbook = serde_yaml::from_str(content)
|
|
|
|
|
.context("Failed to parse runbook YAML")?;
|
|
|
|
|
|
|
|
|
|
// Assign step indices
|
|
|
|
|
for (i, step) in runbook.steps.iter_mut().enumerate() {
|
|
|
|
|
step.index = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate: at least one step
|
|
|
|
|
if runbook.steps.is_empty() {
|
|
|
|
|
anyhow::bail!("Runbook must have at least one step");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(runbook)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Substitute variables in a command string.
|
|
|
|
|
/// Variables use `{{name}}` syntax.
|
|
|
|
|
pub fn substitute_variables(
|
|
|
|
|
command: &str,
|
|
|
|
|
variables: &std::collections::HashMap<String, String>,
|
|
|
|
|
) -> Result<String> {
|
|
|
|
|
let mut result = command.to_string();
|
|
|
|
|
for (key, value) in variables {
|
|
|
|
|
result = result.replace(&format!("{{{{{}}}}}", key), value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for unresolved variables
|
|
|
|
|
if result.contains("{{") {
|
|
|
|
|
let unresolved: Vec<&str> = result
|
|
|
|
|
.match_indices("{{")
|
|
|
|
|
.filter_map(|(start, _)| {
|
|
|
|
|
result[start..].find("}}").map(|end| &result[start..start + end + 2])
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
anyhow::bail!("Unresolved variables: {:?}", unresolved);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
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
2026-03-01 03:03:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
const SAMPLE_YAML: &str = r#"
|
|
|
|
|
name: restart-service
|
|
|
|
|
description: Restart a stuck ECS service
|
|
|
|
|
variables:
|
|
|
|
|
cluster:
|
|
|
|
|
description: ECS cluster name
|
|
|
|
|
required: true
|
|
|
|
|
service:
|
|
|
|
|
description: ECS service name
|
|
|
|
|
required: true
|
|
|
|
|
steps:
|
|
|
|
|
- description: Check current task count
|
|
|
|
|
command: "aws ecs describe-services --cluster {{cluster}} --services {{service}}"
|
|
|
|
|
timeout_seconds: 30
|
|
|
|
|
- description: Force new deployment
|
|
|
|
|
command: "aws ecs update-service --cluster {{cluster}} --service {{service}} --force-new-deployment"
|
|
|
|
|
timeout_seconds: 60
|
|
|
|
|
on_failure:
|
|
|
|
|
action: abort
|
|
|
|
|
- description: Wait for stable
|
|
|
|
|
command: "aws ecs wait services-stable --cluster {{cluster}} --services {{service}}"
|
|
|
|
|
timeout_seconds: 300
|
|
|
|
|
on_failure:
|
|
|
|
|
action: retry
|
|
|
|
|
max_attempts: 3
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_parse_valid_yaml() {
|
|
|
|
|
let runbook = parse_yaml(SAMPLE_YAML).unwrap();
|
|
|
|
|
assert_eq!(runbook.name, "restart-service");
|
|
|
|
|
assert_eq!(runbook.steps.len(), 3);
|
|
|
|
|
assert_eq!(runbook.steps[0].index, 0);
|
|
|
|
|
assert_eq!(runbook.steps[2].timeout_seconds, 300);
|
|
|
|
|
assert_eq!(runbook.variables.len(), 2);
|
|
|
|
|
assert!(runbook.variables.get("cluster").unwrap().required);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_parse_empty_steps_fails() {
|
|
|
|
|
let yaml = "name: empty\nsteps: []";
|
|
|
|
|
assert!(parse_yaml(yaml).is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_parse_invalid_yaml_fails() {
|
|
|
|
|
assert!(parse_yaml("not: [valid: yaml: {{").is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_substitute_variables() {
|
|
|
|
|
let mut vars = HashMap::new();
|
|
|
|
|
vars.insert("cluster".into(), "prod".into());
|
|
|
|
|
vars.insert("service".into(), "api".into());
|
|
|
|
|
|
|
|
|
|
let result = substitute_variables(
|
|
|
|
|
"aws ecs describe-services --cluster {{cluster}} --services {{service}}",
|
|
|
|
|
&vars,
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(result, "aws ecs describe-services --cluster prod --services api");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_unresolved_variable_fails() {
|
|
|
|
|
let vars = HashMap::new();
|
|
|
|
|
let result = substitute_variables("echo {{missing}}", &vars);
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_default_failure_action_is_abort() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
name: simple
|
|
|
|
|
steps:
|
|
|
|
|
- description: test
|
|
|
|
|
command: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let runbook = parse_yaml(yaml).unwrap();
|
|
|
|
|
assert!(matches!(runbook.steps[0].on_failure, FailureAction::Abort));
|
|
|
|
|
}
|
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
2026-03-01 03:03:29 +00:00
|
|
|
|
|
|
|
|
#[test]
|
Add notification dispatchers (P3 Slack/Email/Webhook, P5 Slack), full YAML parser for P6
- P3 alert: NotificationDispatcher with Slack Block Kit, Resend email, generic webhook; severity-gated dispatch
- P5 cost: CostSlackNotifier with anomaly Block Kit (score, deviation, snooze/expected buttons)
- P6 run: Full YAML runbook parser with serde_yaml, variable substitution ({{var}}), failure actions, 7 tests
- P6 parser: validates non-empty steps, default timeout (300s), default abort on failure
2026-03-01 03:13:06 +00:00
|
|
|
fn test_default_timeout() {
|
|
|
|
|
let yaml = r#"
|
|
|
|
|
name: simple
|
|
|
|
|
steps:
|
|
|
|
|
- description: test
|
|
|
|
|
command: echo hello
|
|
|
|
|
"#;
|
|
|
|
|
let runbook = parse_yaml(yaml).unwrap();
|
|
|
|
|
assert_eq!(runbook.steps[0].timeout_seconds, 300);
|
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
2026-03-01 03:03:29 +00:00
|
|
|
}
|
|
|
|
|
}
|