feat(webapp): facts the session may have changed, suggested by the local model for the GM to use (#1280) #1325

Merged
claude-bot merged 11 commits from feat/1280-fact-suggestions into main 2026-11-03 16:27:20 +00:00
Collaborator

Closes #1280 (D323–D326; the drawing is lane 6j, also in this PR).

What it does

  • After a session is summarised, the worker asks the campaign's LLM one schema-constrained question per page that appeared: did its Status, Allegiances, Ruler, Leader or Owner change?
    • Status is Alive, Dead, Missing or Unknown, on NPC, PC, Creature and Deity pages. Allegiances is on NPC pages, Ruler on Settlement and Country, Leader on Organization, and Owner on Business.
    • The question covers only the transcript lines that mention the page, plus one line either side.
  • Every change must quote the transcript. The quote is matched after normalising, within one line or across two adjacent lines. The speaker and the time come from the matched line, never from the model. A change is dropped and counted when its quote isn't found, its value is unchanged, its fact isn't allowed for the type, or its Status isn't one of the four. Counts go on the run (summarisation_runs.fact_suggestions).
  • Suggestions are GMs-only until used. "Facts that may have changed" in Suggested wiki updates offers Use it, Change it…, Not this one, and a receipt with Undo. The page editor shows a waiting suggestion under its fact. A used fact keeps its own "Who sees it"; a new one is for everyone at the table (D324). It's written through the normal edit path, so history records it.
  • The queueing hook comes after summarisation, in a try, so it can't fail or slow that path. Without an LLM configured, a session is skipped.

Privacy (three security-review findings, all fixed with tests)

  • Retention: when a transcript is destroyed, its pending suggestions go, and decided ones lose their quote and speaker.
  • Trash: while a transcript is binned, no read shows its suggestions.
  • Erasure: each suggestion records the seq of its quoted line or lines, so erasing a member's lines removes the suggestions quoting them, however the scrub matched the speaker. A mixed erasure is tested.
  • Reprocess: replacing the lines renumbers them, so a reprocess forgets the session's suggestions first, and the next run suggests again.
  • Deleting a session, page or campaign cascades.

Server: migration f1280a0b1c2d (revises e1279a0b1c2d) adds fact_suggestions and summarisation_runs.fact_suggestions; it has a real downgrade, and Alembic has a single head. New routes: GET /api/campaigns/{id}/wiki/fact-suggestions, plus …/{sid}/use, /undo and /dismiss, all for GMs only. The article read (GM only) and the GM session read carry the waiting suggestions, and the attention count includes them. The bot is unaffected.

After the deploy (D325, on the owner's say): run this once over past sessions, newest first. It's safe to repeat.

celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill

Checked

  • Backend, on the dev host: every migration test, tests/wiki, tests/export, attention, transcript bin, member erasure, enqueue-off-loop, sessions, audio tasks and retention gave 3,056 passed and 0 failed. ruff format and check are clean; the version and changelog checks are OK.
  • Frontend: src/pages/wiki and src/wiki, 43 files and 625 tests passed. eslint has 0 errors.
  • Not yet: the full backend suite (CI) and the render check, which is starting.

New wording goes on the Wording Review page before merge.

🤖 Generated with Claude Code

Closes #1280 (D323–D326; the drawing is lane 6j, also in this PR). **What it does** - After a session is summarised, the worker asks the campaign's LLM one schema-constrained question per page that appeared: did its Status, Allegiances, Ruler, Leader or Owner change? - Status is Alive, Dead, Missing or Unknown, on NPC, PC, Creature and Deity pages. Allegiances is on NPC pages, Ruler on Settlement and Country, Leader on Organization, and Owner on Business. - The question covers only the transcript lines that mention the page, plus one line either side. - **Every change must quote the transcript.** The quote is matched after normalising, within one line or across two adjacent lines. The speaker and the time come from the matched line, never from the model. A change is dropped and counted when its quote isn't found, its value is unchanged, its fact isn't allowed for the type, or its Status isn't one of the four. Counts go on the run (`summarisation_runs.fact_suggestions`). - Suggestions are **GMs-only until used.** "Facts that may have changed" in Suggested wiki updates offers Use it, Change it…, Not this one, and a receipt with Undo. The page editor shows a waiting suggestion under its fact. A used fact keeps its own "Who sees it"; a new one is for everyone at the table (D324). It's written through the normal edit path, so history records it. - The queueing hook comes after summarisation, in a `try`, so it can't fail or slow that path. Without an LLM configured, a session is skipped. **Privacy (three security-review findings, all fixed with tests)** - **Retention:** when a transcript is destroyed, its pending suggestions go, and decided ones lose their quote and speaker. - **Trash:** while a transcript is binned, no read shows its suggestions. - **Erasure:** each suggestion records the `seq` of its quoted line or lines, so erasing a member's lines removes the suggestions quoting them, however the scrub matched the speaker. A mixed erasure is tested. - **Reprocess:** replacing the lines renumbers them, so a reprocess forgets the session's suggestions first, and the next run suggests again. - Deleting a session, page or campaign cascades. **Server:** migration `f1280a0b1c2d` (revises `e1279a0b1c2d`) adds `fact_suggestions` and `summarisation_runs.fact_suggestions`; it has a real downgrade, and Alembic has a single head. New routes: `GET /api/campaigns/{id}/wiki/fact-suggestions`, plus `…/{sid}/use`, `/undo` and `/dismiss`, all for GMs only. The article read (GM only) and the GM session read carry the waiting suggestions, and the attention count includes them. The bot is unaffected. **After the deploy (D325, on the owner's say):** run this once over past sessions, newest first. It's safe to repeat. ``` celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill ``` **Checked** - **Backend,** on the dev host: every migration test, `tests/wiki`, `tests/export`, attention, transcript bin, member erasure, enqueue-off-loop, sessions, audio tasks and retention gave 3,056 passed and 0 failed. ruff format and check are clean; the version and changelog checks are OK. - **Frontend:** `src/pages/wiki` and `src/wiki`, 43 files and 625 tests passed. eslint has 0 errors. - **Not yet:** the full backend suite (CI) and the render check, which is starting. New wording goes on the Wording Review page before merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Suggested wiki updates gains "Facts that may have changed": one card
per page, old -> new with the quote from the recording, and Use it,
Change it... or Not this one. The editor shows a pending suggestion
under its fact. Status, Allegiances, Ruler, Leader and Owner first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner's answers to Q214–Q217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fact_suggestions table and run counts, the suggest_fact_changes task and
its backfill, GM-only routes, the article and session reads, the attention
count, and the transcript's lifecycle (retention, bin, erasure).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(webapp): a reprocess forgets the session's fact suggestions before renumbering its lines (#1280)
Some checks failed
CI / Does this change touch the recording path? (pull_request) Successful in 20s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Which journeys does this change need? (pull_request) Successful in 23s
CI / Bot/backend version sync (pull_request) Successful in 25s
CI / Backend lint (ruff) (pull_request) Successful in 30s
CI / Feedback site tests (pull_request) Successful in 46s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 56s
CI / Bot tests and audit (pull_request) Successful in 2m4s
CI / Docker image build (pull_request) Successful in 5m17s
CI / Frontend (shard 2/2) (pull_request) Successful in 9m9s
CI / Frontend (shard 1/2) (pull_request) Successful in 10m29s
CI / End-to-end journeys (desktop) (pull_request) Successful in 14m13s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
4352bfffd9
A suggestion links its quote to transcript lines by seq. A reprocess
replaces the lines and numbers them afresh, so the old links named the
wrong lines and a later erasure could miss the quote. Pending ones go
and decided ones lose their quote; the next run suggests from the new
lines. Found by the security review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

Render check of 4352bfff, on the seeded journeys stack built from this branch. The migration reached f1280a0b1c2d. Suggestions were inserted directly, since that stack has no LLM. Light and dark, 1280, 820 (queue only) and 390, 44 shots.

  • Every page returned HTTP 200, with no axe violations (WCAG 2.2 AA), no console errors from the pages, and no sideways scroll at 390.
  • Queue: "Facts that may have changed · 5", one card per page and one row per change, each with its quote and who said it when.
    • Change it… gives a Select for Status and a text field otherwise.
    • Use it turns the row into "Status is now Dead." with Undo (POST /use 200), and Undo brings it back (200).
    • Not this one removes the row (POST /dismiss 200).
  • A player: GET …/wiki/fact-suggestions returns 403. The article read carries no fact_suggestions, and the page shows Status "Alive", nothing suggested.
  • Editor: a pending suggestion shows under Status. Use it writes "Dead", focus lands on the value with a visible ring, and a later Save of another fact is a 200 with no version conflict.
  • Targets: 34 px tall or more.

Fixed after the check (latest commit): a long new value left "— →" alone on a line; on a phone, Undo stretched across the receipt; and a quote-less row drew empty "“”". There are two new tests.

Seen, as designed:

  • The session line counts only the latest played sessions, as it already did; older sessions' facts still show in the group.
  • The editor shows the newest suggestion for a fact, and an older one for the same fact is in the queue.
**Render check** of 4352bfff, on the seeded journeys stack built from this branch. The migration reached `f1280a0b1c2d`. Suggestions were inserted directly, since that stack has no LLM. Light and dark, 1280, 820 (queue only) and 390, 44 shots. - Every page returned HTTP 200, with no axe violations (WCAG 2.2 AA), no console errors from the pages, and no sideways scroll at 390. - **Queue:** "Facts that may have changed · 5", one card per page and one row per change, each with its quote and who said it when. - Change it… gives a Select for Status and a text field otherwise. - Use it turns the row into "Status is now Dead." with Undo (POST `/use` 200), and Undo brings it back (200). - Not this one removes the row (POST `/dismiss` 200). - **A player:** `GET …/wiki/fact-suggestions` returns 403. The article read carries no `fact_suggestions`, and the page shows Status "Alive", nothing suggested. - **Editor:** a pending suggestion shows under Status. Use it writes "Dead", focus lands on the value with a visible ring, and a later Save of another fact is a 200 with no version conflict. - **Targets:** 34 px tall or more. **Fixed after the check (latest commit):** a long new value left "— →" alone on a line; on a phone, Undo stretched across the receipt; and a quote-less row drew empty "“”". There are two new tests. **Seen, as designed:** - The session line counts only the latest played sessions, as it already did; older sessions' facts still show in the group. - The editor shows the newest suggestion for a fact, and an older one for the same fact is in the queue.
fix(webapp): the facts queue after its render check (#1280)
Some checks failed
CI / Does this change touch the recording path? (pull_request) Successful in 27s
CI / Which journeys does this change need? (pull_request) Successful in 27s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend lint (ruff) (pull_request) Successful in 30s
CI / Bot/backend version sync (pull_request) Successful in 40s
CI / Feedback site tests (pull_request) Successful in 48s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 52s
CI / Bot tests and audit (pull_request) Successful in 1m58s
CI / Docker image build (pull_request) Successful in 5m55s
CI / Frontend (shard 2/2) (pull_request) Successful in 8m57s
CI / Frontend (shard 1/2) (pull_request) Successful in 10m19s
CI / End-to-end journeys (desktop) (pull_request) Successful in 11m28s
CI / Backend migration, tests, and audit (pull_request) Failing after 24m3s
f9fbcb47f7
- The arrow travels with a long new value instead of sitting alone.
- On a phone, Undo sits beside the receipt rather than across it.
- A quote blanked with its transcript draws no empty quotation marks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-11-03 15:32:13 +00:00
CI's transcript-bin guard caught a raw .transcript read in
sessions_to_backfill. It now selects sessions with transcript lines
and no binned transcript; the task already refused a binned one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test: list the fact suggestions' uses of the transcript rows (#1280)
All checks were successful
CI / Which journeys does this change need? (pull_request) Successful in 19s
CI / Does this change touch the recording path? (pull_request) Successful in 20s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend lint (ruff) (pull_request) Successful in 28s
CI / Bot/backend version sync (pull_request) Successful in 38s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 52s
CI / Feedback site tests (pull_request) Successful in 1m16s
CI / Bot tests and audit (pull_request) Successful in 1m57s
CI / Docker image build (pull_request) Successful in 5m54s
CI / Frontend (shard 2/2) (pull_request) Successful in 8m36s
CI / Frontend (shard 1/2) (pull_request) Successful in 10m14s
CI / End-to-end journeys (desktop) (pull_request) Successful in 19m49s
CI / Backend migration, tests, and audit (pull_request) Successful in 37m14s
08da4836e8
The suggestion task reads rows only once readable_transcript is not
None; the backfill's EXISTS is filtered on the bin; the rest are type
annotations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Collaborator

CI: tests/test_transcript_bin_readers.py failed. That file is the transcript-bin guard (#680): a binned transcript must never be served, searched or sent to a model.

  • 1a008cd7: sessions_to_backfill read Session.transcript to find processed sessions. It now picks sessions with transcript lines (an EXISTS) whose transcript isn't binned, and never reads the text. A new test covers that a binned session is skipped and a restored one comes back.
  • 08da4836: the guard's sibling check lists every use of the transcript rows. The suggestion task reads rows only once readable_transcript(session) is not None, as it already did. The backfill's EXISTS is filtered on the bin, and the other four uses are type annotations.

Checked: the fact-suggestion and transcript-bin tests passed on the dev host, apart from the listing fixed in 08da4836, which I checked with the guard's own collector. ruff is clean. The PR is re-scheduled to merge when CI passes.

**CI:** `tests/test_transcript_bin_readers.py` failed. That file is the transcript-bin guard (#680): a binned transcript must never be served, searched or sent to a model. - **1a008cd7:** `sessions_to_backfill` read `Session.transcript` to find processed sessions. It now picks sessions with transcript lines (an EXISTS) whose transcript isn't binned, and never reads the text. A new test covers that a binned session is skipped and a restored one comes back. - **08da4836:** the guard's sibling check lists every use of the transcript rows. The suggestion task reads rows only once `readable_transcript(session)` is not None, as it already did. The backfill's EXISTS is filtered on the bin, and the other four uses are type annotations. Checked: the fact-suggestion and transcript-bin tests passed on the dev host, apart from the listing fixed in 08da4836, which I checked with the guard's own collector. ruff is clean. The PR is re-scheduled to merge when CI passes.
Sign in to join this conversation.
No description provided.