"""backup_blob: which library files are already in the off-site bucket. One row per (destination, sha256) sent. The media backup's work list is every image and attachment sha with no row for the current destination, so a run is incremental and resumable by construction, and pointing backups at a new bucket starts that bucket's copy from the beginning. backup_run.last_progress_at is the heartbeat of a run that spans hours; the stall sweep measures a media run's silence from it, not from its start. Revision ID: 0121 Revises: 0120 Create Date: 2026-11-07 """ import sqlalchemy as sa from alembic import op revision = "0121" down_revision = "0120" branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "backup_blob", sa.Column("target", sa.Text(), nullable=False), sa.Column("sha256", sa.String(64), nullable=False), sa.Column("key", sa.Text(), nullable=False), sa.Column("size_bytes", sa.BigInteger(), nullable=False), sa.Column( "uploaded_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()"), ), sa.PrimaryKeyConstraint("target", "sha256", name=op.f("pk_backup_blob")), ) op.add_column( "backup_run", sa.Column("last_progress_at", sa.DateTime(timezone=True), nullable=True), ) def downgrade() -> None: op.drop_column("backup_run", "last_progress_at") op.drop_table("backup_blob")