"""播种 ``github_admin`` (GitHub 组织管理员) 角色。 这个角色属于**研发侧的 org owner 本人**,不是 IT。理由:Helix 接管 GitHub 组织成员资格的执行之后,「谁该进哪个组织」「哪些部门天然就该 在里面」「一个离职的人是不是仍然是社区里合法的 maintainer」这几个判断 仍然只有 org owner 答得了。如果配置权留在 global_admin 手里,每加一条 自动加入规则都要走 IT —— 自动化就退化成了换一个人工窗口。 作用域是 GitHub 组织域的全部,域外一无所有(见 ``core/rbac.py`` 里 ``GITHUB_ADMIN_ROLE`` 的说明)。它也**不**加入任何硬编码 ``global_admin`` 的管理员广播扇出:一个研发侧的组织 owner 不该因为拿了这个角色就开始收 所有运维告警。 **本迁移只建角色,不授予任何人。** 与 541(安全员)不同 —— 那次的名单 是平台负责人当场给的。这次的候选人来自飞书审批流 ``AAF99056-...`` 的 流程管理员字段(刘寅 / 左武辉),但「谁在飞书上管这条流程」和「谁应该 在 Helix 里拿一个可以改自动加入规则的角色」是两个决定,后者应该由人在 RBAC 页面上明确做出,而不是由一次迁移代劳。授予入口:管理后台 → 角色 管理。 Revision ID: 567 Revises: 566 """ from __future__ import annotations import uuid import sqlalchemy as sa from alembic import op revision = "567" down_revision = "566" branch_labels = None depends_on = None ROLE = "github_admin" DESCRIPTION = ( "GitHub 组织管理员 —— GitHub 组织域读写(组织登记、自动加入规则、" "成员台账、对账处置),不含全局管理员权限" ) def _table_exists(bind, name: str) -> bool: return bind.execute( sa.text( "SELECT 1 FROM information_schema.tables " "WHERE table_schema = DATABASE() AND table_name = :n" ), {"n": name}, ).scalar() is not None def upgrade() -> None: bind = op.get_bind() if not _table_exists(bind, "auth_roles"): return existing = bind.execute( sa.text("SELECT id FROM auth_roles WHERE name = :n"), {"n": ROLE}, ).scalar() if existing is not None: return bind.execute( sa.text( "INSERT INTO auth_roles (id, name, description, is_system, " " created_at) " "VALUES (:id, :n, :d, 1, UTC_TIMESTAMP())" ), {"id": str(uuid.uuid4()), "n": ROLE, "d": DESCRIPTION}, ) def downgrade() -> None: bind = op.get_bind() if not _table_exists(bind, "auth_roles"): return # 先撤授予再删角色 —— 留着孤儿 auth_user_roles 行会让下一次 # 「这个人有哪些角色」的查询读到一个不存在的角色名。 if _table_exists(bind, "auth_user_roles"): bind.execute( sa.text( "DELETE FROM auth_user_roles WHERE role_id IN " "(SELECT id FROM auth_roles WHERE name = :n)" ), {"n": ROLE}, ) bind.execute(sa.text("DELETE FROM auth_roles WHERE name = :n"), {"n": ROLE})