From 31678beec7024d7ef7d7fcabe264af24a67ac9b6 Mon Sep 17 00:00:00 2001 From: jedarden Date: Fri, 22 May 2026 15:52:54 -0400 Subject: [PATCH] Infra: Add initial SQL migration file (0001_initial.sql) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the embedded schema from cmd/acb-api/db.go into a standalone migration file per ยง11.1 plan inventory. Creates all core tables: - Core: bots, matches, match_participants, jobs, rating_history - Season/series: seasons, season_snapshots, series, series_games - Predictions: predictions, predictor_stats - Maps: maps, map_scores, map_votes, map_fairness - Evolution: programs - Playlists: playlists, playlist_matches - Feedback: replay_feedback, feedback_upvotes - Enrichment: enrichment_requests Co-Authored-By: Claude Opus 4.7 --- migrations/0001_initial.sql | 296 ++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 migrations/0001_initial.sql diff --git a/migrations/0001_initial.sql b/migrations/0001_initial.sql new file mode 100644 index 0000000..f90d348 --- /dev/null +++ b/migrations/0001_initial.sql @@ -0,0 +1,296 @@ +-- Initial schema for AI Code Battle +-- This migration creates all core tables for the application +-- Includes: bots, matches, jobs, ratings, seasons, series, maps, programs, playlists, feedback, enrichment + +-- ===================================================== +-- Core Tables +-- ===================================================== + +CREATE TABLE bots ( + bot_id VARCHAR(16) PRIMARY KEY, + name VARCHAR(32) UNIQUE NOT NULL, + owner VARCHAR(128) NOT NULL, + endpoint_url TEXT NOT NULL, + shared_secret TEXT NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'pending', + rating_mu DOUBLE PRECISION NOT NULL DEFAULT 1500.0, + rating_phi DOUBLE PRECISION NOT NULL DEFAULT 350.0, + rating_sigma DOUBLE PRECISION NOT NULL DEFAULT 0.06, + evolved BOOLEAN NOT NULL DEFAULT FALSE, + island VARCHAR(16), + generation INTEGER, + parent_ids JSONB, + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_active TIMESTAMPTZ, + consec_fails INTEGER NOT NULL DEFAULT 0, + archetype VARCHAR(64), + crash_strikes INTEGER NOT NULL DEFAULT 0, + cooldown_until TIMESTAMPTZ, + debug_public BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE matches ( + match_id VARCHAR(32) PRIMARY KEY, + map_id VARCHAR(32) NOT NULL, + map_seed BIGINT, + status VARCHAR(16) NOT NULL DEFAULT 'pending', + winner INTEGER, + condition VARCHAR(32), + turn_count INTEGER, + combat_turns INTEGER NOT NULL DEFAULT 0, + scores_json JSONB, + enrichment_requested_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + completed_at TIMESTAMPTZ +); + +CREATE TABLE match_participants ( + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + bot_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + player_slot INTEGER NOT NULL, + score INTEGER, + status VARCHAR(16), + PRIMARY KEY (match_id, bot_id) +); + +CREATE TABLE jobs ( + job_id VARCHAR(32) PRIMARY KEY, + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + status VARCHAR(16) NOT NULL DEFAULT 'pending', + worker_id VARCHAR(64), + config_json JSONB NOT NULL, + claimed_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + heartbeat_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE rating_history ( + bot_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + rating DOUBLE PRECISION NOT NULL, + recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (bot_id, match_id) +); +CREATE INDEX idx_rating_history_bot ON rating_history(bot_id, recorded_at); + +-- ===================================================== +-- Season & Series Tables (Phase 9) +-- ===================================================== + +CREATE TABLE seasons ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(64) NOT NULL, + theme VARCHAR(128), + rules_version VARCHAR(32) NOT NULL DEFAULT '1.0', + status VARCHAR(16) NOT NULL DEFAULT 'active', + champion_id VARCHAR(16), + starts_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + ends_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE season_snapshots ( + id BIGSERIAL PRIMARY KEY, + season_id BIGINT NOT NULL REFERENCES seasons(id), + bot_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + rank INTEGER NOT NULL, + rating DOUBLE PRECISION NOT NULL, + wins INTEGER NOT NULL DEFAULT 0, + losses INTEGER NOT NULL DEFAULT 0, + recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_season_snapshots_season ON season_snapshots(season_id, rank); + +CREATE TABLE series ( + id BIGSERIAL PRIMARY KEY, + bot_a_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + bot_b_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + format INTEGER NOT NULL DEFAULT 5, + a_wins INTEGER NOT NULL DEFAULT 0, + b_wins INTEGER NOT NULL DEFAULT 0, + status VARCHAR(16) NOT NULL DEFAULT 'active', + winner_id VARCHAR(16), + season_id BIGINT REFERENCES seasons(id), + bracket_round VARCHAR(32), + bracket_position INTEGER, + featured BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_series_bots ON series(bot_a_id, bot_b_id); +CREATE INDEX idx_series_status ON series(status); +CREATE INDEX idx_series_season ON series(season_id); +CREATE INDEX idx_series_bracket ON series(season_id, bracket_round) WHERE bracket_round IS NOT NULL; +CREATE INDEX idx_series_featured ON series(featured, created_at DESC) WHERE featured = TRUE; + +CREATE TABLE series_games ( + id BIGSERIAL PRIMARY KEY, + series_id BIGINT NOT NULL REFERENCES series(id), + match_id VARCHAR(32) REFERENCES matches(match_id), + game_num INTEGER NOT NULL, + winner_id VARCHAR(16), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_series_games_series ON series_games(series_id); + +-- ===================================================== +-- Prediction Tables +-- ===================================================== + +CREATE TABLE predictions ( + id BIGSERIAL PRIMARY KEY, + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + predictor_id VARCHAR(64) NOT NULL, + predicted_bot VARCHAR(16) NOT NULL, + confidence SMALLINT CHECK (confidence >= 1 AND confidence <= 100), + correct BOOLEAN, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + resolved_at TIMESTAMPTZ, + UNIQUE(match_id, predictor_id) +); +CREATE INDEX idx_predictions_match ON predictions(match_id); +CREATE INDEX idx_predictions_predictor ON predictions(predictor_id); + +CREATE TABLE predictor_stats ( + predictor_id VARCHAR(64) PRIMARY KEY, + correct INTEGER NOT NULL DEFAULT 0, + incorrect INTEGER NOT NULL DEFAULT 0, + streak INTEGER NOT NULL DEFAULT 0, + best_streak INTEGER NOT NULL DEFAULT 0, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- ===================================================== +-- Map Tables +-- ===================================================== + +CREATE TABLE maps ( + map_id VARCHAR(32) PRIMARY KEY, + player_count INTEGER NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'active', + engagement DOUBLE PRECISION NOT NULL DEFAULT 0.0, + wall_density DOUBLE PRECISION NOT NULL DEFAULT 0.0, + energy_count INTEGER NOT NULL DEFAULT 0, + grid_width INTEGER NOT NULL, + grid_height INTEGER NOT NULL, + map_json JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + retired_at TIMESTAMPTZ +); +CREATE INDEX idx_maps_status ON maps(status, player_count); +CREATE INDEX idx_maps_engagement ON maps(player_count, engagement DESC); + +CREATE TABLE map_scores ( + map_id VARCHAR(32) PRIMARY KEY, + engagement DOUBLE PRECISION NOT NULL DEFAULT 0.0, + symmetry_score DOUBLE PRECISION NOT NULL DEFAULT 0.0, + wall_density DOUBLE PRECISION NOT NULL DEFAULT 0.0, + last_used_at TIMESTAMPTZ, + match_count INTEGER NOT NULL DEFAULT 0, + avg_turns DOUBLE PRECISION, + scored_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE map_votes ( + id BIGSERIAL PRIMARY KEY, + map_id VARCHAR(32) NOT NULL REFERENCES maps(map_id) ON DELETE CASCADE, + voter_id VARCHAR(64) NOT NULL, + vote SMALLINT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(map_id, voter_id) +); +CREATE INDEX idx_map_votes_map ON map_votes(map_id); + +CREATE TABLE map_fairness ( + map_id VARCHAR(32) NOT NULL REFERENCES maps(map_id) ON DELETE CASCADE, + player_slot INTEGER NOT NULL, + games INTEGER NOT NULL DEFAULT 0, + wins INTEGER NOT NULL DEFAULT 0, + last_check TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (map_id, player_slot) +); + +-- ===================================================== +-- Program Tables (Evolution) +-- ===================================================== + +CREATE TABLE programs ( + id BIGSERIAL PRIMARY KEY, + code TEXT NOT NULL, + language VARCHAR(32) NOT NULL, + island VARCHAR(16) NOT NULL, + generation INTEGER NOT NULL DEFAULT 0, + parent_ids JSONB NOT NULL DEFAULT '[]', + behavior_vector DOUBLE PRECISION[] NOT NULL DEFAULT '{}', + fitness DOUBLE PRECISION NOT NULL DEFAULT 0.0, + promoted BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_programs_island ON programs(island); +CREATE INDEX idx_programs_island_fitness ON programs(island, fitness DESC); + +-- ===================================================== +-- Playlist Tables +-- ===================================================== + +CREATE TABLE playlists ( + slug VARCHAR(64) PRIMARY KEY, + title VARCHAR(128) NOT NULL, + description TEXT NOT NULL DEFAULT '', + category VARCHAR(32) NOT NULL DEFAULT 'featured', + is_auto BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE playlist_matches ( + playlist_slug VARCHAR(64) NOT NULL REFERENCES playlists(slug) ON DELETE CASCADE, + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + sort_order INTEGER NOT NULL DEFAULT 0, + curation_tag TEXT NOT NULL DEFAULT '', + added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (playlist_slug, match_id) +); +CREATE INDEX idx_playlist_matches_playlist ON playlist_matches(playlist_slug, sort_order); + +-- ===================================================== +-- Feedback Tables +-- ===================================================== + +CREATE TABLE replay_feedback ( + feedback_id VARCHAR(32) PRIMARY KEY, + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + turn INTEGER NOT NULL, + type VARCHAR(16) NOT NULL CHECK (type IN ('insight', 'mistake', 'idea', 'highlight')), + body TEXT NOT NULL, + author VARCHAR(128) NOT NULL, + upvotes INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +CREATE INDEX idx_feedback_match ON replay_feedback(match_id, turn); + +CREATE TABLE feedback_upvotes ( + feedback_id VARCHAR(32) NOT NULL REFERENCES replay_feedback(feedback_id) ON DELETE CASCADE, + voter_id VARCHAR(36) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (feedback_id, voter_id) +); + +-- ===================================================== +-- Enrichment Tables +-- ===================================================== + +CREATE TABLE enrichment_requests ( + request_id VARCHAR(32) PRIMARY KEY, + match_id VARCHAR(32) NOT NULL REFERENCES matches(match_id), + bot_id VARCHAR(16) NOT NULL REFERENCES bots(bot_id), + status VARCHAR(16) NOT NULL DEFAULT 'pending', + requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + processed_at TIMESTAMPTZ, + error_msg TEXT, + UNIQUE(match_id, bot_id) +); +CREATE INDEX idx_enrichment_requests_status ON enrichment_requests(status, requested_at); +CREATE INDEX idx_enrichment_requests_bot ON enrichment_requests(bot_id, requested_at);