A GM may read their own campaign's AI settings while per-campaign AI is off (#777) #782

Merged
claude-bot merged 3 commits from build/777-ai-settings-read into main 2026-09-23 18:44:27 +00:00
Collaborator

Closes #777. Owner, 2026-09-23: allow it.

The change

GET /api/campaigns/{id}/ai-settings no longer answers 403 while the instance switch (D25) is off. It returns the stored settings with a new overrides_allowed: bool. Writes are unchanged — PUT and DELETE still refuse while the switch is off. require_gm is unchanged, so a player is still refused either way.

No key can reach the response. I read _campaign_ai_response rather than take it on trust: the only thing it ever says about a key is llm_api_key_set / asr_api_key_set, both booleans. The tests assert the key string is absent from the body, and from the page's DOM.

What it fixes on the page

IS-314 draws the AI panel's switched-off states apart, and while the read was refused the page could not tell them apart, so #776 shipped them as one sentence covering both. Now:

  • never set up here → no panel at all
  • set up, then switched off → the reassurance, naming the summariser's and transcriber's address and model it is keeping, and saying only that a key is stored
  • on → the editable panel, unchanged

A network failure keeps the old combined wording, since a failed request genuinely cannot tell the two apart.

The page also stops making a separate fetchInstanceSettings call just to learn the switch's state: overrides_allowed on the one response is the source of truth.

Tests

  • Backend test_campaign_ai_settings.py: 35 passed (+7). test_the_instance_toggle_gates_the_read_too asserted the old 403 and was wrong by decision; it is replaced by test_the_read_survives_the_instance_toggle_but_the_write_does_not, which asserts both halves — the read answers, and the PUT still 403s. Plus: stored overrides readable while off with no key in the body; overrides_allowed true when on; a player refused while off.
  • Full backend suite: 4010 passed. One failure, test_backup_client_version.py, reads docker-compose.yml from the repo root, which the backend-only bind mount does not contain; CI runs it with the full checkout.
  • ruff check and ruff format --check (0.4.4) clean.
  • Frontend: 1865 passed in 136 files, re-run by me on a quiet host after the delegate's run was cut short by Docker contention. eslint 0 errors, warning count unchanged.

🤖 Generated with Claude Code

Closes #777. **Owner, 2026-09-23: allow it.** ## The change `GET /api/campaigns/{id}/ai-settings` no longer answers 403 while the instance switch (D25) is off. It returns the stored settings with a new `overrides_allowed: bool`. **Writes are unchanged** — `PUT` and `DELETE` still refuse while the switch is off. `require_gm` is unchanged, so a player is still refused either way. **No key can reach the response.** I read `_campaign_ai_response` rather than take it on trust: the only thing it ever says about a key is `llm_api_key_set` / `asr_api_key_set`, both booleans. The tests assert the key string is absent from the body, and from the page's DOM. ## What it fixes on the page `IS-314` draws the AI panel's switched-off states apart, and while the read was refused the page could not tell them apart, so #776 shipped them as one sentence covering both. Now: - **never set up here** → no panel at all - **set up, then switched off** → the reassurance, **naming** the summariser's and transcriber's address and model it is keeping, and saying only that a key is stored - **on** → the editable panel, unchanged A network failure keeps the old combined wording, since a failed request genuinely cannot tell the two apart. The page also stops making a separate `fetchInstanceSettings` call just to learn the switch's state: `overrides_allowed` on the one response is the source of truth. ## Tests - Backend `test_campaign_ai_settings.py`: **35 passed** (+7). `test_the_instance_toggle_gates_the_read_too` asserted the old 403 and was wrong by decision; it is replaced by `test_the_read_survives_the_instance_toggle_but_the_write_does_not`, which asserts both halves — the read answers, **and the PUT still 403s**. Plus: stored overrides readable while off with no key in the body; `overrides_allowed` true when on; a player refused while off. - Full backend suite: 4010 passed. One failure, `test_backup_client_version.py`, reads `docker-compose.yml` from the repo root, which the backend-only bind mount does not contain; CI runs it with the full checkout. - `ruff check` and `ruff format --check` (0.4.4) clean. - Frontend: **1865 passed in 136 files**, re-run by me on a quiet host after the delegate's run was cut short by Docker contention. eslint 0 errors, warning count unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The instance switch previously gated the GET too, so a GM whose group
had configured per-campaign AI before an admin turned the feature off
lost visibility into what was stored. Per owner decision (2026-09-23),
reads now succeed regardless of the switch; writes stay refused.

Backend: GET /api/campaigns/{id}/ai-settings drops the
_require_campaign_ai_overrides_enabled guard and returns a new
overrides_allowed field so callers can tell "never set up" apart from
"set up, then switched off" without a 403. The PUT/DELETE guards are
unchanged.

Frontend: CampaignSettingsRecording reads overrides_allowed from the
GET response instead of a separate instance-settings fetch inferred
from a 403 (IS-314). Off + nothing stored shows no panel; off + stored
names the summariser's and transcriber's address and model (never a
key's value, only whether one is set); on shows the normal editable
form. A network error falls back to the old merged wording, since
there is no answer to distinguish the two off-states from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merge build/lane3-settings-b into build/777-ai-settings-read (#777)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m26s
CI / Backend lint (ruff) (pull_request) Successful in 38s
CI / Bot/backend version sync (pull_request) Successful in 39s
CI / Feedback site tests (pull_request) Successful in 1m20s
CI / Docker image build (pull_request) Successful in 53s
CI / Bot tests and audit (pull_request) Successful in 3m14s
CI / Frontend tests, audit, and build (pull_request) Successful in 6m35s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 6m29s
CI / Backend migration, tests, and audit (pull_request) Successful in 22m40s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 28m49s
1b1659cad5
Picks up 23c82b4, the Contents test's race fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/777-ai-settings-read
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 21s
CI / Backend lint (ruff) (pull_request) Successful in 1m32s
CI / Feedback site tests (pull_request) Successful in 2m30s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 3m23s
CI / Docker image build (pull_request) Successful in 3m59s
CI / Frontend tests, audit, and build (pull_request) Successful in 4m57s
CI / Bot tests and audit (pull_request) Successful in 5m0s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 4m48s
CI / Backend migration, tests, and audit (pull_request) Successful in 10m57s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 12m59s
e6c41f175a
Sign in to join this conversation.
No description provided.