"""add tree forking support with lineage tracking Revision ID: 022 Revises: 021 Create Date: 2026-02-07 Adds fork relationship and lineage tracking to trees table: - parent_tree_id: Points to immediate parent tree (NULL for root trees) - fork_reason: Optional engineer note explaining fork purpose - parent_updated_at: Snapshot of parent's updated_at at fork time - root_tree_id: Points to original tree at root of fork chain - fork_depth: How many forks deep (1 = direct fork, 2 = fork of fork, etc.) Fork on parent delete behavior: SET NULL (orphaned forks survive) Root tree delete behavior: SET NULL (lineage preserved via fork_depth) """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects.postgresql import UUID # revision identifiers, used by Alembic. revision: str = '022' down_revision: Union[str, None] = '021' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # Add fork tracking columns op.add_column('trees', sa.Column('parent_tree_id', UUID(as_uuid=True), nullable=True)) op.add_column('trees', sa.Column('fork_reason', sa.String(255), nullable=True)) op.add_column('trees', sa.Column('parent_updated_at', sa.DateTime(timezone=True), nullable=True)) # Add fork lineage columns op.add_column('trees', sa.Column('root_tree_id', UUID(as_uuid=True), nullable=True)) op.add_column('trees', sa.Column('fork_depth', sa.Integer, nullable=False, server_default='0')) # Add foreign key constraints op.create_foreign_key( 'fk_trees_parent_tree_id', 'trees', 'trees', ['parent_tree_id'], ['id'], ondelete='SET NULL' ) op.create_foreign_key( 'fk_trees_root_tree_id', 'trees', 'trees', ['root_tree_id'], ['id'], ondelete='SET NULL' ) # Add indexes for fork queries op.create_index('ix_trees_parent_tree_id', 'trees', ['parent_tree_id']) op.create_index('ix_trees_root_tree_id', 'trees', ['root_tree_id']) # Composite index for fork analytics (descendants + depth sorting) op.create_index('ix_trees_fork_analytics', 'trees', ['root_tree_id', 'fork_depth']) def downgrade() -> None: op.drop_index('ix_trees_fork_analytics', table_name='trees') op.drop_index('ix_trees_root_tree_id', table_name='trees') op.drop_index('ix_trees_parent_tree_id', table_name='trees') op.drop_constraint('fk_trees_root_tree_id', 'trees', type_='foreignkey') op.drop_constraint('fk_trees_parent_tree_id', 'trees', type_='foreignkey') op.drop_column('trees', 'fork_depth') op.drop_column('trees', 'root_tree_id') op.drop_column('trees', 'parent_updated_at') op.drop_column('trees', 'fork_reason') op.drop_column('trees', 'parent_tree_id')