Skip to content

Arena DB access layer and seed script - #138

Merged
seribaymadina merged 4 commits into
mainfrom
arena/02-db-and-seed
Jun 19, 2026
Merged

Arena DB access layer and seed script#138
seribaymadina merged 4 commits into
mainfrom
arena/02-db-and-seed

Conversation

@seribaymadina

@seribaymadina seribaymadina commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Layer 2 (+1) of the Voice Arena plan: the typed data-access layer for the arena schema, plus a local seed fixture. Stacks on feat/voice-arena.

  • arena/models.py — Pydantic Battle/Vote (mirrors db/models.py); outcome/voter_type as StrEnum matching the DB checks.
  • arena/store.py — async ArenaStore over the shared pool: insert_battle, upsert_vote (ON CONFLICT → updates the existing vote; the trigger bumps updated_at), get_battle, list_battles, list_votes.
  • arena/seed.pypython -m coval_bench.arena.seed: 4 models, 6 battles (all pairs = connected graph), 7 deterministic votes each. Skips if already seeded; --reset to reseed. Local dev only.
  • tests — embedded Postgres; covers vote dedup-as-update, the self-battle check, and domain/battle scoping.

Validated: ruff, ruff format, mypy --strict, 5/5 tests; seed verified against the local DB (6 battles, 42 votes).

No API/web/rating/audio changes.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced arena system for model-to-model comparisons with voting-based evaluation.
    • Implemented persistent storage for battles, votes, and voting outcome tracking.
  • Tests

    • Added comprehensive test coverage for arena battles and voting persistence layer.

Greptile Summary

Adds the typed data-access layer (ArenaStore) and Pydantic models (Battle, Vote, VoteOutcome, VoterType) for the voice arena schema, along with a local seed script and embedded-Postgres tests. All SQL is parameterised, transaction management is correct (explicit conn.commit() for writes, no-op for reads), and the seed script includes a conservative _assert_local guard that blocks execution against any non-loopback host.

  • arena_store.py exposes insert_battle, upsert_vote (ON CONFLICT → update), get_battle, list_battles, and list_votes; the list_votes unbounded-by-default behaviour is intentional and documented (rating refit needs all votes).
  • seed_arena.py seeds 4 models × 6 pairings × 7 deterministic votes and is idempotent (skip-if-seeded / --reset); the _clear helper deletes in FK-safe order (leaderboard_snapshots → votes → battles).
  • Five tests cover the full read/write round-trip, vote dedup-as-update, self-battle DB check constraint, domain filtering, and cross-battle vote scoping.

Confidence Score: 5/5

Safe to merge — the change is purely additive (new store, models, seed script, tests) with no modifications to existing production paths.

All SQL is parameterised with no string interpolation of caller data. Transaction management follows the existing writer.py convention and correctly commits writes. The models mirror the DB schema with optional DB-generated fields. Tests exercise every store method against a real embedded Postgres. The only observations are non-deterministic ORDER BY when timestamps collide, which is a minor hardening note and does not affect correctness today.

No files require special attention. arena_store.py is the core of the change and looks solid end-to-end.

Important Files Changed

