Files
FabledGrimoire/alembic/versions/0118_rule_moment_judgments.py
T

61 lines
2.7 KiB
Python

"""rule_moment_judgments — whether a rule belongs on a moment, and who said so
Revision ID: 0118
Revises: 0117
Create Date: 2026-10-05
One row per (rule, moment) with a state: `suggested` while a proposal waits
for a judgment, `confirmed` or `rejected` once one is made. The mount itself
stays in rule_moments; this records the proposals that are not mounts yet and
the rejections that must stop them being proposed again. Moment '' is "no
moment fits this rule". CASCADE on the rule — a judgment about a rule that no
longer exists says nothing. No backfill: the mounts made before this table
were judgments too, but recording them now would invent who made them.
"""
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
revision = "0118"
down_revision = "0117"
branch_labels = None
depends_on = None
# One place each, so the CHECKs and the model's JUDGMENT_STATES / SOURCES
# cannot drift (a new value later means DROP + ADD CONSTRAINT in the same
# migration).
_STATES = ("suggested", "confirmed", "rejected")
_SOURCES = ("pass", "signal", "edit")
def upgrade() -> None:
op.create_table(
"rule_moment_judgments",
sa.Column("id", sa.BigInteger(), primary_key=True),
sa.Column("rule_id", sa.BigInteger(), sa.ForeignKey("rules.id", ondelete="CASCADE"), nullable=False),
sa.Column("moment", sa.Text(), nullable=False),
sa.Column("state", sa.Text(), nullable=False, server_default="suggested"),
sa.Column("source", sa.Text(), nullable=False, server_default="pass"),
sa.Column("note", sa.Text(), nullable=True),
sa.Column("evidence", postgresql.JSONB(), nullable=True),
sa.Column("judged_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.UniqueConstraint("rule_id", "moment", name="uq_rule_moment_judgments_pair"),
)
op.create_check_constraint(
"ck_rule_moment_judgments_state", "rule_moment_judgments",
"state IN (" + ", ".join(f"'{s}'" for s in _STATES) + ")",
)
op.create_check_constraint(
"ck_rule_moment_judgments_source", "rule_moment_judgments",
"source IN (" + ", ".join(f"'{s}'" for s in _SOURCES) + ")",
)
op.create_index("ix_rule_moment_judgments_rule_id", "rule_moment_judgments", ["rule_id"])
def downgrade() -> None:
op.drop_index("ix_rule_moment_judgments_rule_id", table_name="rule_moment_judgments")
op.drop_constraint("ck_rule_moment_judgments_source", "rule_moment_judgments", type_="check")
op.drop_constraint("ck_rule_moment_judgments_state", "rule_moment_judgments", type_="check")
op.drop_table("rule_moment_judgments")