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:
94
products/06-runbook-automation/saas/migrations/001_init.sql
Normal file
94
products/06-runbook-automation/saas/migrations/001_init.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
-- dd0c/run V1 schema
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Tenants
|
||||
CREATE TABLE tenants (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free', 'pro')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- Runbooks
|
||||
CREATE TABLE runbooks (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
yaml_content TEXT NOT NULL,
|
||||
step_count INT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived')),
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_runbooks_tenant ON runbooks(tenant_id, status);
|
||||
|
||||
-- Executions
|
||||
CREATE TABLE executions (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
runbook_id UUID NOT NULL REFERENCES runbooks(id) ON DELETE CASCADE,
|
||||
triggered_by TEXT NOT NULL,
|
||||
trigger_source TEXT NOT NULL DEFAULT 'api' CHECK (trigger_source IN ('api', 'slack', 'schedule', 'webhook')),
|
||||
dry_run BOOLEAN NOT NULL DEFAULT false,
|
||||
variables JSONB NOT NULL DEFAULT '{}',
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'awaiting_approval', 'completed', 'failed', 'aborted')),
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX idx_executions_tenant ON executions(tenant_id, created_at DESC);
|
||||
CREATE INDEX idx_executions_runbook ON executions(runbook_id);
|
||||
|
||||
-- Audit entries (append-only, immutable — BMad must-have)
|
||||
CREATE TABLE audit_entries (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
execution_id UUID NOT NULL REFERENCES executions(id) ON DELETE CASCADE,
|
||||
step_index INT NOT NULL,
|
||||
command TEXT NOT NULL,
|
||||
safety_level TEXT NOT NULL CHECK (safety_level IN ('read_only', 'modifying', 'destructive')),
|
||||
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'awaiting_approval', 'approved', 'rejected', 'executing', 'completed', 'failed', 'timed_out')),
|
||||
approved_by TEXT,
|
||||
approval_method TEXT CHECK (approval_method IN ('slack_button', 'api', 'auto')),
|
||||
exit_code INT,
|
||||
stdout_hash TEXT,
|
||||
stderr_hash TEXT,
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
completed_at TIMESTAMPTZ,
|
||||
duration_ms INT
|
||||
);
|
||||
CREATE INDEX idx_audit_execution ON audit_entries(execution_id, step_index);
|
||||
CREATE INDEX idx_audit_tenant ON audit_entries(tenant_id, started_at DESC);
|
||||
|
||||
-- Agent registrations
|
||||
CREATE TABLE agents (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
api_key_hash TEXT NOT NULL,
|
||||
api_key_prefix TEXT NOT NULL,
|
||||
last_heartbeat_at TIMESTAMPTZ,
|
||||
version TEXT,
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE runbooks ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE executions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE audit_entries ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE agents ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_iso_runbooks ON runbooks
|
||||
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
||||
CREATE POLICY tenant_iso_executions ON executions
|
||||
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
||||
CREATE POLICY tenant_iso_audit ON audit_entries
|
||||
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
||||
CREATE POLICY tenant_iso_agents ON agents
|
||||
USING (tenant_id::text = current_setting('app.tenant_id', true));
|
||||
Reference in New Issue
Block a user