Filename Overview
runner/src/coval_bench/db/arena_store.py New ArenaStore class with parameterized SQL for insert_battle, upsert_vote, get_battle, list_battles, list_votes; correct transaction management (explicit commit for writes, no commit for reads); consistent psycopg3 pool patterns.
runner/src/coval_bench/db/models.py Adds VoteOutcome, VoterType (StrEnum), Battle, and Vote Pydantic models; DB-generated fields (id, created_at, updated_at) correctly typed as Optional with None defaults.
runner/src/coval_bench/db/seed_arena.py Local-dev seed script with _assert_local guard (urlsplit hostname check against localhost/127.0.0.1/::1); correct FK-ordered _clear; idempotent skip-if-seeded logic; --reset flag.
runner/tests/unit/test_arena_store.py Five embedded-Postgres tests covering insert/read round-trip, vote dedup-as-update, self-battle rejection, domain filtering, and vote scoping; _async_dsn correctly uses quote_plus for both user and password.
runner/src/coval_bench/db/init.py Exports ArenaStore, Battle, Vote, VoteOutcome, VoterType alongside existing symbols; straightforward additive change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant ArenaStore
    participant Pool
    participant Postgres

    Note over Caller,Postgres: insert_battle
    Caller->>ArenaStore: insert_battle(battle)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: INSERT INTO arena.battles ... RETURNING ...
    Postgres-->>ArenaStore: row
    ArenaStore->>Postgres: COMMIT
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: Battle (with id, created_at)

    Note over Caller,Postgres: upsert_vote
    Caller->>ArenaStore: upsert_vote(battle_id, outcome, voter_type, voter_id)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: INSERT ... ON CONFLICT DO UPDATE ... RETURNING ...
    Note right of Postgres: BEFORE UPDATE trigger sets updated_at
    Postgres-->>ArenaStore: row
    ArenaStore->>Postgres: COMMIT
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: Vote (id, outcome, updated_at)

    Note over Caller,Postgres: list_votes (rating refit path)
    Caller->>ArenaStore: list_votes(battle_id?)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: SELECT ... FROM arena.votes [WHERE] ORDER BY created_at [LIMIT]
    Postgres-->>ArenaStore: rows[]
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: list[Vote]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant ArenaStore
    participant Pool
    participant Postgres

    Note over Caller,Postgres: insert_battle
    Caller->>ArenaStore: insert_battle(battle)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: INSERT INTO arena.battles ... RETURNING ...
    Postgres-->>ArenaStore: row
    ArenaStore->>Postgres: COMMIT
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: Battle (with id, created_at)

    Note over Caller,Postgres: upsert_vote
    Caller->>ArenaStore: upsert_vote(battle_id, outcome, voter_type, voter_id)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: INSERT ... ON CONFLICT DO UPDATE ... RETURNING ...
    Note right of Postgres: BEFORE UPDATE trigger sets updated_at
    Postgres-->>ArenaStore: row
    ArenaStore->>Postgres: COMMIT
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: Vote (id, outcome, updated_at)

    Note over Caller,Postgres: list_votes (rating refit path)
    Caller->>ArenaStore: list_votes(battle_id?)
    ArenaStore->>Pool: connection()
    Pool->>Postgres: acquire conn
    ArenaStore->>Postgres: SELECT ... FROM arena.votes [WHERE] ORDER BY created_at [LIMIT]
    Postgres-->>ArenaStore: rows[]
    Pool->>Postgres: return conn
    ArenaStore-->>Caller: list[Vote]
Loading

Reviews (5): Last reviewed commit: "Move arena DB layer into db/ and guard s..." | Re-trigger Greptile

@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
benchmarks Ready Ready Preview, Comment Jun 19, 2026 5:15pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds arena battle and vote persistence to the coval_bench.db package: new VoteOutcome, VoterType, Battle, and Vote Pydantic models; an ArenaStore async class with parameterized SQL for insert, upsert, and list operations; a dev-only seeding script with local-only safety guards; and a pytest-postgresql integration test suite.

Changes

Arena Battle/Vote Persistence

Layer / File(s) Summary
Battle and Vote domain models
runner/src/coval_bench/db/models.py, runner/src/coval_bench/db/__init__.py
Adds VoteOutcome and VoterType StrEnums and Battle/Vote Pydantic models with UUID PKs and typed timestamp fields. Expands __all__ in both models.py and the package __init__ to re-export ArenaStore, Battle, Vote, VoteOutcome, and VoterType.
ArenaStore async persistence class
runner/src/coval_bench/db/arena_store.py
Defines ArenaPool type alias and ArenaStore with insert_battle, upsert_vote (ON CONFLICT DO UPDATE), get_battle, list_battles (optional domain filter + limit), and list_votes (optional battle_id filter + optional limit) — all using dict-row async psycopg cursors with explicit commits.
Dev-only arena seeding script
runner/src/coval_bench/db/seed_arena.py
Adds a seeding CLI that enforces local-only DB access via hostname allowlist, supports --reset to truncate arena tables in FK-safe order, skips if battles already exist, inserts all i<j model pairings as battles, and upserts 7 deterministic votes per battle.
ArenaStore integration tests
runner/tests/unit/test_arena_store.py
Adds a pytest-postgresql suite with a dedicated embedded Postgres instance and Alembic migrations applied before each test: covers battle insert/read-back, upsert vote deduplication/timestamp ordering, self-battle check constraint rejection, list_battles domain filtering, and list_votes scoping and limit behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant ArenaStore
    participant PostgreSQL

    rect rgba(100, 149, 237, 0.5)
        Note over Caller,PostgreSQL: Write path
        Caller->>ArenaStore: insert_battle(battle)
        ArenaStore->>PostgreSQL: INSERT INTO arena.battles RETURNING *
        PostgreSQL-->>ArenaStore: DictRow
        ArenaStore->>PostgreSQL: COMMIT
        ArenaStore-->>Caller: Battle

        Caller->>ArenaStore: upsert_vote(battle_id, outcome, voter_type, voter_id)
        ArenaStore->>PostgreSQL: INSERT INTO arena.votes ON CONFLICT DO UPDATE RETURNING *
        PostgreSQL-->>ArenaStore: DictRow
        ArenaStore->>PostgreSQL: COMMIT
        ArenaStore-->>Caller: Vote
    end

    rect rgba(144, 238, 144, 0.5)
        Note over Caller,PostgreSQL: Read path
        Caller->>ArenaStore: list_battles(domain, limit)
        ArenaStore->>PostgreSQL: SELECT FROM arena.battles WHERE domain=? ORDER BY created_at LIMIT ?
        PostgreSQL-->>ArenaStore: DictRow[]
        ArenaStore-->>Caller: list[Battle]

        Caller->>ArenaStore: list_votes(battle_id, limit)
        ArenaStore->>PostgreSQL: SELECT FROM arena.votes WHERE battle_id=? LIMIT ?
        PostgreSQL-->>ArenaStore: DictRow[]
        ArenaStore-->>Caller: list[Vote]
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • coval-ai/benchmarks#119: Introduced the arena.battles and arena.votes schema migration (including the self-battle check constraint and ON CONFLICT unique index) that this PR's ArenaStore and tests directly target.

