feat: add challenge and red-blue competitions across API and web

This commit is contained in:
Joey Yakimowich-Payne 2025-10-01 06:49:09 -06:00
commit 8fd3c4bb64
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
77 changed files with 5355 additions and 24 deletions

View file

@ -0,0 +1,170 @@
"""add challenge & red/blue tables
Revision ID: 183e2d30fb4e
Revises: 68519ad5cd18
Create Date: 2025-09-30 08:22:31.223257
"""
from alembic import op
import models as models
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '183e2d30fb4e'
down_revision = '68519ad5cd18'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('challenge_attempts',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('challenge_id', models.types.StringUUID(), nullable=False),
sa.Column('end_user_id', models.types.StringUUID(), nullable=True),
sa.Column('account_id', models.types.StringUUID(), nullable=True),
sa.Column('workflow_run_id', models.types.StringUUID(), nullable=True),
sa.Column('succeeded', sa.Boolean(), server_default=sa.text('false'), nullable=False),
sa.Column('score', sa.Float(), nullable=True),
sa.Column('judge_rating', sa.Integer(), nullable=True),
sa.Column('judge_feedback', sa.Text(), nullable=True),
sa.Column('judge_output_raw', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('tokens_total', sa.Integer(), nullable=True),
sa.Column('elapsed_ms', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='challenge_attempts_pkey')
)
with op.batch_alter_table('challenge_attempts', schema=None) as batch_op:
batch_op.create_index('challenge_attempts_challenge_id_idx', ['challenge_id'], unique=False)
batch_op.create_index('challenge_attempts_tenant_id_idx', ['tenant_id'], unique=False)
op.create_table('challenges',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('app_id', models.types.StringUUID(), nullable=False),
sa.Column('workflow_id', models.types.StringUUID(), nullable=True),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('goal', sa.Text(), nullable=True),
sa.Column('success_type', sa.String(length=64), server_default=sa.text("'regex'"), nullable=False),
sa.Column('success_pattern', sa.Text(), nullable=True),
sa.Column('secret_ref', sa.Text(), nullable=True),
sa.Column('evaluator_type', sa.String(length=32), server_default=sa.text("'rules'"), nullable=False),
sa.Column('evaluator_plugin_id', sa.Text(), nullable=True),
sa.Column('evaluator_entrypoint', sa.Text(), nullable=True),
sa.Column('evaluator_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('scoring_strategy', sa.String(length=64), server_default=sa.text("'first'"), nullable=False),
sa.Column('scoring_plugin_id', sa.Text(), nullable=True),
sa.Column('scoring_entrypoint', sa.Text(), nullable=True),
sa.Column('scoring_config', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('is_active', sa.Boolean(), server_default=sa.text('true'), nullable=False),
sa.Column('created_by', models.types.StringUUID(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_by', models.types.StringUUID(), nullable=True),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='challenges_pkey')
)
with op.batch_alter_table('challenges', schema=None) as batch_op:
batch_op.create_index('challenges_app_id_idx', ['app_id'], unique=False)
batch_op.create_index('challenges_tenant_id_idx', ['tenant_id'], unique=False)
op.create_table('red_blue_challenges',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('app_id', models.types.StringUUID(), nullable=False),
sa.Column('workflow_id', models.types.StringUUID(), nullable=True),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('judge_suite', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column('defense_selection_policy', sa.String(length=64), server_default=sa.text("'latest_best'"), nullable=False),
sa.Column('attack_selection_policy', sa.String(length=64), server_default=sa.text("'latest_best'"), nullable=False),
sa.Column('scoring_strategy', sa.String(length=64), server_default=sa.text("'red_blue_ratio'"), nullable=False),
sa.Column('theme', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('instructions_md', sa.Text(), nullable=True),
sa.Column('is_active', sa.Boolean(), server_default=sa.text('true'), nullable=False),
sa.Column('created_by', models.types.StringUUID(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.Column('updated_by', models.types.StringUUID(), nullable=True),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='red_blue_challenges_pkey')
)
with op.batch_alter_table('red_blue_challenges', schema=None) as batch_op:
batch_op.create_index('red_blue_challenges_app_id_idx', ['app_id'], unique=False)
batch_op.create_index('red_blue_challenges_tenant_id_idx', ['tenant_id'], unique=False)
op.create_table('team_pairings',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('red_blue_challenge_id', models.types.StringUUID(), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('attack_submission_id', models.types.StringUUID(), nullable=True),
sa.Column('defense_submission_id', models.types.StringUUID(), nullable=True),
sa.Column('judge_output_raw', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('categories', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('judge_rating', sa.Integer(), nullable=True),
sa.Column('judge_feedback', sa.Text(), nullable=True),
sa.Column('red_points', sa.Float(), server_default=sa.text('0'), nullable=False),
sa.Column('blue_points', sa.Float(), server_default=sa.text('0'), nullable=False),
sa.Column('tokens_total', sa.Integer(), nullable=True),
sa.Column('elapsed_ms', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='team_pairings_pkey')
)
with op.batch_alter_table('team_pairings', schema=None) as batch_op:
batch_op.create_index('team_pairings_challenge_id_idx', ['red_blue_challenge_id'], unique=False)
batch_op.create_index('team_pairings_tenant_id_idx', ['tenant_id'], unique=False)
op.create_table('team_submissions',
sa.Column('id', models.types.StringUUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
sa.Column('red_blue_challenge_id', models.types.StringUUID(), nullable=False),
sa.Column('tenant_id', models.types.StringUUID(), nullable=False),
sa.Column('account_id', models.types.StringUUID(), nullable=True),
sa.Column('end_user_id', models.types.StringUUID(), nullable=True),
sa.Column('team', sa.String(length=16), nullable=False),
sa.Column('prompt', sa.Text(), nullable=False),
sa.Column('active', sa.Boolean(), server_default=sa.text('true'), nullable=False),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=False),
sa.PrimaryKeyConstraint('id', name='team_submissions_pkey')
)
with op.batch_alter_table('team_submissions', schema=None) as batch_op:
batch_op.create_index('team_submissions_challenge_id_idx', ['red_blue_challenge_id'], unique=False)
batch_op.create_index('team_submissions_tenant_id_idx', ['tenant_id'], unique=False)
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.drop_column('credential_status')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('providers', schema=None) as batch_op:
batch_op.add_column(sa.Column('credential_status', sa.VARCHAR(length=20), server_default=sa.text("'active'::character varying"), autoincrement=False, nullable=True))
with op.batch_alter_table('team_submissions', schema=None) as batch_op:
batch_op.drop_index('team_submissions_tenant_id_idx')
batch_op.drop_index('team_submissions_challenge_id_idx')
op.drop_table('team_submissions')
with op.batch_alter_table('team_pairings', schema=None) as batch_op:
batch_op.drop_index('team_pairings_tenant_id_idx')
batch_op.drop_index('team_pairings_challenge_id_idx')
op.drop_table('team_pairings')
with op.batch_alter_table('red_blue_challenges', schema=None) as batch_op:
batch_op.drop_index('red_blue_challenges_tenant_id_idx')
batch_op.drop_index('red_blue_challenges_app_id_idx')
op.drop_table('red_blue_challenges')
with op.batch_alter_table('challenges', schema=None) as batch_op:
batch_op.drop_index('challenges_tenant_id_idx')
batch_op.drop_index('challenges_app_id_idx')
op.drop_table('challenges')
with op.batch_alter_table('challenge_attempts', schema=None) as batch_op:
batch_op.drop_index('challenge_attempts_tenant_id_idx')
batch_op.drop_index('challenge_attempts_challenge_id_idx')
op.drop_table('challenge_attempts')
# ### end Alembic commands ###