A raw campaign DELETE works: quest_objectives' FK is deferred (#943) #957

Merged
claude-bot merged 2 commits from fix/943-quest-objectives-fk into main 2026-09-30 07:02:03 +00:00
Collaborator

Closes #943.

Diagnosis confirmed: reproduced on main. A raw DELETE FROM campaigns fails when an objective has a done_session_id. The row is reached by two paths in one statement:

  • CASCADE, via quest_objectives.lore_entry_id → quest_details.lore_entry_id;
  • SET NULL, via sessions.

The SET NULL update then references a quest_details row that is already being deleted.

Fix: migration a9430d1e2f3b (down_revision 10e5f6a7b8c9, the single head) recreates quest_objectives_lore_entry_id_fkey with the same column, target and ON DELETE CASCADE, now DEFERRABLE INITIALLY DEFERRED. Downgrade restores it as not deferrable. The model's ForeignKey matches. Deferring that one constraint was enough.

The seed's workaround is gone: _delete_lane8_first was removed from demo/engine.py, and delete_demo's single DELETE FROM campaigns now covers everything, the GM copy (#954) included.

Tests:

  • test_quest_objectives_fk_migration.py (new):
    • the head pin, moved here;
    • the raw-delete repro, with SET CONSTRAINTS ALL IMMEDIATE because the test rolls back. It fails on main.
    • upgrade/downgrade/upgrade in a scratch schema, checking condeferrable and condeferred.
  • test_test_usage_migration.py's head pin is loosened to "the revision exists".
  • The delegate's run: every module matching quest_objectives|QuestObjective|delete_demo|_migration, plus the seed, passed (1071).
  • Re-run after merging main with #954: test_demo_seed, the new file and the usage-migration test pass (21).
  • Real Alembic: upgrade head, downgrade -1, then upgrade head, with pg_constraint showing it deferrable and deferred.
  • Lint and changelog: ruff check and format are clean, and changelog.py check passes.

Changelog: changelog.d/943-quest-objectives-fk.fixed.md.

🤖 Generated with Claude Code

Closes #943. **Diagnosis confirmed:** reproduced on main. A raw `DELETE FROM campaigns` fails when an objective has a `done_session_id`. The row is reached by two paths in one statement: - CASCADE, via `quest_objectives.lore_entry_id → quest_details.lore_entry_id`; - SET NULL, via `sessions`. The SET NULL update then references a `quest_details` row that is already being deleted. **Fix:** migration `a9430d1e2f3b` (down_revision `10e5f6a7b8c9`, the single head) recreates `quest_objectives_lore_entry_id_fkey` with the same column, target and `ON DELETE CASCADE`, now **`DEFERRABLE INITIALLY DEFERRED`**. Downgrade restores it as not deferrable. The model's `ForeignKey` matches. Deferring that one constraint was enough. **The seed's workaround is gone:** `_delete_lane8_first` was removed from `demo/engine.py`, and `delete_demo`'s single `DELETE FROM campaigns` now covers everything, the GM copy (#954) included. **Tests:** - `test_quest_objectives_fk_migration.py` (new): - the head pin, moved here; - the raw-delete repro, with `SET CONSTRAINTS ALL IMMEDIATE` because the test rolls back. It fails on main. - `upgrade`/`downgrade`/`upgrade` in a scratch schema, checking `condeferrable` and `condeferred`. - `test_test_usage_migration.py`'s head pin is loosened to "the revision exists". - **The delegate's run:** every module matching `quest_objectives|QuestObjective|delete_demo|_migration`, plus the seed, passed (1071). - **Re-run after merging main with #954:** `test_demo_seed`, the new file and the usage-migration test pass (21). - **Real Alembic:** upgrade head, downgrade -1, then upgrade head, with `pg_constraint` showing it deferrable and deferred. - **Lint and changelog:** ruff check and format are clean, and `changelog.py check` passes. Changelog: `changelog.d/943-quest-objectives-fk.fixed.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/943-quest-objectives-fk
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 39s
CI / Docker image build (pull_request) Successful in 20s
CI / Bot/backend version sync (pull_request) Successful in 25s
CI / Does this change touch the recording path? (pull_request) Successful in 27s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 45s
CI / Bot tests and audit (pull_request) Successful in 2m0s
CI / Frontend (shard 2/2) (pull_request) Successful in 6m6s
CI / Frontend (shard 1/2) (pull_request) Successful in 8m17s
CI / End-to-end journeys (desktop) (pull_request) Successful in 8m54s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 8m31s
CI / End-to-end journeys (phone) (pull_request) Successful in 7m1s
CI / Backend migration, tests, and audit (pull_request) Successful in 17m14s
d1ff37eb3d
Sign in to join this conversation.
No description provided.