Suggested reviewers

  • coval-cale

Poem

🐇 Hop, hop, into the arena I go,
Where battles and votes are stored in a row!
With UUIDs bright and SQL so neat,
ON CONFLICT DO UPDATE — no duplicate feat!
The seeder only runs where localhost calls,
And tests keep the constraints safe within their walls! 🏆

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title "Arena DB access layer and seed script" directly and clearly summarizes the main changes: introduction of database access functionality and a seeding script for the arena schema.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch arena/02-db-and-seed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@seribaymadina
seribaymadina changed the base branch from feat/voice-arena to main June 17, 2026 23:55
Comment thread runner/src/coval_bench/db/arena_store.py
Comment thread runner/tests/unit/test_arena_store.py Outdated

@coval-cale coval-cale left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved but give some thought to the structure before merging. We should either match the existing structure or commit to the modular one.

I default to staying with the current structure.

Comment thread runner/src/coval_bench/db/seed_arena.py
Comment thread runner/src/coval_bench/arena/models.py Outdated
Comment thread runner/src/coval_bench/db/seed_arena.py
Typed Battle/Vote models and an async ArenaStore (insert battle, upsert vote,
fetch battles/votes) over the shared pool, plus a local seed of 4 models / 6
battles / 42 votes. Unit tests cover the vote dedup-as-update and the schema
checks.
Default None keeps the full result for the rating refit; an int bounds it for
paginated callers.
Snapshots are derived rows with no FK to battles/votes, so a reset left stale
rankings behind.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@runner/tests/unit/test_arena_store.py`:
- Around line 63-75: The _make_pool function currently returns
AsyncConnectionPool with type-ignore comments, but it should return the
properly-typed ArenaPool to fix type-checking failures at all ArenaStore
instantiation calls. Import ArenaPool and cast from typing, update the return
type annotation of _make_pool from AsyncConnectionPool to ArenaPool, remove the
type-ignore comments from the function signature and AsyncConnectionPool
instantiation, and use cast(ArenaPool, pool) in the return statement to properly
cast the pool instance before returning it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2da17f2e-67b4-4389-a65b-d3e4f56f3483

📥 Commits

Reviewing files that changed from the base of the PR and between 53470c7 and 10f619f.

📒 Files selected for processing (5)
  • runner/src/coval_bench/db/__init__.py
  • runner/src/coval_bench/db/arena_store.py
  • runner/src/coval_bench/db/models.py
  • runner/src/coval_bench/db/seed_arena.py
  • runner/tests/unit/test_arena_store.py

Comment thread runner/tests/unit/test_arena_store.py Outdated
Fold the arena models and store into the existing db/ package to match the
current persistence pattern, and refuse to seed/reset unless the DB host is
local. Also URL-encode credentials in the test DSN helper.
@seribaymadina
seribaymadina force-pushed the arena/02-db-and-seed branch from 10f619f to aebf47d Compare June 19, 2026 17:14
@seribaymadina
seribaymadina added this pull request to the merge queue Jun 19, 2026
Merged via the queue into main with commit 60a8f9b Jun 19, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants