[Backend] Surface the four invisible post-session tasks, including the party-wide recap email #380

Closed
opened 2026-08-25 20:42:19 +00:00 by claude-bot · 2 comments
Contributor

Impact: HIGH

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

What the user experiences

One summary event fans out six post-session tasks. Two of them — audio processing and lore-proposal generation — have real status fields and are shown to the GM. The other four have no status field and no UI trace whatsoever: the campaign-storyline rebuild, the journal-entry generation, the session-title-suggestion generation, and — most notably — an email sent to the entire party. If any of these four silently fails, or simply hasn't run yet, nothing in the product tells anyone.

Evidence

  • webapp/backend/app/services/summary_events.py:70-80 fans out all six tasks from one summary event.
  • webapp/backend/app/schemas/session.py:161-170 exposes only audio_processing_status/_error and lore_generation_status/_error — the other four tasks have nothing.
  • webapp/frontend/src/pages/SessionDetail.jsx:100-106,92-98 show the two tasks that do have status fields with clear human labels; nothing on the page acknowledges the other four exist.
  • webapp/frontend/src/components/TitleSuggestions.jsx:49 — the title-suggestion fetch failure is separately swallowed (see the silent-failures issue in this milestone), compounding the fact that there is no status field to check in the first place.

Why it matters for a hosted product

A party-wide email fires today with zero UI trace anywhere in the product — nobody can tell it happened, is happening, or failed to happen, from either the web app or Discord.

Proposed fix

Add status columns for the storyline rebuild, journal entry, title suggestions, and recap email to the session schema (webapp/backend/app/schemas/session.py:161-170), and render them as a single "Post-session processing" checklist on SessionDetail. This is the audit's P9.

Acceptance criteria

  • The session schema exposes a status field for each of the four currently-invisible post-session tasks.
  • SessionDetail renders a "Post-session processing" checklist showing the state of all six post-session tasks, not just the two that exist today.
  • The recap-email task's status (queued/sent/failed) is visible somewhere in the UI.
  • A failure in any of the four tasks is distinguishable from "hasn't run yet" in the UI.
