Files
dd0c/products/06-runbook-automation/agent/src/audit.rs
Max Mayfield 57e7083986 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

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,
}
}
}