Skip to content

fix: enforce prediction write guards atomically at the database layer#130

Merged
adrunkhuman merged 1 commit intomasterfrom
fix/atomic-prediction-writes
Mar 21, 2026
Merged

fix: enforce prediction write guards atomically at the database layer#130
adrunkhuman merged 1 commit intomasterfrom
fix/atomic-prediction-writes

Conversation

@adrunkhuman
Copy link
Owner

Summary

  • Adds SaveResult enum and two new DB methods (try_save_prediction, save_prediction_guarded) that perform fixture-open validation and duplicate checks inside a single BEGIN IMMEDIATE transaction — making both guards atomic
  • Thread handler now delegates entirely to try_save_prediction (first-write-wins); removes the app-level get_prediction + conditional that left a race window
  • DM handler now calls save_prediction_guarded (upsert, re-submissions still work) and handles FIXTURE_CLOSED gracefully
  • Also fixes user_name staleness on upsert: DO UPDATE SET was missing user_name = excluded.user_name in both save_prediction and save_prediction_guarded

Closes #120
Closes #122

Test plan

  • uv run pytest tests/test_database.py -k "TrySave or SaveGuarded" -v — new DB unit tests (fixture-open guard, first-write-wins, duplicate, both-conditions ordering)
  • uv run pytest tests/test_thread_prediction_handler.py tests/test_dm_prediction_handler.py -v — handler tests including new fixture-closed paths and updated monkeypatch targets
  • uv run pytest — full suite (284 pass, 1 skip)
  • uv run ruff check typer_bot/ tests/ — clean

🤖 Generated with Claude Code

Fixes two race conditions in the prediction write path:

- #120: ThreadPredictionHandler's first-write-wins rule was enforced at
  the app layer (get_prediction check before save), leaving a window
  where a concurrent DM save could land and be overwritten by the
  subsequent thread write.

- #122: Neither handler revalidated fixture state at write time, so a
  results calculation closing the fixture between the handler's read and
  the actual INSERT could silently succeed.

Both are fixed by moving the invariants into the DB layer inside a
single BEGIN IMMEDIATE transaction:

- try_save_prediction(): thread path — fixture-open check + INSERT OR
  IGNORE (first-write-wins). Returns SaveResult.FIXTURE_CLOSED or
  SaveResult.DUPLICATE instead of writing.
- save_prediction_guarded(): DM path — fixture-open check + upsert
  (re-submissions intentionally allowed). Returns FIXTURE_CLOSED only.

Also fixes user_name staleness on upsert in both save_prediction and
save_prediction_guarded (DO UPDATE SET was missing user_name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to patient-quietude / matchday-typer-pr-130 March 21, 2026 20:10 Destroyed
@railway-app
Copy link

railway-app bot commented Mar 21, 2026

🚅 Deployed to the matchday-typer-pr-130 environment in patient-quietude

Service Status Web Updated (UTC)
matchday-typer ✅ Success (View Logs) Mar 21, 2026 at 8:11 pm

@adrunkhuman adrunkhuman merged commit 018770f into master Mar 21, 2026
2 checks passed
@adrunkhuman adrunkhuman deleted the fix/atomic-prediction-writes branch March 21, 2026 20:13
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.

fix: revalidate fixture state atomically when saving predictions fix: enforce first-write-wins atomically across DM and thread predictions

1 participant