Files
synapbus/schema/009_webhooks.sql
T
Algis DumbrisandClaude Opus 4.6 42775df65f feat: webhooks & Kubernetes Job runner for event-driven agents
- Webhook registration via MCP (register_webhook, list_webhooks, delete_webhook)
- HMAC-SHA256 payload signing, SSRF-safe HTTP client, loop detection (depth 5)
- 8-worker goroutine delivery pool with exponential backoff retry (1s/5s/30s)
- Dead letter queue with auto-purge, auto-disable after 50 consecutive failures
- Per-agent rate limiting (60 deliveries/min)
- K8s Job runner (register_k8s_handler, list_k8s_handlers, delete_k8s_handler)
- Auto-detect in-cluster via InClusterConfig, NoopRunner fallback
- REST API for webhook deliveries, dead letters, K8s job runs and logs
- Web UI: webhook management, K8s handler pages, dead letters view
- MultiDispatcher fan-out pattern for webhook + K8s event dispatch
- SQLite migration 009: webhooks, webhook_deliveries, k8s_handlers, k8s_job_runs
- 51 tests across 9 test packages, all passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 18:14:04 +02:00

81 lines
3.6 KiB
SQL

-- Webhook registrations
CREATE TABLE IF NOT EXISTS webhooks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_name TEXT NOT NULL REFERENCES agents(name) ON DELETE CASCADE,
url TEXT NOT NULL,
events TEXT NOT NULL DEFAULT '[]',
secret_hash TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'disabled')),
consecutive_failures INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(agent_name, url)
);
CREATE INDEX IF NOT EXISTS idx_webhooks_agent_name ON webhooks(agent_name);
CREATE INDEX IF NOT EXISTS idx_webhooks_agent_status ON webhooks(agent_name, status);
-- Webhook delivery tracking
CREATE TABLE IF NOT EXISTS webhook_deliveries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
webhook_id INTEGER NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
agent_name TEXT NOT NULL,
event TEXT NOT NULL,
message_id INTEGER NOT NULL,
payload TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'delivered', 'retrying', 'dead_lettered')),
http_status INTEGER,
attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
last_error TEXT,
next_retry_at DATETIME,
depth INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
delivered_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_deliveries_webhook_id ON webhook_deliveries(webhook_id);
CREATE INDEX IF NOT EXISTS idx_deliveries_status ON webhook_deliveries(status);
CREATE INDEX IF NOT EXISTS idx_deliveries_agent_status ON webhook_deliveries(agent_name, status);
CREATE INDEX IF NOT EXISTS idx_deliveries_next_retry ON webhook_deliveries(next_retry_at) WHERE status = 'retrying';
CREATE INDEX IF NOT EXISTS idx_deliveries_created_at ON webhook_deliveries(created_at);
-- K8s handler registrations
CREATE TABLE IF NOT EXISTS k8s_handlers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_name TEXT NOT NULL REFERENCES agents(name) ON DELETE CASCADE,
image TEXT NOT NULL,
events TEXT NOT NULL DEFAULT '[]',
namespace TEXT NOT NULL,
resources_memory TEXT NOT NULL DEFAULT '256Mi',
resources_cpu TEXT NOT NULL DEFAULT '100m',
env TEXT NOT NULL DEFAULT '{}',
timeout_seconds INTEGER NOT NULL DEFAULT 600 CHECK(timeout_seconds >= 60 AND timeout_seconds <= 3600),
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'disabled')),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE(agent_name, image, namespace)
);
CREATE INDEX IF NOT EXISTS idx_k8s_handlers_agent ON k8s_handlers(agent_name);
CREATE INDEX IF NOT EXISTS idx_k8s_handlers_agent_status ON k8s_handlers(agent_name, status);
-- K8s job run tracking
CREATE TABLE IF NOT EXISTS k8s_job_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
handler_id INTEGER NOT NULL REFERENCES k8s_handlers(id) ON DELETE CASCADE,
agent_name TEXT NOT NULL,
message_id INTEGER NOT NULL,
job_name TEXT NOT NULL,
namespace TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'running', 'succeeded', 'failed')),
failure_reason TEXT,
started_at DATETIME,
completed_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_job_runs_handler ON k8s_job_runs(handler_id);
CREATE INDEX IF NOT EXISTS idx_job_runs_agent_status ON k8s_job_runs(agent_name, status);
CREATE INDEX IF NOT EXISTS idx_job_runs_job_name ON k8s_job_runs(job_name);