feat: bot↔backend version handshake + CI contract test (#93) #165

Merged
claude-bot merged 1 commit from feat/93-version-handshake into main 2026-07-15 23:25:58 +00:00
Contributor

Summary

The bot↔backend API contract was kept in sync purely by discipline — nothing verified at runtime or in CI that the two components actually match. A self-hoster upgrading one image but not the other got undefined behaviour (404s, silently missing fields) with no startup signal. This adds a version handshake and contract tests. Cross-component change, both sides in one PR.

Changes

Backend

  • New webapp/backend/app/version.py — single source of truth: APP_VERSION = "3.4.0", BOT_CONTRACT_VERSION = 1, with the bump-on-breaking-change contract documented.
  • main.py now uses APP_VERSION (was a stale hardcoded "0.10.0").
  • New GET /api/bot/meta (guarded by require_bot_auth) → {"app_version", "bot_contract_version"}.

Bot

  • api_client.py: BotMeta model + get_meta(), plus module-level BOT_CONTRACT_VERSION / BOT_EXPECTED_APP_VERSION.
  • main.py: run_version_handshake() runs once in setup_hook before cogs load:
    • contract match → proceed (info); app_version differs but contract matches → prominent warning, proceed (never fatal);
    • contract mismatch → error naming both versions + SystemExit(1) (refuses to start);
    • 404 (backend predates the endpoint) → warn, proceed; network/other error → warn, proceed. Only a genuine contract mismatch aborts startup.

Docs: contract-version bump rule documented in root CLAUDE.md and bot/CLAUDE.md.

Tests (contract, no live services)

  • webapp/backend/tests/test_bot_meta.py — shape + bot_contract_version == 1.
  • bot/tests/test_version_handshake.py — all five branches incl. SystemExit code 1 on mismatch, + a constant-value guard.
  • bot/tests/test_api_client.pyget_meta success/error.

Verification

Full suites green on the test DB: backend 362 passed, bot 167 passed. Backend changes are ruff-clean.

Note: BOT_EXPECTED_APP_VERSION is a second place carrying the version string, but it only ever drives a non-fatal warning — drift there is low-risk by design.

Closes #93

🤖 Generated with Claude Code

## Summary The bot↔backend API contract was kept in sync purely by discipline — nothing verified at runtime or in CI that the two components actually match. A self-hoster upgrading one image but not the other got undefined behaviour (404s, silently missing fields) with no startup signal. This adds a version handshake and contract tests. Cross-component change, both sides in one PR. ## Changes **Backend** - New `webapp/backend/app/version.py` — single source of truth: `APP_VERSION = "3.4.0"`, `BOT_CONTRACT_VERSION = 1`, with the bump-on-breaking-change contract documented. - `main.py` now uses `APP_VERSION` (was a stale hardcoded `"0.10.0"`). - New `GET /api/bot/meta` (guarded by `require_bot_auth`) → `{"app_version", "bot_contract_version"}`. **Bot** - `api_client.py`: `BotMeta` model + `get_meta()`, plus module-level `BOT_CONTRACT_VERSION` / `BOT_EXPECTED_APP_VERSION`. - `main.py`: `run_version_handshake()` runs once in `setup_hook` before cogs load: - contract match → proceed (info); app_version differs but contract matches → prominent warning, proceed (never fatal); - **contract mismatch → error naming both versions + `SystemExit(1)`** (refuses to start); - 404 (backend predates the endpoint) → warn, proceed; network/other error → warn, proceed. Only a genuine contract mismatch aborts startup. **Docs**: contract-version bump rule documented in root `CLAUDE.md` and `bot/CLAUDE.md`. ## Tests (contract, no live services) - `webapp/backend/tests/test_bot_meta.py` — shape + `bot_contract_version == 1`. - `bot/tests/test_version_handshake.py` — all five branches incl. `SystemExit` code 1 on mismatch, + a constant-value guard. - `bot/tests/test_api_client.py` — `get_meta` success/error. ## Verification Full suites green on the test DB: **backend 362 passed, bot 167 passed**. Backend changes are ruff-clean. Note: `BOT_EXPECTED_APP_VERSION` is a second place carrying the version string, but it only ever drives a non-fatal warning — drift there is low-risk by design. Closes #93 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(bot): add bot↔backend version handshake and contract test
All checks were successful
CI / Frontend tests, audit, and build (pull_request) Successful in 1m9s
CI / Backend lint (ruff) (pull_request) Successful in 33s
CI / Bot tests and audit (pull_request) Successful in 1m53s
CI / Backend migration, tests, and audit (pull_request) Successful in 3m3s
CI / Docker image build (pull_request) Successful in 2m20s
4431269ed1
Nothing verified that the bot and backend were compatible versions, so a
self-hoster upgrading one image but not the other got undefined behaviour.

Backend:
- New GET /api/bot/meta (require_bot_auth) returning app_version and
  bot_contract_version.
- New app/version.py as the single source of truth: APP_VERSION = "3.4.0",
  BOT_CONTRACT_VERSION = 1. main.py now uses APP_VERSION for the OpenAPI
  version instead of the hardcoded "0.10.0".

Bot:
- api_client gains a typed get_meta() -> BotMeta plus module-level
  BOT_CONTRACT_VERSION and BOT_EXPECTED_APP_VERSION constants.
- main.py runs run_version_handshake() once in setup_hook: contract match
  proceeds (info); differing backend app_version with matching contract logs
  a warning and proceeds; contract mismatch logs a clear error and exits
  non-zero; a 404 (older backend) or an unreachable backend logs a warning
  and proceeds.

Docs: document the bump-both-sides contract rule in root and bot CLAUDE.md.

Tests: backend asserts /api/bot/meta shape and constant; bot exercises all
handshake branches and pins each side's BOT_CONTRACT_VERSION == 1 so a
one-sided bump is caught in review.

Closes #93

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-07-15 23:23:21 +00:00
claude-bot deleted branch feat/93-version-handshake 2026-07-15 23:25:59 +00:00
Sign in to join this conversation.
No description provided.