"""撤下配置发布的"审核人不得等于创建人"检查约束。 为什么撤下:单管理员部署下自审是唯一可行的发布路径。原约束 `chk_config_release_separation`(`reviewer_id IS NULL OR reviewer_id <> created_by`) 让"创建人自审"在数据库层被拒绝,表现为审核接口 500 而不是业务异常;同时自审若按 "留空 reviewer_id"规避约束,又会被 `admin_service` 的激活分支判为"版本未审核" (该分支要求 `reviewer_id is not None`),自审版本因此无法激活。 服务层 `ConfigReleaseService.approve`、`AdminService` 均已按"允许自审、如实写入 reviewer_id"实现,本迁移补齐缺失的数据库侧变更。 对基线的影响:本迁移只删除 `config_release` 上的一个 CHECK 约束,不新增、不重命名、 不删除任何表或字段,不改变任何字段的类型、可空性与业务含义;`chk_config_release_status` 约束以及全部外键、唯一键、索引保持原样(可用 `tools/schema_fingerprint.py` 与 `tools/audit_constraints.py` 复核)。 回滚:`downgrade` 重建该约束。若库中已存在 `reviewer_id = created_by` 的行,重建会被 MySQL 拒绝——这是有意为之:回滚到"双人复核"语义前必须先处理这些自审记录。 """ from sqlalchemy import text from alembic import op revision = "20260911_drop_review_separation" down_revision = "20260911_field_correction" branch_labels = None depends_on = None TABLE = "config_release" CONSTRAINT = "chk_config_release_separation" CLAUSE = "reviewer_id IS NULL OR reviewer_id <> created_by" def _constraint_exists() -> bool: connection = op.get_bind() found = connection.scalar( text( """ SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = database() AND TABLE_NAME = :table AND CONSTRAINT_NAME = :constraint AND CONSTRAINT_TYPE = 'CHECK' """ ), {"table": TABLE, "constraint": CONSTRAINT}, ) return int(found or 0) > 0 def upgrade() -> None: """撤下检查约束;约束已不存在时跳过(兼容手工处理过的库)。""" if not _constraint_exists(): return op.execute(f"ALTER TABLE {TABLE} DROP CHECK {CONSTRAINT}") def downgrade() -> None: """恢复双人复核约束;存在自审记录时 MySQL 会拒绝执行。""" if _constraint_exists(): return op.execute(f"ALTER TABLE {TABLE} ADD CONSTRAINT {CONSTRAINT} CHECK ({CLAUSE})")