Merge main (roles and access revamp) into connectors

Main's access model (team editors and viewers, edit_credentials, owner-only
OAuth servers, per-user tool preferences, resource sponsors) now applies to
connection-backed tools and sources. Our migrations are renumbered to
0040_connections and 0041_connection_account_name, after main's
0038_resource_access_settings and 0039_resource_sponsors.

Where the two sides met: MCP tools save and load their connections as the
tool owner, editors may add a key (a new connection on the owner's account)
but never rewrite an existing connection's secret, fixed values stay
owner-only, and a member's own connection is used in member mode.
This commit is contained in:
arc53-machine committed 2026-09-29 14:02:26 +01:00
commit a1789d2ab4
167 files changed
+16909 -2126

No files matched your search

@@ -0,0 +1,65 @@
"""0038 resource access settings — per-asset sharing switches and tool chat prefs.
``resource_share_settings`` holds the owner's per-asset switches that adjust
what the fixed roles may do on one shared resource ("Editors can share",
"Viewers can see logs", ...). One row per ``(resource_type, resource_id)``;
a missing row means every switch is at its default. The table is polymorphic
like ``team_resource_grants``, so it has no FK and the switch keys are
validated in code (``docsgpt/api/user/resource_access.py``), which keeps new
switches migration-free.
``user_tool_preferences`` is the personal "In my chats" switch for a tool
shared with the caller. A grantee can't write the owner's ``user_tools.status``
(that is the owner's own chat setting), so each grantee gets a row here.
A missing row means off: sharing a tool never adds it to anyone's chats.
Idempotent both ways.
Revision ID: 0038_resource_access_settings
Revises: 0037_request_traces
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0038_resource_access_settings"
down_revision: Union[str, None] = "0037_request_traces"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.execute(
"""
CREATE TABLE IF NOT EXISTS resource_share_settings (
resource_type TEXT NOT NULL
CONSTRAINT resource_share_settings_type_check
CHECK (resource_type IN ('agent', 'source', 'prompt', 'tool')),
resource_id UUID NOT NULL,
settings JSONB NOT NULL DEFAULT '{}'::jsonb,
updated_by TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (resource_type, resource_id)
);
"""
)
op.execute(
"""
CREATE TABLE IF NOT EXISTS user_tool_preferences (
user_id TEXT NOT NULL,
tool_id UUID NOT NULL REFERENCES user_tools(id) ON DELETE CASCADE,
in_chat BOOLEAN NOT NULL DEFAULT false,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, tool_id)
);
CREATE INDEX IF NOT EXISTS user_tool_preferences_tool_idx
ON user_tool_preferences (tool_id);
"""
)
def downgrade() -> None:
op.execute("DROP TABLE IF EXISTS user_tool_preferences;")
op.execute("DROP TABLE IF EXISTS resource_share_settings;")
@@ -0,0 +1,40 @@
"""0039 resource sponsors — who vouches for a saved resource the owner can't use.
An agent (and a workflow) runs as its owner, so every source, prompt and tool
it references is authorized against the owner. A team editor who attaches
their own private tool, prompt or source would have it dropped at run time.
``resource_sponsors`` records the editor who attached such a resource, keyed
``"<type>:<id>"`` (e.g. ``{"tool:<uuid>": "bob"}``). At run time the resource
is authorized as that sponsor, provided they can still edit the agent and
still use the resource (``docsgpt/api/user/resource_access.py``).
An empty map means today's behaviour: owner-only authorization.
Idempotent both ways.
Revision ID: 0039_resource_sponsors
Revises: 0038_resource_access_settings
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0039_resource_sponsors"
down_revision: Union[str, None] = "0038_resource_access_settings"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
for table in ("agents", "workflows"):
op.execute(
f"ALTER TABLE {table} ADD COLUMN IF NOT EXISTS "
"resource_sponsors JSONB NOT NULL DEFAULT '{}'::jsonb;"
)
def downgrade() -> None:
for table in ("agents", "workflows"):
op.execute(f"ALTER TABLE {table} DROP COLUMN IF EXISTS resource_sponsors;")
@@ -1,4 +1,4 @@
"""0038 connections — connector_sessions becomes the connections table.
"""0040 connections — connector_sessions becomes the connections table.
``connector_sessions`` already holds one row per signed-in account (OAuth
ingest providers) or per MCP server. This migration names what each row is
@@ -49,8 +49,8 @@ until then. The instance-wide "Allow custom MCP servers" switch
is the ``connectors.allow_custom_mcp`` key in ``app_metadata`` (absent means
allowed).
Revision ID: 0038_connections
Revises: 0037_request_traces
Revision ID: 0040_connections
Revises: 0039_resource_sponsors
"""
from typing import Sequence, Union
@@ -58,8 +58,8 @@ from typing import Sequence, Union
from alembic import op
revision: str = "0038_connections"
down_revision: Union[str, None] = "0037_request_traces"
revision: str = "0040_connections"
down_revision: Union[str, None] = "0039_resource_sponsors"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@@ -1,4 +1,4 @@
"""0039 connection account name — what the user calls an account.
"""0041 connection account name — what the user calls an account.
``account_label`` identifies an account: the email an OAuth sign-in returns,
or a hint of a pasted key. Signing in again finds the connection by it, so it
@@ -9,8 +9,8 @@ user never named it.
Idempotent both ways.
Revision ID: 0039_connection_account_name
Revises: 0038_connections
Revision ID: 0041_connection_account_name
Revises: 0040_connections
"""
from typing import Sequence, Union
@@ -18,8 +18,8 @@ from typing import Sequence, Union
from alembic import op
revision: str = "0039_connection_account_name"
down_revision: Union[str, None] = "0038_connections"
revision: str = "0041_connection_account_name"
down_revision: Union[str, None] = "0040_connections"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None