A GM cannot read their own AI settings while per-campaign AI is switched off (IS-314) #777

Closed
opened 2026-09-23 10:19:00 +00:00 by claude-bot · 2 comments
Collaborator

Found building CAMP-13 (#776, lane 3).

GET /api/campaigns/{id}/ai-settings is 403'd by the very instance switch D25 is about. So while per-campaign AI is off, a GM's Settings page cannot read this campaign's stored summariser and transcriber — or even whether it has any.

IS-314 draws three states, and the first two need exactly that:

  1. never set up here → no panel at all
  2. configured, then switched off → the reassurance, naming what is kept
  3. switched back on → the settings return

States 1 and 2 are indistinguishable from the client, so #776 draws them as one panel worded to be true of both: "Anything set here is kept — the address, the model and the key are untouched, and they apply again the moment that switch goes back on." D25's substance is intact, but the drawing's kv list of the GM's actual endpoints is not built, because the only honest source for it answers 403.

The alternative — caching the values in localStorage and claiming them back — was considered and rejected. A stale claim about somebody's stored API key is a worse failure than a sentence that covers two cases.

The change, if we want it

In webapp/backend/app/routers/campaigns.py, let get_campaign_ai_settings succeed while the switch is off, and add overrides_allowed: bool to CampaignAiSettingsResponse. Writes stay 403.

It is already require_gm-gated and never returns a key, so it leaks nothing new.

But it is a policy call, not a mechanical one

The question is what a GM may read about their own campaign while a feature is switched off at the instance. Two defensible answers:

  • Yes — it is their configuration, they entered it, and hiding it makes "is it still there?" unanswerable. The page can then draw state 1 and state 2 differently, as drawn.
  • No — a switched-off feature should be wholly absent, and a read endpoint that answers while the feature is off is a surface that has to keep being reasoned about.

I lean to the first: the reassurance is only reassuring if it can name what it is reassuring you about, and the endpoint already refuses to return the key itself. But this is the owner's to decide, and the page is correct and honest either way in the meantime.

Not urgent — CAMP-13 ships without it.

🤖 Generated with Claude Code

Found building CAMP-13 (#776, lane 3). `GET /api/campaigns/{id}/ai-settings` is **403'd by the very instance switch D25 is about**. So while per-campaign AI is off, a GM's Settings page cannot read this campaign's stored summariser and transcriber — or even *whether it has any*. `IS-314` draws three states, and the first two need exactly that: 1. **never set up here** → no panel at all 2. **configured, then switched off** → the reassurance, naming what is kept 3. switched back on → the settings return States 1 and 2 are indistinguishable from the client, so #776 draws them as **one** panel worded to be true of both: *"Anything set here is kept — the address, the model and the key are untouched, and they apply again the moment that switch goes back on."* D25's substance is intact, but the drawing's `kv` list of the GM's actual endpoints is not built, because the only honest source for it answers 403. The alternative — caching the values in `localStorage` and claiming them back — was considered and rejected. A stale claim about somebody's stored API key is a worse failure than a sentence that covers two cases. ## The change, if we want it In `webapp/backend/app/routers/campaigns.py`, let `get_campaign_ai_settings` **succeed** while the switch is off, and add `overrides_allowed: bool` to `CampaignAiSettingsResponse`. Writes stay 403. It is already `require_gm`-gated and never returns a key, so it leaks nothing new. ## But it is a policy call, not a mechanical one The question is **what a GM may read about their own campaign while a feature is switched off at the instance**. Two defensible answers: - **Yes** — it is their configuration, they entered it, and hiding it makes "is it still there?" unanswerable. The page can then draw state 1 and state 2 differently, as drawn. - **No** — a switched-off feature should be wholly absent, and a read endpoint that answers while the feature is off is a surface that has to keep being reasoned about. I lean to the first: the reassurance is only reassuring if it can name what it is reassuring you about, and the endpoint already refuses to return the key itself. But this is the owner's to decide, and the page is correct and honest either way in the meantime. Not urgent — CAMP-13 ships without it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Owner, 2026-09-23: allow it. A GM may read their own campaign's AI settings while per-campaign AI is switched off at the instance.

So the change is as described: get_campaign_ai_settings succeeds while the switch is off, CampaignAiSettingsResponse gains overrides_allowed: bool, and writes stay 403. It is already require_gm-gated and never returns a key.

Then CAMP-13 can draw IS-314's first two states apart, as the drawing has them:

  • never set up here → no panel
  • configured, then switched off → the reassurance, naming the summariser and transcriber it is reassuring you about

The single combined panel in #776 is the honest version of those two while the read is refused; it comes out when this lands.

Sequenced after #776 so the two do not collide in CampaignSettingsRecording.jsx.

🤖 Generated with Claude Code

**Owner, 2026-09-23: allow it.** A GM may read their own campaign's AI settings while per-campaign AI is switched off at the instance. So the change is as described: `get_campaign_ai_settings` succeeds while the switch is off, `CampaignAiSettingsResponse` gains `overrides_allowed: bool`, and **writes stay 403**. It is already `require_gm`-gated and never returns a key. Then CAMP-13 can draw `IS-314`'s first two states apart, as the drawing has them: - **never set up here** → no panel - **configured, then switched off** → the reassurance, naming the summariser and transcriber it is reassuring you about The single combined panel in #776 is the honest version of those two while the read is refused; it comes out when this lands. Sequenced after #776 so the two do not collide in `CampaignSettingsRecording.jsx`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Done in #782, merged 2026-09-23.

  • GET /api/campaigns/{id}/ai-settings answers while the instance switch is off and carries overrides_allowed. PUT and DELETE still refuse while off, and a player is still refused either way. The one test that asserted the old 403 was replaced by one asserting both halves: the read answers, and the write still 403s.
  • No key can reach the response. _campaign_ai_response only ever says llm_api_key_set / asr_api_key_set, both booleans, and the tests check for the key string in both the body and the page's DOM.
  • On the page, IS-314's two switched-off states are now drawn apart. "Never set up here" shows no panel. "Set up, then switched off" shows the reassurance, naming the address and model it's keeping.

🤖 Generated with Claude Code

Done in #782, merged 2026-09-23. - `GET /api/campaigns/{id}/ai-settings` answers while the instance switch is off and carries `overrides_allowed`. `PUT` and `DELETE` still refuse while off, and a player is still refused either way. The one test that asserted the old 403 was replaced by one asserting both halves: the read answers, and the write still 403s. - No key can reach the response. `_campaign_ai_response` only ever says `llm_api_key_set` / `asr_api_key_set`, both booleans, and the tests check for the key string in both the body and the page's DOM. - On the page, `IS-314`'s two switched-off states are now drawn apart. "Never set up here" shows no panel. "Set up, then switched off" shows the reassurance, naming the address and model it's keeping. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#777
No description provided.