[Backend] Stop the storyline auto-rebuild from overwriting GM manual edits #400

Closed
opened 2026-08-25 20:44:39 +00:00 by claude-bot · 0 comments
Contributor

Severity: HIGH

Found in the August 2026 session lifecycle review (#319).

A GM who spends time polishing the campaign storyline's prose loses that work the moment any session in the campaign is transcribed, re-transcribed, or has its summary touched — because the storyline body is unconditionally rebuilt from raw session summaries on every one of those events, with no distinction between "generated" and "GM-authored" content, and no history to recover from.

Evidence

  • webapp/backend/app/routers/campaigns.py:3330-3352 (patch_storyline) — PATCH /campaigns/{id}/storyline writes storyline.body = body.body (:3349) directly; this is explicitly documented as "GM manual edit of the storyline body" (:3336).
  • webapp/backend/app/tasks/reminder_tasks.py:4144-4171 (_update_campaign_storyline_async) — new_body is rebuilt from scratch by concatenating one auto-generated chapter per completed session (_chapter(), :4145-4147), and storyline.body = new_body (:4171) overwrites whatever was there — including a GM's hand-edit — every time this task runs. The full_regenerate flag (:4149-4159) makes no difference: both branches compute the identical rebuilt body, so there is no code path that preserves an existing body at all.
  • webapp/backend/app/services/summary_events.py:70update_campaign_storyline is one of the tasks fired unconditionally (not gated by first_time) from on_session_summary_available, which is called after every transcription, every GM summary edit, and every canonical-name pick, for any session in the campaign — not just the one the GM most recently touched.

Failure scenario
A GM spends an hour after session 12 turning six raw AI summaries into a cohesive, well-written campaign chronicle in the storyline editor. The following week, session 13 is transcribed. on_session_summary_available fires for session 13, which enqueues update_campaign_storyline — a task with no idea a hand-edit exists — and it silently rebuilds and overwrites the entire storyline body back to raw concatenated summaries. There is no history table for storylines, so the GM's authorship is gone with no way to diff or recover it, and it will happen again next session.

Proposed fix
Give CampaignStoryline a manually_edited_at (or a boolean is_manually_edited) flag set by patch_storyline. Have update_campaign_storyline check it: if set, append the new chapter's summary to a separate addendum field (or skip the automatic rebuild and notify the GM there's a new session to fold in) rather than clobbering body. A full-regenerate action should remain available but must be an explicit GM action (already exists as regenerate_storyline, campaigns.py:3358) — the automatic per-session rebuild should never silently overwrite manual prose. This is the same "guard the overwrite path" pattern needed for canonical-name summary regeneration (tracked separately); both belong to the same "never silently overwrite explicit GM authorship" principle.

Acceptance criteria

  • CampaignStoryline tracks whether its body has been manually edited by a GM.
  • update_campaign_storyline's automatic per-session trigger never overwrites a manually-edited body.
  • The GM has a clear, explicit way to fold new session content into a manually-edited storyline (append or a confirmed full regenerate) without an implicit silent overwrite.
  • Regression test: PATCH the storyline, then trigger a session transcription in the same campaign, and assert the manual edit survives.
**Severity: HIGH** Found in the August 2026 session lifecycle review (#319). A GM who spends time polishing the campaign storyline's prose loses that work the moment any session in the campaign is transcribed, re-transcribed, or has its summary touched — because the storyline body is unconditionally rebuilt from raw session summaries on every one of those events, with no distinction between "generated" and "GM-authored" content, and no history to recover from. **Evidence** - `webapp/backend/app/routers/campaigns.py:3330-3352` (`patch_storyline`) — `PATCH /campaigns/{id}/storyline` writes `storyline.body = body.body` (`:3349`) directly; this is explicitly documented as "GM manual edit of the storyline body" (`:3336`). - `webapp/backend/app/tasks/reminder_tasks.py:4144-4171` (`_update_campaign_storyline_async`) — `new_body` is rebuilt from scratch by concatenating one auto-generated chapter per completed session (`_chapter()`, `:4145-4147`), and `storyline.body = new_body` (`:4171`) overwrites whatever was there — including a GM's hand-edit — every time this task runs. The `full_regenerate` flag (`:4149-4159`) makes no difference: both branches compute the identical rebuilt body, so there is no code path that preserves an existing body at all. - `webapp/backend/app/services/summary_events.py:70` — `update_campaign_storyline` is one of the tasks fired unconditionally (not gated by `first_time`) from `on_session_summary_available`, which is called after every transcription, every GM summary edit, and every canonical-name pick, for any session in the campaign — not just the one the GM most recently touched. **Failure scenario** A GM spends an hour after session 12 turning six raw AI summaries into a cohesive, well-written campaign chronicle in the storyline editor. The following week, session 13 is transcribed. `on_session_summary_available` fires for session 13, which enqueues `update_campaign_storyline` — a task with no idea a hand-edit exists — and it silently rebuilds and overwrites the entire storyline body back to raw concatenated summaries. There is no history table for storylines, so the GM's authorship is gone with no way to diff or recover it, and it will happen again next session. **Proposed fix** Give `CampaignStoryline` a `manually_edited_at` (or a boolean `is_manually_edited`) flag set by `patch_storyline`. Have `update_campaign_storyline` check it: if set, append the new chapter's summary to a separate addendum field (or skip the automatic rebuild and notify the GM there's a new session to fold in) rather than clobbering `body`. A full-regenerate action should remain available but must be an explicit GM action (already exists as `regenerate_storyline`, `campaigns.py:3358`) — the automatic per-session rebuild should never silently overwrite manual prose. This is the same "guard the overwrite path" pattern needed for canonical-name summary regeneration (tracked separately); both belong to the same "never silently overwrite explicit GM authorship" principle. **Acceptance criteria** - [ ] `CampaignStoryline` tracks whether its body has been manually edited by a GM. - [ ] `update_campaign_storyline`'s automatic per-session trigger never overwrites a manually-edited body. - [ ] The GM has a clear, explicit way to fold new session content into a manually-edited storyline (append or a confirmed full regenerate) without an implicit silent overwrite. - [ ] Regression test: PATCH the storyline, then trigger a session transcription in the same campaign, and assert the manual edit survives.
Sign in to join this conversation.
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#400
No description provided.