"""add workstream planning metadata and typed dependency targets Revision ID: s6n7o8p9q0r1 Revises: r5m6n7o8p9q0 Create Date: 2026-05-04 """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects.postgresql import UUID revision = "s6n7o8p9q0r1" down_revision = "r5m6n7o8p9q0" branch_labels = None depends_on = None def upgrade() -> None: op.add_column("workstreams", sa.Column("planning_priority", sa.String(length=20), nullable=True)) op.add_column("workstreams", sa.Column("planning_order", sa.Integer(), nullable=True)) op.create_index("ix_workstreams_planning_priority", "workstreams", ["planning_priority"]) op.create_index("ix_workstreams_planning_order", "workstreams", ["planning_order"]) op.drop_constraint("uq_ws_dep_pair", "workstream_dependencies", type_="unique") op.add_column("workstream_dependencies", sa.Column("to_task_id", UUID(as_uuid=True), nullable=True)) op.add_column( "workstream_dependencies", sa.Column("relationship_type", sa.String(length=40), nullable=False, server_default="blocks"), ) op.alter_column( "workstream_dependencies", "to_workstream_id", existing_type=UUID(as_uuid=True), nullable=True, ) op.create_foreign_key( "fk_ws_dep_to_task_id", "workstream_dependencies", "tasks", ["to_task_id"], ["id"], ondelete="CASCADE", ) op.create_index("ix_workstream_dependencies_to_task_id", "workstream_dependencies", ["to_task_id"]) op.create_index( "ix_workstream_dependencies_relationship_type", "workstream_dependencies", ["relationship_type"], ) op.create_check_constraint( "ck_ws_dep_exactly_one_target", "workstream_dependencies", "(to_workstream_id IS NOT NULL AND to_task_id IS NULL) " "OR (to_workstream_id IS NULL AND to_task_id IS NOT NULL)", ) op.create_index( "uq_ws_dep_workstream_target", "workstream_dependencies", ["from_workstream_id", "to_workstream_id", "relationship_type"], unique=True, postgresql_where=sa.text("to_workstream_id IS NOT NULL"), ) op.create_index( "uq_ws_dep_task_target", "workstream_dependencies", ["from_workstream_id", "to_task_id", "relationship_type"], unique=True, postgresql_where=sa.text("to_task_id IS NOT NULL"), ) def downgrade() -> None: op.drop_index("uq_ws_dep_task_target", table_name="workstream_dependencies") op.drop_index("uq_ws_dep_workstream_target", table_name="workstream_dependencies") op.drop_constraint("ck_ws_dep_exactly_one_target", "workstream_dependencies", type_="check") op.drop_index("ix_workstream_dependencies_relationship_type", table_name="workstream_dependencies") op.drop_index("ix_workstream_dependencies_to_task_id", table_name="workstream_dependencies") op.drop_constraint("fk_ws_dep_to_task_id", "workstream_dependencies", type_="foreignkey") op.alter_column( "workstream_dependencies", "to_workstream_id", existing_type=UUID(as_uuid=True), nullable=False, ) op.drop_column("workstream_dependencies", "relationship_type") op.drop_column("workstream_dependencies", "to_task_id") op.create_unique_constraint( "uq_ws_dep_pair", "workstream_dependencies", ["from_workstream_id", "to_workstream_id"], ) op.drop_index("ix_workstreams_planning_order", table_name="workstreams") op.drop_index("ix_workstreams_planning_priority", table_name="workstreams") op.drop_column("workstreams", "planning_order") op.drop_column("workstreams", "planning_priority")