- 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
59 lines
1.7 KiB
Rust
59 lines
1.7 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use chrono::{DateTime, Utc};
|
|
use uuid::Uuid;
|
|
|
|
/// Immutable, append-only audit log entry.
|
|
/// Every command execution gets logged — no exceptions (BMad must-have).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AuditEntry {
|
|
pub id: String,
|
|
pub tenant_id: String,
|
|
pub runbook_id: String,
|
|
pub step_index: usize,
|
|
pub command: String,
|
|
pub safety_level: String,
|
|
pub approved_by: Option<String>,
|
|
pub approval_method: Option<String>, // "slack_button", "api", "auto" (read-only only)
|
|
pub exit_code: Option<i32>,
|
|
pub stdout_hash: Option<String>, // SHA-256 of stdout (don't store raw output)
|
|
pub stderr_hash: Option<String>,
|
|
pub started_at: DateTime<Utc>,
|
|
pub completed_at: Option<DateTime<Utc>>,
|
|
pub duration_ms: Option<u64>,
|
|
pub status: AuditStatus,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum AuditStatus {
|
|
Pending,
|
|
AwaitingApproval,
|
|
Approved,
|
|
Rejected,
|
|
Executing,
|
|
Completed,
|
|
Failed,
|
|
TimedOut,
|
|
}
|
|
|
|
impl AuditEntry {
|
|
pub fn new(tenant_id: &str, runbook_id: &str, step_index: usize, command: &str, safety_level: &str) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4().to_string(),
|
|
tenant_id: tenant_id.to_string(),
|
|
runbook_id: runbook_id.to_string(),
|
|
step_index,
|
|
command: command.to_string(),
|
|
safety_level: safety_level.to_string(),
|
|
approved_by: None,
|
|
approval_method: None,
|
|
exit_code: None,
|
|
stdout_hash: None,
|
|
stderr_hash: None,
|
|
started_at: Utc::now(),
|
|
completed_at: None,
|
|
duration_ms: None,
|
|
status: AuditStatus::Pending,
|
|
}
|
|
}
|
|
}
|