-
Notifications
You must be signed in to change notification settings - Fork 369
feat(integrations): add catalog-backed connections #2766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| """consolidated integrations catalog | ||
|
|
||
| Revision ID: b7e1c9f2a3d4 | ||
| Revises: a3d7c9e8b4f2 | ||
| Create Date: 2026-05-25 21:00:00.000000 | ||
|
|
||
| Adds the consolidated Integrations catalog model: an integration catalog table. | ||
|
|
||
| Additive only -- no existing tables are dropped. Secret, OAuthIntegration, | ||
| WorkspaceOAuthProvider, and MCPIntegration storage continue to drive credential | ||
| and MCP behavior. | ||
| """ | ||
|
|
||
| from collections.abc import Sequence | ||
|
|
||
| import sqlalchemy as sa | ||
| from sqlalchemy.dialects import postgresql | ||
|
|
||
| from alembic import op | ||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision: str = "b7e1c9f2a3d4" | ||
| down_revision: str | None = "a3d7c9e8b4f2" | ||
| branch_labels: str | Sequence[str] | None = None | ||
| depends_on: str | Sequence[str] | None = None | ||
|
|
||
|
|
||
| INTEGRATION_SOURCE_VALUES = ("platform", "workspace") | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| # --- enums --------------------------------------------------------- | ||
| sa.Enum(*INTEGRATION_SOURCE_VALUES, name="integrationsource").create(op.get_bind()) | ||
|
|
||
| # --- integration --------------------------------------------------- | ||
| op.create_table( | ||
| "integration", | ||
| sa.Column("id", sa.UUID(), nullable=False), | ||
| sa.Column("workspace_id", sa.UUID(), nullable=True), | ||
| sa.Column("namespace", sa.String(), nullable=False), | ||
| sa.Column("display_name", sa.String(), nullable=False), | ||
| sa.Column("description", sa.String(), nullable=True), | ||
| sa.Column("icon_url", sa.String(), nullable=True), | ||
| sa.Column( | ||
| "source", | ||
| postgresql.ENUM( | ||
| *INTEGRATION_SOURCE_VALUES, | ||
| name="integrationsource", | ||
| create_type=False, | ||
| ), | ||
| nullable=False, | ||
| ), | ||
| sa.Column( | ||
| "created_at", | ||
| sa.TIMESTAMP(timezone=True), | ||
| server_default=sa.text("now()"), | ||
| nullable=False, | ||
| ), | ||
| sa.Column( | ||
| "updated_at", | ||
| sa.TIMESTAMP(timezone=True), | ||
| server_default=sa.text("now()"), | ||
| nullable=False, | ||
| ), | ||
| sa.ForeignKeyConstraint( | ||
| ["workspace_id"], | ||
| ["workspace.id"], | ||
| name=op.f("fk_integration_workspace_id_workspace"), | ||
| ondelete="CASCADE", | ||
| ), | ||
| sa.PrimaryKeyConstraint("id", name=op.f("pk_integration")), | ||
| sa.UniqueConstraint( | ||
| "workspace_id", | ||
| "namespace", | ||
| name="uq_integration_workspace_namespace", | ||
| ), | ||
| ) | ||
| op.create_index(op.f("ix_integration_id"), "integration", ["id"], unique=True) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Redundant unique index created on primary key column Prompt for AI agents |
||
| op.create_index("ix_integration_namespace", "integration", ["namespace"]) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_index("ix_integration_namespace", table_name="integration") | ||
| op.drop_index(op.f("ix_integration_id"), table_name="integration") | ||
| op.drop_table("integration") | ||
|
|
||
| sa.Enum(name="integrationsource").drop(op.get_bind()) | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||||||
| """backfill secret namespaces into integrations | ||||||||||
|
|
||||||||||
| Revision ID: c8f2d1e4a5b6 | ||||||||||
| Revises: b7e1c9f2a3d4 | ||||||||||
| Create Date: 2026-05-25 23:00:00.000000 | ||||||||||
|
|
||||||||||
| For secret names that don't already have an Integration row, a workspace-scoped | ||||||||||
| ``Integration`` is created on demand so the Integrations catalog can project | ||||||||||
| legacy static credentials without moving credential storage. | ||||||||||
|
|
||||||||||
| Additive only -- the legacy ``secret`` table remains the source of truth for | ||||||||||
| static/API-key credentials. | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| from collections.abc import Sequence | ||||||||||
|
|
||||||||||
| from alembic import op | ||||||||||
|
|
||||||||||
| # revision identifiers, used by Alembic. | ||||||||||
| revision: str = "c8f2d1e4a5b6" | ||||||||||
| down_revision: str | None = "b7e1c9f2a3d4" | ||||||||||
| branch_labels: str | Sequence[str] | None = None | ||||||||||
| depends_on: str | Sequence[str] | None = None | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def upgrade() -> None: | ||||||||||
| # Backfill workspace-scoped Integration rows for every Secret name that | ||||||||||
| # doesn't already have a catalog entry. Source=workspace so we can tell | ||||||||||
| # these apart from the platform-seeded providers. | ||||||||||
| op.execute( | ||||||||||
| """ | ||||||||||
| INSERT INTO integration ( | ||||||||||
| id, workspace_id, namespace, display_name, description, | ||||||||||
| source, created_at, updated_at | ||||||||||
| ) | ||||||||||
| SELECT | ||||||||||
| gen_random_uuid(), | ||||||||||
| s.workspace_id, | ||||||||||
| s.name, | ||||||||||
| INITCAP(REPLACE(s.name, '_', ' ')), | ||||||||||
| 'Backfilled from legacy credential.', | ||||||||||
| 'workspace', | ||||||||||
| NOW(), | ||||||||||
| NOW() | ||||||||||
| FROM ( | ||||||||||
| SELECT DISTINCT workspace_id, name | ||||||||||
| FROM secret | ||||||||||
| ) AS s | ||||||||||
| WHERE NOT EXISTS ( | ||||||||||
| SELECT 1 | ||||||||||
| FROM integration i | ||||||||||
| WHERE i.namespace = s.name | ||||||||||
| AND ( | ||||||||||
| i.workspace_id IS NULL | ||||||||||
| OR i.workspace_id = s.workspace_id | ||||||||||
| ) | ||||||||||
| ) | ||||||||||
| """ | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def downgrade() -> None: | ||||||||||
| # This migration only adds catalog rows for pre-existing secret names. | ||||||||||
| # Leave them in place on downgrade; deleting them could hide credentials | ||||||||||
| # from the catalog after a downgrade/upgrade loop. | ||||||||||
| pass | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Do not use a silent no-op downgrade for a one-way data migration; raise an explicit Prompt for AI agents
Suggested change
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This uniqueness rule does not actually prevent duplicate platform rows because PostgreSQL treats
NULLvalues as distinct in unique constraints. Since platform entries useworkspace_id = NULL, multiple rows with the samenamespacecan be inserted, which can lead to duplicated catalog entries and unstable behavior in seed/list flows that assume one platform row per namespace.Useful? React with 👍 / 👎.