**Impact: HIGH** Found in the August 2026 session lifecycle review (#319). ## What the user experiences One summary event fans out six post-session tasks. Two of them — audio processing and lore-proposal generation — have real status fields and are shown to the GM. The other four have no status field and no UI trace whatsoever: the campaign-storyline rebuild, the journal-entry generation, the session-title-suggestion generation, and — most notably — an email sent to the entire party. If any of these four silently fails, or simply hasn't run yet, nothing in the product tells anyone. ## Evidence - `webapp/backend/app/services/summary_events.py:70-80` fans out all six tasks from one summary event. - `webapp/backend/app/schemas/session.py:161-170` exposes only `audio_processing_status/_error` and `lore_generation_status/_error` — the other four tasks have nothing. - `webapp/frontend/src/pages/SessionDetail.jsx:100-106,92-98` show the two tasks that do have status fields with clear human labels; nothing on the page acknowledges the other four exist. - `webapp/frontend/src/components/TitleSuggestions.jsx:49` — the title-suggestion fetch failure is separately swallowed (see the silent-failures issue in this milestone), compounding the fact that there is no status field to check in the first place. ## Why it matters for a hosted product A party-wide email fires today with zero UI trace anywhere in the product — nobody can tell it happened, is happening, or failed to happen, from either the web app or Discord. ## Proposed fix Add status columns for the storyline rebuild, journal entry, title suggestions, and recap email to the session schema (`webapp/backend/app/schemas/session.py:161-170`), and render them as a single "Post-session processing" checklist on `SessionDetail`. This is the audit's P9. ## Acceptance criteria - [ ] The session schema exposes a status field for each of the four currently-invisible post-session tasks. - [ ] `SessionDetail` renders a "Post-session processing" checklist showing the state of all six post-session tasks, not just the two that exist today. - [ ] The recap-email task's status (queued/sent/failed) is visible somewhere in the UI. - [ ] A failure in any of the four tasks is distinguishable from "hasn't run yet" in the UI.
Author
Contributor

Picking this up as v4.3.0 phase 4 (#514). Decision: one nullable JSONB column post_session_tasks on sessions (a single migration) holding {task: {status, error, at}} for the storyline rebuild, journal entry, title suggestions and recap email, written at fan-out and at each task's start, finish and failure; exposed on the session response; a "Post-session processing" checklist on the session page covering all six tasks, with failed and not-yet-run visibly different and the recap email reading queued / sent / failed.

Picking this up as v4.3.0 phase 4 (#514). Decision: one nullable JSONB column `post_session_tasks` on sessions (a single migration) holding `{task: {status, error, at}}` for the storyline rebuild, journal entry, title suggestions and recap email, written at fan-out and at each task's start, finish and failure; exposed on the session response; a "Post-session processing" checklist on the session page covering all six tasks, with failed and not-yet-run visibly different and the recap email reading queued / sent / failed.
Author
Contributor

Done in PR #526 (auto-merging on green); ships with v4.3.0.

sessions.post_session_tasks (JSONB, nullable, migration 0b1c2d3e4f5a) carries {task: {status, error, at}} for the four tasks that reported nowhere: the storyline rebuild, the journal entry, the next-session title suggestions and the recap email. The other two of the six keep their own columns and are not duplicated, so each fact has one source.

Every write goes through app/services/post_session_tasks.py as a single COALESCE(post_session_tasks,'{}') || :patch statement. That is load-bearing rather than stylistic: the four tasks run concurrently in separate workers, and a read-modify-write from four processes loses updates, the one it loses being somebody's failure. mark() never raises, since bookkeeping must not be able to fail the work it describes.

Queued marks are written at fan-out from both call sites, driven off a new summary_events.fanout_task_names() so the fan-out list and the status map cannot drift (there is a test for exactly that). A refused enqueue is recorded failed, not queued, the #413 failure mode, and worse here because there is no retry button. On a summary edit the recap-email key is left alone rather than reset to "queued" for a send that will never happen.

Absent is not failed, throughout: a NULL column (fan-out predating the release) and a missing key both read as "no record", and skipped (journal entries off, campaign archived, storyline hand-edited) is a distinct state from failed, with its reason attached. In the two llm_task-decorated bodies the skip reason is written on the way past and the decorator's sentinel re-raised, so the SKIP policy still runs.

generate_session_title_suggestions gained an optional session_id: it generates for a campaign, but a GM looks for "did this run?" on the session whose summary set it off. The manual regenerate path omits it and records nothing rather than stamping an unrelated session.

UI: PostSessionChecklist.jsx on the session page, all six rows, renders nothing until something has been recorded. The recap row says Queued / Sent / Failed. Failure reasons are GM-only (they can carry an SMTP message or an exception name); the status word is shown to everyone. Tests: 19 in test_post_session_tasks.py, 3 in test_recap_email.py against the real SMTP harness, 11 component tests.

Done in PR #526 (auto-merging on green); ships with v4.3.0. `sessions.post_session_tasks` (JSONB, nullable, migration `0b1c2d3e4f5a`) carries `{task: {status, error, at}}` for the four tasks that reported nowhere: the storyline rebuild, the journal entry, the next-session title suggestions and the recap email. The other two of the six keep their own columns and are not duplicated, so each fact has one source. Every write goes through `app/services/post_session_tasks.py` as a single `COALESCE(post_session_tasks,'{}') || :patch` statement. That is load-bearing rather than stylistic: the four tasks run concurrently in separate workers, and a read-modify-write from four processes loses updates, the one it loses being somebody's failure. `mark()` never raises, since bookkeeping must not be able to fail the work it describes. Queued marks are written at fan-out from both call sites, driven off a new `summary_events.fanout_task_names()` so the fan-out list and the status map cannot drift (there is a test for exactly that). A refused enqueue is recorded `failed`, not `queued`, the #413 failure mode, and worse here because there is no retry button. On a summary *edit* the recap-email key is left alone rather than reset to "queued" for a send that will never happen. Absent is not failed, throughout: a NULL column (fan-out predating the release) and a missing key both read as "no record", and `skipped` (journal entries off, campaign archived, storyline hand-edited) is a distinct state from `failed`, with its reason attached. In the two `llm_task`-decorated bodies the skip reason is written on the way past and the decorator's sentinel re-raised, so the SKIP policy still runs. `generate_session_title_suggestions` gained an optional `session_id`: it generates *for* a campaign, but a GM looks for "did this run?" on the session whose summary set it off. The manual regenerate path omits it and records nothing rather than stamping an unrelated session. UI: `PostSessionChecklist.jsx` on the session page, all six rows, renders nothing until something has been recorded. The recap row says Queued / Sent / Failed. Failure *reasons* are GM-only (they can carry an SMTP message or an exception name); the status word is shown to everyone. Tests: 19 in `test_post_session_tasks.py`, 3 in `test_recap_email.py` against the real SMTP harness, 11 component tests.
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#380
No description provided.