"""rulebook tables: rulebooks, rulebook_topics, rules, project_rulebook_subscriptions Revision ID: 0055 Revises: 0054 Create Date: 2026-05-27 Adds the Scribe Rulebook hierarchy as a fourth top-level entity, sibling to Project. Rules carry a structural (statement, why, how_to_apply) triple. Projects subscribe to Rulebooks (many-to-many). See the design doc at docs/superpowers/specs/2026-05-27-scribe-rulebook-design.md. """ from alembic import op import sqlalchemy as sa revision = "0055" down_revision = "0054" branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "rulebooks", sa.Column("id", sa.BigInteger, primary_key=True), sa.Column( "owner_user_id", sa.BigInteger, sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column("title", sa.Text, nullable=False), sa.Column("description", sa.Text), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), ) op.create_index("ix_rulebooks_owner", "rulebooks", ["owner_user_id"]) op.create_table( "rulebook_topics", sa.Column("id", sa.BigInteger, primary_key=True), sa.Column( "rulebook_id", sa.BigInteger, sa.ForeignKey("rulebooks.id", ondelete="CASCADE"), nullable=False, ), sa.Column("title", sa.Text, nullable=False), sa.Column("description", sa.Text), sa.Column( "order_index", sa.Integer, nullable=False, server_default="0", ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.UniqueConstraint("rulebook_id", "title", name="uq_topic_per_rulebook"), ) op.create_index( "ix_rulebook_topics_rulebook", "rulebook_topics", ["rulebook_id"], ) op.create_table( "rules", sa.Column("id", sa.BigInteger, primary_key=True), sa.Column( "topic_id", sa.BigInteger, sa.ForeignKey("rulebook_topics.id", ondelete="CASCADE"), nullable=False, ), sa.Column("title", sa.Text, nullable=False), sa.Column("statement", sa.Text, nullable=False), sa.Column("why", sa.Text), sa.Column("how_to_apply", sa.Text), sa.Column( "order_index", sa.Integer, nullable=False, server_default="0", ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.UniqueConstraint("topic_id", "title", name="uq_rule_per_topic"), ) op.create_index("ix_rules_topic", "rules", ["topic_id"]) op.create_table( "project_rulebook_subscriptions", sa.Column( "project_id", sa.BigInteger, sa.ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "rulebook_id", sa.BigInteger, sa.ForeignKey("rulebooks.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.PrimaryKeyConstraint("project_id", "rulebook_id"), ) op.create_index( "ix_project_rulebook_subs_rulebook", "project_rulebook_subscriptions", ["rulebook_id"], ) def downgrade() -> None: op.drop_index( "ix_project_rulebook_subs_rulebook", table_name="project_rulebook_subscriptions", ) op.drop_table("project_rulebook_subscriptions") op.drop_index("ix_rules_topic", table_name="rules") op.drop_table("rules") op.drop_index("ix_rulebook_topics_rulebook", table_name="rulebook_topics") op.drop_table("rulebook_topics") op.drop_index("ix_rulebooks_owner", table_name="rulebooks") op.drop_table("rulebooks")