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,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));