"""Add persistent professional-software workspaces and job lineage. Revision ID: 0035 Revises: 0034 Create Date: 2026-08-19 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "0035" down_revision: str | None = "0034" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.create_table( "software_workspaces", sa.Column("workspace_id", postgresql.UUID(as_uuid=True), primary_key=True), sa.Column( "user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.user_id", ondelete="CASCADE"), nullable=False, ), sa.Column("capability", sa.Text(), nullable=False), sa.Column( "home_node_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("software_nodes.node_id", ondelete="SET NULL"), nullable=True, ), sa.Column( "head_job_id", postgresql.UUID(as_uuid=True), sa.ForeignKey( "software_jobs.job_id", name="fk_software_workspaces_head_job_id", ondelete="SET NULL", ), nullable=True, ), sa.Column("status", sa.Text(), nullable=False, server_default="pending"), sa.Column("size_bytes", sa.BigInteger(), nullable=False, server_default="0"), sa.Column("state_manifest", postgresql.JSONB(), nullable=False, server_default="[]"), sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True), sa.Column("last_reported_at", sa.DateTime(timezone=True), nullable=True), sa.Column("retention_until", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), ) op.create_index( "ix_software_workspaces_user_status", "software_workspaces", ["user_id", "status"], ) op.create_index( "ix_software_workspaces_node_status", "software_workspaces", ["home_node_id", "status"], ) op.add_column( "software_jobs", sa.Column("workspace_id", postgresql.UUID(as_uuid=True), nullable=True), ) op.add_column( "software_jobs", sa.Column("source_job_id", postgresql.UUID(as_uuid=True), nullable=True), ) op.add_column( "software_jobs", sa.Column("local_manifest", postgresql.JSONB(), nullable=False, server_default="[]"), ) op.add_column( "software_jobs", sa.Column("preview_manifest", postgresql.JSONB(), nullable=False, server_default="[]"), ) op.add_column( "software_jobs", sa.Column("export_status", sa.Text(), nullable=False, server_default="none"), ) op.add_column( "software_jobs", sa.Column("export_outputs", postgresql.JSONB(), nullable=False, server_default="[]"), ) op.create_foreign_key( "fk_software_jobs_workspace_id", "software_jobs", "software_workspaces", ["workspace_id"], ["workspace_id"], ondelete="SET NULL", ) op.create_foreign_key( "fk_software_jobs_source_job_id", "software_jobs", "software_jobs", ["source_job_id"], ["job_id"], ondelete="SET NULL", ) op.create_index("ix_software_jobs_workspace_created", "software_jobs", ["workspace_id", "created_at"]) def downgrade() -> None: op.drop_index("ix_software_jobs_workspace_created", table_name="software_jobs") op.drop_constraint("fk_software_jobs_source_job_id", "software_jobs", type_="foreignkey") op.drop_constraint("fk_software_jobs_workspace_id", "software_jobs", type_="foreignkey") op.drop_column("software_jobs", "export_outputs") op.drop_column("software_jobs", "export_status") op.drop_column("software_jobs", "preview_manifest") op.drop_column("software_jobs", "local_manifest") op.drop_column("software_jobs", "source_job_id") op.drop_column("software_jobs", "workspace_id") op.drop_constraint( "fk_software_workspaces_head_job_id", "software_workspaces", type_="foreignkey", ) op.drop_index("ix_software_workspaces_node_status", table_name="software_workspaces") op.drop_index("ix_software_workspaces_user_status", table_name="software_workspaces") op.drop_table("software_workspaces")