"""commit-reveal draw: round secret, participant order and draw height (B-73) Revision ID: d3e8b15a7c42 Revises: b7d2e41c9a05 Create Date: 2026-10-14 16:40:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision: str = 'd3e8b15a7c42' down_revision: Union[str, Sequence[str], None] = 'b7d2e41c9a05' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None _ACTIVE = "'open', 'closing', 'drawing', 'paying_out'" def upgrade() -> None: """Upgrade schema. B-73 (DAT-61 … DAT-66): the round's secret (`server_seed_enc`, `server_seed`, `server_seed_hash`), the fixed participant list (`participants_hash`, `draw_height`, `round_participants.draw_position`) and the number of confirmations the draw block needs (`draw_confirmations` on the round, copied from `round_config` at open). `bet_vout` records which output of a bet pays the pool; every existing bet pays it with its first output. Rounds that already exist keep NULL in the three secret columns (they were drawn, or are drawn, on the block hash alone) and take the default of 3 confirmations. Plain add_column / create_index calls — never batch_alter_table, which on SQLite rebuilds the table and silently drops the expression indexes (B-97, DAT-56). """ op.add_column("rounds", sa.Column("draw_confirmations", sa.Integer(), server_default="3", nullable=False)) op.add_column("rounds", sa.Column("server_seed_enc", sa.Text(), nullable=True)) op.add_column("rounds", sa.Column("server_seed", sa.String(length=64), nullable=True)) op.add_column("rounds", sa.Column("server_seed_hash", sa.String(length=64), nullable=True)) op.add_column("rounds", sa.Column("participants_hash", sa.String(length=64), nullable=True)) op.add_column("rounds", sa.Column("draw_height", sa.Integer(), nullable=True)) op.add_column("round_participants", sa.Column("bet_vout", sa.Integer(), server_default="0", nullable=False)) op.add_column("round_participants", sa.Column("draw_position", sa.Integer(), nullable=True)) op.create_index( "ux_round_participants_round_draw_position", "round_participants", ["round_id", "draw_position"], unique=True, ) op.add_column("round_config", sa.Column("draw_confirmations", sa.Integer(), server_default="3", nullable=False)) def downgrade() -> None: """Downgrade schema.""" op.drop_column("round_config", "draw_confirmations") op.drop_index("ux_round_participants_round_draw_position", table_name="round_participants") op.drop_column("round_participants", "draw_position") op.drop_column("round_participants", "bet_vout") for column in ("draw_height", "participants_hash", "server_seed_hash", "server_seed", "server_seed_enc", "draw_confirmations"): op.drop_column("rounds", column) # drop_column on SQLite is a table rebuild: recreate the expression index (B-97). op.execute("DROP INDEX IF EXISTS ix_rounds_single_active") op.execute(f"CREATE UNIQUE INDEX ix_rounds_single_active ON rounds ((1)) WHERE status IN ({_ACTIVE})")