feat(backend): GameSystem registry + campaign linkage (#133) #205

Merged
claude-bot merged 1 commit from feat/133-gamesystem-registry-backend into feat/v3.9-game-aware-systems 2026-07-18 16:28:57 +00:00
Contributor

Foundational backend issue for the v3.9.0 Game-Aware Systems pillar. Targets the integration branch feat/v3.9-game-aware-systems.

Closes #133.

What

A first-class GameSystem registry that campaigns can optionally link to, coexisting with the existing free-text game_system column (never overwritten — preserved for rollback/flavour).

  • New game_systems table seeded with dnd5e, pf2e, and a system-agnostic generic at fixed UUIDs (migration b5c6d7e8f9a0, chains off head a3b4c5d6e7f8).
  • campaigns.game_system_id nullable FK, ON DELETE SET NULL.
  • Resolution helper (services/game_system_service.py): effective display name = linked system's short_name if linked, else the free text. Also validation + an idempotent builtin self-heal (ensure_builtin_systems) because the test harness builds schema via create_all, not migrations.
  • GET /api/game-systems — authenticated registry list (for #135's selector).
  • Schemas: CampaignCreate/Update accept game_system_id (validated against active systems → 400 on unknown/inactive); CampaignResponse/Summary gain game_system_ref and return the resolved effective name. Campaign responses are now built via to_campaign_response() with game_system_ref eager-loaded (avoids async lazy-load).
  • Bot (SessionTimeslotsResponse, NextSessionResponse): send the resolved name; field shape unchanged, BOT_CONTRACT_VERSION stays 1, bot package untouched.

Verification (Docker, pinned py3.12)

  • 594 backend tests pass — full suite, no regressions from the shared response-builder refactor.
  • New test_game_systems.py + additions to test_campaigns.py/test_bot.py cover: registry endpoint, link on create, resolved response + game_system_ref, invalid/inactive id → 400, free-text campaign unchanged, unlink restores free text, bot endpoints resolve correctly.
  • Migration verified against a real Postgres 16: full chain upgrade headdowngrade -1upgrade head all clean; seeds present; game_system_id confirmed as uuid FK with ON DELETE SET NULL.

Scope discipline

No UI, no LLM prompt integration, no stat schemas — those are #135 / #137 / #139, which build on this. Existing free-text campaigns (game_system_id IS NULL) are behaviourally unchanged.

🤖 Generated with Claude Code

Foundational backend issue for the **v3.9.0 Game-Aware Systems** pillar. Targets the integration branch `feat/v3.9-game-aware-systems`. Closes #133. ## What A first-class `GameSystem` registry that campaigns can optionally link to, coexisting with the existing free-text `game_system` column (never overwritten — preserved for rollback/flavour). - **New `game_systems` table** seeded with `dnd5e`, `pf2e`, and a system-agnostic `generic` at fixed UUIDs (migration `b5c6d7e8f9a0`, chains off head `a3b4c5d6e7f8`). - **`campaigns.game_system_id`** nullable FK, `ON DELETE SET NULL`. - **Resolution helper** (`services/game_system_service.py`): effective display name = linked system's `short_name` if linked, else the free text. Also validation + an idempotent builtin self-heal (`ensure_builtin_systems`) because the test harness builds schema via `create_all`, not migrations. - **`GET /api/game-systems`** — authenticated registry list (for #135's selector). - **Schemas**: `CampaignCreate`/`Update` accept `game_system_id` (validated against active systems → 400 on unknown/inactive); `CampaignResponse`/`Summary` gain `game_system_ref` and return the *resolved* effective name. Campaign responses are now built via `to_campaign_response()` with `game_system_ref` eager-loaded (avoids async lazy-load). - **Bot** (`SessionTimeslotsResponse`, `NextSessionResponse`): send the resolved name; field shape unchanged, **`BOT_CONTRACT_VERSION` stays 1**, bot package untouched. ## Verification (Docker, pinned py3.12) - **594 backend tests pass** — full suite, no regressions from the shared response-builder refactor. - New `test_game_systems.py` + additions to `test_campaigns.py`/`test_bot.py` cover: registry endpoint, link on create, resolved response + `game_system_ref`, invalid/inactive id → 400, free-text campaign unchanged, unlink restores free text, bot endpoints resolve correctly. - **Migration verified against a real Postgres 16**: full chain `upgrade head` → `downgrade -1` → `upgrade head` all clean; seeds present; `game_system_id` confirmed as `uuid` FK with `ON DELETE SET NULL`. ## Scope discipline No UI, no LLM prompt integration, no stat schemas — those are #135 / #137 / #139, which build on this. Existing free-text campaigns (`game_system_id IS NULL`) are behaviourally unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Introduce a GameSystem registry (game_systems table, seeded with D&D 5e,
Pathfinder 2e, and a system-agnostic Generic at fixed UUIDs) and an optional
campaigns.game_system_id FK (ON DELETE SET NULL). A campaign can link to a
registry system without disturbing its existing free-text game_system column;
a resolution helper decides the effective display name (registry short_name
when linked, else the free text).

- New: models/game_system.py, services/game_system_service.py (resolution,
  validation, idempotent builtin self-heal for the create_all test harness),
  routers/game_systems.py (GET /api/game-systems), Alembic b5c6d7e8f9a0.
- CampaignCreate/Update accept game_system_id (validated against active
  systems; 400 on unknown/inactive). CampaignResponse/Summary gain
  game_system_ref and return the resolved effective name; responses now built
  via campaign_service.to_campaign_response() with game_system_ref eager-loaded.
- Bot payloads (SessionTimeslotsResponse, NextSessionResponse) send the
  resolved name; field shape unchanged, BOT_CONTRACT_VERSION stays 1.

Foundational issue for the v3.9.0 Game-Aware Systems pillar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot merged commit 8fc3701958 into feat/v3.9-game-aware-systems 2026-07-18 16:28:57 +00:00
Sign in to join this conversation.
No description provided.