-- ========================================== -- Chat Switchboard — 022 Extension Connections -- ========================================== -- v0.38.1: Scoped credential management for extensions. -- Same scope/resolution pattern as provider_configs. -- ========================================== CREATE TABLE IF NOT EXISTS ext_connections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), type TEXT NOT NULL, package_id TEXT NOT NULL, scope VARCHAR(10) NOT NULL CHECK (scope IN ('global', 'team', 'personal')), owner_id TEXT NOT NULL DEFAULT '', name TEXT NOT NULL, config JSONB NOT NULL DEFAULT '{}'::jsonb, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(type, scope, owner_id, name) ); CREATE INDEX IF NOT EXISTS idx_ext_connections_type ON ext_connections(type); CREATE INDEX IF NOT EXISTS idx_ext_connections_scope ON ext_connections(scope); CREATE INDEX IF NOT EXISTS idx_ext_connections_owner ON ext_connections(owner_id) WHERE owner_id != ''; CREATE INDEX IF NOT EXISTS idx_ext_connections_active ON ext_connections(is_active) WHERE is_active = true; DROP TRIGGER IF EXISTS ext_connections_updated_at ON ext_connections; CREATE TRIGGER ext_connections_updated_at BEFORE UPDATE ON ext_connections FOR EACH ROW EXECUTE FUNCTION update_updated_at(); COMMENT ON TABLE ext_connections IS 'v0.38.1: Extension connection credentials (scoped, encrypted secrets in config JSON)'; COMMENT ON COLUMN ext_connections.type IS 'Connection type identifier, shared across packages (e.g. "gitea", "github")'; COMMENT ON COLUMN ext_connections.package_id IS 'Declaring package ID (UI attribution only, not access control)'; COMMENT ON COLUMN ext_connections.scope IS 'global=admin-managed, team=team-scoped, personal=user-owned'; COMMENT ON COLUMN ext_connections.owner_id IS 'Empty for global; team_id for team scope; user_id for personal scope'; COMMENT ON COLUMN ext_connections.config IS 'JSON config blob; secret-type fields encrypted at rest by handler layer';