Lane 7: Talking — discussions, Talk tabs, Needs you, and Table rules & safety pages (#733) [HOLD for owner's T-answers] #846

Merged
claude-bot merged 31 commits from build/lane7-all into main 2026-09-27 01:01:06 +00:00
Collaborator

Hold: don't merge until the owner has answered T1–T15 on the morning list. It builds every recommended answer, and each lives in one named place so a different answer is a small change.

This is all of lane 7 except #831 (the safety server, already merged), integrated onto main.

Server (migrations 7d15c0a1b2e3 for discussions, then 7f2d0c1a3b4e for discussion notifications, re-chained onto e5f7a9b1c3d4)

  • audience_service: the single audience check, built for lanes 8 and 11 to reuse.
  • Discussion threads and posts:
    • thread ∩ post audiences;
    • a thread you can't see is 404, never 403;
    • a crawl test walks every read endpoint, plus the bell and Needs you, as every kind of reader.
  • Notifications (T5): new thread, reply, rumour decided, and thread widened. ck_notifications_kind is the union with safety_waiting.
  • Needs you: a question for you, a question for the party, rumours to decide, and questions waiting.
  • Canon (T7(a)): table-wide answers only. A party answer would have leaked into a page the whole table reads; lane 8's blocks will widen this.
  • Export and import: schema 13. Narrow audiences come back GM-only, so an import never widens anything.
  • Account deletion, and talk-counts with safety_waiting.

Pages

  • The audience picker in the lane 0 kit: a pill and a dialog, a Players/Characters switch and a people checklist, fully keyboard-operable.
  • Discussions:
    • the board, starting a thread, and a thread with answer, verdict, notes, edits (T9), lock, hide and canon;
    • Talk tabs on sessions (T1) and articles (T8);
    • Home's Needs you rows, the Contents entry with an unread count, and the bell's new kinds.
  • Table rules & safety:
    • lines and veils;
    • the GM's queue, with add as a line or veil, and dismiss;
    • the anonymous suggest and flag dialogs.
  • Four flag doors:
    • This week;
    • a live or played session's foot (T2);
    • the players' table face;
    • the GM table face, as a quiet waiting count (T4).

Journeys and seed

  • J2: a player asks about a session and the GM answers it. A safety block: an anonymous suggestion becomes a veil.
  • J6: a read-only "discussed" act.
  • Demo seed: 15 threads and 51 posts, 3 lines and 3 veils, 3 anonymous submissions.

Tests

  • Backend: full suite, 4706 passed.
  • Frontend: full suite, 2573 passed.
  • Journeys: the final full run, J4 included, gave 89 passed and 7 skipped on desktop, phone and keyboard. Earlier loaded runs found #844 (a vote upsert race), #845 and #841 (worker deaths under load).
  • The migration chain: up, down and up.

🤖 Generated with Claude Code

**Hold: don't merge until the owner has answered T1–T15** on the morning list. It builds every recommended answer, and each lives in one named place so a different answer is a small change. This is all of lane 7 except #831 (the safety server, already merged), integrated onto main. **Server** (migrations `7d15c0a1b2e3` for discussions, then `7f2d0c1a3b4e` for discussion notifications, re-chained onto `e5f7a9b1c3d4`) - **`audience_service`:** the single audience check, built for lanes 8 and 11 to reuse. - **Discussion threads and posts:** - thread ∩ post audiences; - a thread you can't see is 404, never 403; - a crawl test walks every read endpoint, plus the bell and Needs you, as every kind of reader. - **Notifications (T5):** new thread, reply, rumour decided, and thread widened. `ck_notifications_kind` is the union with `safety_waiting`. - **Needs you:** a question for you, a question for the party, rumours to decide, and questions waiting. - **Canon (T7(a)):** **table-wide answers only.** A party answer would have leaked into a page the whole table reads; lane 8's blocks will widen this. - **Export and import:** schema 13. Narrow audiences come back GM-only, so an import never widens anything. - **Account deletion,** and `talk-counts` with `safety_waiting`. **Pages** - **The audience picker** in the lane 0 kit: a pill and a dialog, a Players/Characters switch and a people checklist, fully keyboard-operable. - **Discussions:** - the board, starting a thread, and a thread with answer, verdict, notes, edits (T9), lock, hide and canon; - Talk tabs on sessions (T1) and articles (T8); - Home's Needs you rows, the Contents entry with an unread count, and the bell's new kinds. - **Table rules & safety:** - lines and veils; - the GM's queue, with add as a line or veil, and dismiss; - the anonymous suggest and flag dialogs. - **Four flag doors:** - This week; - a live or played session's foot (T2); - the players' table face; - the GM table face, as a quiet waiting count (T4). **Journeys and seed** - **J2:** a player asks about a session and the GM answers it. A safety block: an anonymous suggestion becomes a veil. - **J6:** a read-only "discussed" act. - **Demo seed:** 15 threads and 51 posts, 3 lines and 3 veils, 3 anonymous submissions. **Tests** - **Backend:** full suite, 4706 passed. - **Frontend:** full suite, 2573 passed. - **Journeys:** the final full run, J4 included, gave 89 passed and 7 skipped on desktop, phone and keyboard. Earlier loaded runs found #844 (a vote upsert race), #845 and #841 (worker deaths under load). - **The migration chain:** up, down and up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
WIP milestone: the five discussion tables (model + migration 7d15c0a1b2e3)
and app/services/audience_service.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Written before the router; they fail until it lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP milestone: the endpoints, before the first green run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The audience service (app/services/audience_service.py) is #593's one
definition of who can see a thing: table, party, mine, chosen people and
characters, or GMs only, every GM always inside, resolved when read. It
answers for one reader, for everyone, and as a SQL predicate; a test holds
the three equal. Lanes 8 and 11 reuse it.

Discussions (migration 7d15c0a1b2e3, five tables) with the board, starting,
reading and replying, GM notes for fewer people (refused when they reach
outside the thread, IS-145; thread and post audiences intersected on every
read), answers, verdicts, audience changes with a gains/losses preview,
follows, read markers, lock, soft delete with restore, post edit/hide/delete
(T9(a)) and the Talk counts. A thread or post a reader can't see is a 404
identical to a missing one. A crawl walks every endpoint as every kind of
reader and finds nothing leaked; a test pins which modules may read the
tables (never AI context, #654).

The one-head pin moves to test_discussions_migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migrations chain d4e6f8a0b2c3 -> 7e5afe7a1b2c -> 7d15c0a1b2e3: the
discussions migration is re-chained onto the safety one, and the one-head
pin moves to test_discussions_migration. CHANGELOG and docs/API.md keep
both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Written before the code; they fail until it lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIP milestone before the first run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also: talk-counts' safety_waiting in PR 1's count test, and viewers_for held
equal to Roster.viewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
# Conflicts:
#	CHANGELOG.md
The page at /campaigns/:id/safety (CAMP-18): lines and veils for everyone;
for a GM, Edit, Remove with Undo (D12), "+ Add", and the waiting queue with
Add as a veil / Add as a line (in the GM's wording) and Dismiss. A player gets
the anonymous suggest card with the drawn words (T3(a)) and every GM named
(T10(a)), and IS-017's Sent card.

The flag dialog, "Flag something to Dana", behind four doors: This week's
card (IS-205, about no session), the foot of a live or played session's page
(T2(a), IS-210), and Table mode's players' face ("Flag something quietly").
The GM's Table-mode face gets the quiet "N waiting in Table rules & safety"
in its place, polled from GET …/safety/waiting (T4(a)).

The Contents gains the Table rules & safety entry, with the GM's count
(IS-204, IS-212); a player is never asked for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flag's wrapper on Table mode's players' face carried .qb's paper, which
drew a strip across the old face's own background in both themes (found by
the render check). CHANGELOG bullets for Table rules & safety and its doors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The kit's "who can see this": AudiencePill, AudienceMenu and
  AudienceControl beside the chip row, with the drawn words, T10(a)'s
  naming of every GM, a Players | Characters switch and a checklist, and a
  keyboard-operable dialog that traps focus.
- The Discussions board (CAMP-19) with its filters as the address and
  T12(a)'s sentence only while empty; starting a thread (all three kinds,
  About search, a rumour's quest); a thread (CAMP-20) with answers,
  verdicts, notes for fewer people with IS-145's refusal said before
  posting, follow, moderation, widen/narrow, delete with Undo, and canon.
- Talk on every session that is not cancelled (T1(a)) and on an article,
  with the article's Read · Talk · History bar on today's page (T8(a)).
- The Contents' Discussions entry with its unread count (IS-157), Home's
  Needs you rows (IS-152), and the bell's four discussion kinds and
  safety_waiting, with a test that every server kind has an entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The reply box is the drawing's: a placeholder, not a label above it;
  a refused note stops the button without saying the refusal twice.
- A thread's title at the drawing's h2 step, so a question fits a phone.
- The article's tab bar without the band `.qb` painted on the old page;
  the Talk page and the new-thread form read the article from `entry`.
- A question's Needs you row says its day, not a table time.
- The picker is taller than a plain menu, so its people fit.
- The leave-guard list and a player's Read tabs now include lane 7's.
- CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-chains 7d15c0a1b2e3 (discussions) onto e5f7a9b1c3d4 (lane 5 PR 4b's
scene-text drop), which reached main first; 7f2d0c1a3b4e stays on top of it
and keeps the one-head pin. test_drop_scene_text_migration's pin becomes a
still-in-the-chain one, and test_notifications' history names both moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG: kept main's Table-mode entries (lane 5's #832 had since redrawn
the players' face and added Start/Stop to the bar) under A's new entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The players' flag door moves from SessionTable (deleted by lane 5's #832)
  onto the new players' face, PlayersTable: a block button at the foot of its
  one column, as lane4.html draws it, spaced by .tmp's own gap. The Players'
  view keeps it, being what players see. .qb-flag-table goes.
- The GM's waiting count sits at the foot of lane 5's aside, after the
  preview, "Shown to the table" and the quick note, where the drawing had
  "Flag something quietly".
- One read for the Contents count: the Table rules & safety entry takes
  talk.safety_waiting from the frame's …/talk-counts read (A's useTalkCounts),
  still gated on the viewer being a GM. useSafetyWaiting and its second
  request go; the Talk counts re-read after a write changesSafetyWaiting names.
  AppFrame.test's two safety-count tests now drive fetchTalkCounts, the
  player's with a number the server would never send.
- Notifications: the GM tasks row says "and Table rules & safety", since
  safety_waiting is filed there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Amberwake Accord talks: 15 threads and 51 posts across every kind,
state and audience — an open question with two replies the owner has not
read, an answer made canon into The Tidewatch Ledger, Dana's question to the
owner (Home's "A question for you"), rumours open, confirmed and denied,
threads about Sessions 23, 13 and 4 and two articles, a locked thread, a
deleted one, a hidden post, GM notes, and four threads the owner cannot see.
Follows, reads (the Contents says 2) and the bell's rows, written as rows.

Table rules & safety: the drawing's three lines and three veils, one line
removed, and three anonymous submissions waiting — day-only, no sender —
with Dana's one "Something is waiting" row.

The Tuesday Tide is left empty on purpose. test_demo_seed checks the counts,
the owner's view (11 of 15, 2 unread, the question in Needs you), Dana's
Needs you and waiting count, and that every seeded bell row renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
J2, at its lane 7 TODO: on the session she has just said yes to, Priya
opens Talk (T1(a)), starts a question that defaults to "Just you and Dana"
(asserted), walks the picker (a dialog; Escape hands focus back to the
pill) and asks. Dana is told in the bell, sees who can see it, replies; the
reply box names exactly that audience. Priya is told, marks the answer, and
the thread reads Answered. axe on the Talk tab, the form with the picker
open, the thread for both, and the bell with the new kind in it.

J2's safety block (#121), in a campaign of its own since the count is per
campaign: Priya suggests a veil with no name on it; Dana's Contents says
"Table rules & safety 1", the bell's generic row takes her to the queue, she
adds it as a veil in her own words, and Priya reads it.

J6's act 4: the board, the thread Priya asked about Session 2 and its Talk
tab, with a seeded GMs-only thread and GM note absent on the page and in the
API answers; and the table's line and veil. J5 and J6 expect a player's
Read and Talk tabs (IS-202) where they expected none.

The seed writes canon.talk through the product's API, and pencils Session 3
in, confirming it as its last act: J2 reads "Session 3 is on" among Priya's
twenty newest bell rows in the third project, and the question leg's reply
row per project had pushed it off (J4 met the same wall).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
J1 went straight from "Create the session" to the Contents' This week while
the session page was still arriving. In the keyboard project the Enter was
lost two runs in five once lane 7 gave that page one more request and its
Talk tab: focus ended on the session's heading, the address unchanged. It
now waits for the heading, as the rest of the suite waits for a page before
leaving it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Make it canon" on a party thread wrote the answer, unblocked, into a wiki
page the whole table reads, so members outside the party read it there
(found by the lane 8 survey). CANON_AUDIENCES is ("table",) until lane 8's
party and visibility blocks (IS-148) can carry the thread's audience into
the article; a party answer is now a 422, like a narrower one. The thread
page follows: no "Make it canon" on a party thread. Tests hold both sides;
API.md and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge main (#833: J4 and lane 5's demo seed) into lane 7's integration (#733)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 52s
CI / Backend lint (ruff) (pull_request) Successful in 31s
CI / Feedback site tests (pull_request) Successful in 46s
CI / Bot/backend version sync (pull_request) Successful in 51s
CI / Bot tests and audit (pull_request) Successful in 2m4s
CI / Does this change touch the recording path? (pull_request) Successful in 37s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 2m28s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m15s
CI / Frontend tests, audit, and build (pull_request) Successful in 20m33s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 22m2s
e0ec155c4e
demo/engine.py: both lanes' calls, the rules pack before Session 24's shelf
(lane 5) and lane 7's talking and table rules after it. test_demo_seed: lane
7's discussion and safety checks and lane 5's _the_table_is_seeded, both.
demo/README.md: both lanes' "What it touches" bullets. The e2e seed,
identities.json and README merged cleanly: J4's campaign and Joaquín, and
Session 3 pencilled in and confirmed last, both kept. Preview.jsx takes
#833's `article.entry` fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applies the owner's two lane 7 decisions on top of the built T10(a)/board work:

- D174 (T10(b)): every audience sentence that named a GM now says "the GM"
  (one) or "the GMs" (more than one) instead — the picker's labels and
  foot, the board's default-audience hint, a session note's readers, the
  Table rules & safety flag dialog and its "Not even ... can see who sent
  it" promise, and who may change the lists. A reader who is themself a
  GM still reads "you". Also fixes `rosterFromMembers`'s "party" sentence,
  which could name a GM as "everyone but <GM>" when they have no active
  character — GMs are now excluded from that count entirely.
- D176 (T12(a)): the Discussions board's opening sentence shows only while
  the board is completely empty, in place of the separate empty-state line
  (which is removed) rather than alongside it. A non-empty board shows
  neither.

Updates the J2 journey spec to the new wording, the CHANGELOG's lane 7
bullets, docs/API.md's Audience shape note, and the lane6 coverage
script's description text. Coverage script still prints "unclassified:
none".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(table): the safety waiting count under a phone GM's stage too (T15, #733)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m36s
CI / Bot tests and audit (pull_request) Successful in 5m3s
CI / Backend lint (ruff) (pull_request) Successful in 1m15s
CI / Bot/backend version sync (pull_request) Successful in 1m13s
CI / Feedback site tests (pull_request) Successful in 2m12s
CI / Does this change touch the recording path? (pull_request) Successful in 41s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 1m19s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m57s
CI / Frontend tests, audit, and build (pull_request) Successful in 20m35s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 20m28s
293ec95271
Addendum from the owner's answers to the lane 7 questions: T13 and T14 are
already built as recommended (verified, no change needed) — the private
question's bell line already says "you", never a GM's name, and the flag
doors on This week and a session's page are already player-only. T15 had a
real gap: the desktop aside's "N waiting in Table rules & safety" quiet
count (T4(a)) had no equivalent on a phone, since GmPhone has no aside to
put it in.

Reuses the existing WaitingCount component under the phone stage, with the
same words and the same nothing-when-none-wait rule as desktop. Two new
tests in TableMode.test.jsx cover it showing and staying silent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests first: GET/PATCH /sessions/{id}/prep with the sheet's own version,
migration 3b7e1d9c5a26 (prep_sheets, prep_items, shelf_pins'
shelf start and its backfill), and reconcile_pins filling shelves only
from a pin's shelf start (P3a). The one-head pin moves to the new
migration's test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration 3b7e1d9c5a26 on c3d5e7f9a1b2: prep_sheets (one per session,
made on the first save, with its own version for autosave), prep_items
(the table only; 1b writes it), and shelf_pins.shelf_start_session_id,
backfilled to each pin's start once, when the column is made.

GET and PATCH /api/sessions/{id}/prep, GMs of the campaign only (P7).
A stale save is a 409 carrying the current sheet. The beats stay in
sessions.beat_notes and are saved under the sheet's version; the old
beat-notes endpoint moves that version, and a beats save from the sheet
moves the old endpoint's token, so the two editors meet as conflicts.

P3(a): a pin rides every later sheet from its start, but reconcile_pins
fills shelves only from its shelf start. The table's pin sets both. The
cancel prune and the cold open's carried count treat the shelf start
as where a card originated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane 5's PR 4b took e5f7a9b1c3d4, the survey's first choice, and the
coordinator assigned the survey's spare. down_revision stays on main's
head, c3d5e7f9a1b2; it is re-chained after 4b at merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests first: insert (a run under Generated, a rule under Rules, a wiki
article under Wiki, P10), remove and undo, For later and moving it onto a
sheet (P11), pin / unpin / pin again with P3(a)'s two starts, Add to
shelf into a scene from an item or a carried block, and the plan: draft,
the worker's write into the sheet, redraft, use this draft, and each
part to the shelf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Items: Insert into sheet (a run under Generated, a rule under Rules,
a wiki article under Wiki: P10a), one per source per sheet, Remove with
an undo; Keep for later (P11a) and moving it onto a sheet. The item
routes live at /api/prep-items/{id}, For later at
/api/campaigns/{id}/prep/later.

Pins (D39, P3a): pinning an item makes one pin that rides every later
sheet, and rides the shelves only if the item is already on this shelf.
Add to shelf, from an item or a carried block, sets the pin's shelf
start there and files the card (and the pin) in the scene. Unpin and
Pin again reopen the same pin; the table's Unpin tail is now
shelf_service.end_pin, shared.

The plan (D38): POST .../prep/plan queues session_prep with the sheet's
want as its direction and names the run on the sheet. The worker
(run_workbench_generation_core, on task_session) writes the plan into
the sheet if the sheet still names that run, dated by when the tool
began, so a change made while drafting leaves it stale. Use this draft,
and each scene, secret or NPC onto the shelf as a note or wiki card.
The plan and items never move the sheet's autosave version. No
migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests first: migration f6a8b0c2d4e5 (runs' soft delete, P4a's move of
pinned runs into their session's sheet or For later), the sweep sparing
what a sheet keeps, History rows (kept_by, clears_at, replaced, label,
tool lists), delete with an undo, the sheet's History section; an
output schema per tool; next_beat (P5a); names and warm draws leaving
runs (#693); editing the plan under the sheet's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The migration a7b9c1d3e5f6 (an arc's soft delete, the session a scene was
made for) is here with its tests, which take the one-head pin from
test_prep_sheets_migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Migration f6a8b0c2d4e5 on b8c0d2e4f6a7: generation_results.deleted_at,
and P4(a)'s move: each pinned, finished run becomes an item under
Generated in its session's sheet (For later with no session), kept, not
carried, no pin; its flag is cleared. A pinned run that never finished
keeps its pin. Nothing is deleted. Downgrade re-pins every run a live
item keeps.

The sweep deletes a run only when nothing keeps it (a live item, a
removal within 30 days, a sheet's plan, or the retired pin), and purges
History's soft deletes after a day. History rows gain label, kept_by,
clears_at, replaced and deleted_at, a repeatable tool_id and a
session_id filter; Delete is soft with /restore, and 409 for a run a
sheet keeps. GET /prep gains the sheet's History section.

Every tool declares a strict output schema, sent as json_schema (names
through generate_name_options). next_beat (P5a) proposes one beat from
what the GM wants, the beats, the last recap and the open arcs and
quests. Cached names and every Instant NPC draw leave a run, named by
result_id (#693). PATCH /prep takes the plan under the sheet's version,
naming the draft it was made on, so a draft that lands meanwhile is a
409, not overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- GET /campaigns/{id}/planning: the sessions ahead and what their sheets
  hold, every pin and what it rides, the scene library, the arcs, the open
  quests and For later, in one read that writes nothing.
- P8(a): POST /campaigns/{id}/pins/{pin}/unpin stops a pin at the last
  session played or being played (or where it was pinned, if that is later)
  and names the session; /pin-again is its Undo.
- Arcs: a soft delete with POST .../restore, and PUT .../arcs/order.
- scenes.session_id, the session a scene was made for.
- prep_session on the campaign list, and This week's prep line gains beats,
  plan_drafted and has_sheet, both from the rule Planning uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings lane 6's four server PRs (1a, 1b, 2, 3) onto main, which now has
lane 5's Table mode (d4e6f8a0b2c3, e5f7a9b1c3d4), lane 7's safety
(7e5afe7a1b2c) and R1/R2.

- The chain is now c3d5e7f9a1b2 -> d4e6f8a0b2c3 -> 7e5afe7a1b2c ->
  e5f7a9b1c3d4 -> b8c0d2e4f6a7 -> f6a8b0c2d4e5 -> a7b9c1d3e5f6 (one head).
  test_planning_migration keeps the one-head pin; test_drop_scene_text_migration
  becomes a still-in-the-chain test; test_shelf_cards_migration takes main's
  d4e6f8a0b2c3 pin.
- app/main.py mounts prep, planning and safety.
- docs/API.md and CHANGELOG keep both sides' sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session's prep sheet as a document that saves itself (D38-D40):
Where we left it, Carried forward (pin, unpin with its undo, pin again,
add to shelf in a scene), What I want, Beats (a numbered list written in
place), The plan (draft, drafting, still drafting, failed, drafted, stale,
redraft, edit on the draft it was made on, each part to the shelf),
Generated, Rules & bestiary, Wiki (P10), the Shelf section by scene (make,
rename, file; no Foundry, P2; no "shows at the start", P6) and History
(insert, use this draft).

Autosave: a second after typing stops, only the fields typed, on the
sheet's version; a failed save keeps the words and backs off; the 409 is
"changed in another window" with Keep mine and Use the other window's; the
leave guard asks only when a save has failed or met another window.

The address is the session's (P1): old /planning links naming a session
and the plan or beats go to its sheet; /campaigns/:id/prep is the next
sheet, or "No session to prep". The Contents' Running it entry is
"Prep · Session 14", current on every sheet with the sheet's sections under
it; the header's chip says "GMs only" (P7). Sessions, This week, the
session page and the old campaign page link to the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Tailwind's preflight took the markers off Beats and the plan's lists:
  numbered and bulleted again.
- A tool's output of several lines ran on from its title after a dash:
  it starts under the title now, one line a line.
- The sheet column is capped at the measure, so a section's note lines up
  with the blocks under it rather than with the far edge of the frame.
- The header's time and tally are one run of text, so a phone does not
  leave the dot alone at the end of a line.
- The leave guard's callers list names the sheet's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven drawers beside the sheet (Story arcs, Quests, NPCs, Names, Improv,
Rules & bestiary, Wiki), and on a narrow screen the Tools sheet (D42), which
takes Open the table's place on a phone. Every tool's result row is Insert
into sheet, Add to shelf in a scene (IS-341, TB-085), Append to an article,
Save to the wiki and Copy. Names are runs now (#693); Instant NPC draws are
runs made for the sheet. Suggest the next beat (P5) under Beats and in the
Story arcs drawer: one beat, Add or Another, nothing in without Add. Suggest
a title on a recurring session's heading (IS-342). History's Re-open opens
the run's drawer. The two lookups insert into Rules & bestiary and Wiki
(P10); no Convert stat blocks in bulk (P9); no Foundry (P2).

Rule snapshots carry the pack's scope and whether the record is GM-only, so
the sheet's rules blocks can say so. Old /planning links to a margin tool
open its drawer on the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add to shelf opens its scenes in place inside the margin: the margin
  scrolls on its own, and a popover hung off it was cut off at its edge.
- The Tools sheet's Close sits beside its title, not a long scroll down.
- The header's meta row ends at the margin's edge, not the frame's, at 2560.
- Tests for the margin: the drawers, a required field sending nothing,
  Insert and Add to shelf in a scene, a failure, rumours, Instant NPC and
  its empty pool, ready-made names and not-ready, both lookups, the arcs and
  quests drawers, Suggest the next beat, Suggest a title, Re-open and the
  Tools sheet. The Shelf section's test waits for all its groups at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/campaigns/:id/planning is Planning now (P1, D41): sessions ahead with each
sheet's state; everything pinned with Unpin (P8(a), stops at the last session
played) and its Undo; ended pins; the scene library (sessions used, cards,
made for, rename, new; no Foundry, P2); story arcs edited in place (move
either way by key or button, delete with Undo, add, suggest, PR-054's
failure); and For later (IS-352), with the tool margin beside it keeping
what it makes for later (P11(a), IS-353). Old links naming a session or a
sheet's tool still go to the sheet.

/campaigns/:id/planning/history is Generation history (PREP-3): the groups
there are, where each run lives and when it clears, Re-open (into its sheet
or Planning), Try again, Insert into the next session, Use this draft, and
Delete with Undo; a run a sheet keeps says so in the server's words (409).

The sheet's History links to it (All history); No session to prep offers
Plan without a date; Edit the arcs goes to Planning's arcs. The Contents'
Running it has Planning above Prep. The old Prep page and what only it used
(GeneratorPanel, workbenchTools, BeatNotesEditor, GenerationHistory,
LorePicker, WorkbenchResultActions) are gone, with their tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The render check: on a phone the first arc's row squeezed its title and
notes into a one-word column beside Up, Down and Edit, and History's rows
did the same beside their actions. A row's actions now go under it there,
and a row with none (a run still writing) leaves no gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #833 (J4, lane 5's seed, the stateful fake bot) and #837 (the
last-admin guard). Git resolved it without conflicts; main added no
migrations, so lane 6's chain stays on e5f7a9b1c3d4 with one head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sheet found on Planning, what she wants and the beats saving
themselves, the plan drafted and a secret put on the shelf, a read-aloud,
a creature and a quest from the margin (the Tools sheet on a phone), a pin
carried to the next sheet, a scene with the creature filed in it, Unpin
and Undo on Planning, and History's keep and delete, on all three
projects with axe clean.

J3 preps in its own campaign, The Saltglass Survey, with no players and
no Discord, so the sessions each project makes tell nobody. The fake AI
scripts the plan, the read-aloud and names by their output schema, and
aiRequests()/aiNow() read what it was asked.

Fix what J3 found: the worker marked the plan's run ready in one commit
and wrote the plan in another, so a sheet polling between the two
stopped asking and said "Nothing drafted yet" beside a written plan.
land_plan now marks the run ready in the commit that lands its plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six prep sheets, one per state (complete, stale, what I want and beats
only, two played records, and a blank one in The Tuesday Tide), with
Session 27 left without one; fourteen items across Generated, Rules &
bestiary and Wiki, For later (and one moved from it) and one removed;
three pins made on sheets (sheets only, on the shelves a session later,
ended); eleven more History runs, none pending; the old keep-pinned runs
as the items P4(a)'s migration makes them; arcs in the GM's order with
one soft-deleted; the empty scene made for Session 24.

test_demo_seed reads it back through prep_service, history_service and
planning_service, and lane 5's rule-card tripwire now counts only the
unpinned cards. The README moves Planning to Seeded and says the GM pages
don't show to the owner's account, which plays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty Read-aloud's "nothing reached the model" now filters the fake
AI's log by the read-aloud's scripted answer, so a pipeline another
journey left running cannot answer for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
notify_vote_cast queued send_vote_notification without catching a
failure, though the broker fails fast (a two-second socket timeout) on
the promise that request-path enqueues are non-fatal. A broker slow for
three seconds made a player's vote a 500 and rolled it back; J2's grid
then showed the earlier answer. Found by a full journeys run on a loaded
host; the notification is now best-effort and logged.

J3 also waits for the campaign list before asking the Contents for
Planning: the rail is drawn before it knows Dana is a GM here, and on a
slow stack the Running it group arrived after goVia gave up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_db commits when the request's dependencies close, and FastAPI closes
them after the response has been sent. So the vote grid's second press
could reach the server before the first was committed, find no vote,
insert one, and collide with the first in uq_votes_slot_user: a 500, and
the grid put the earlier answer back. J2 met it twice in lane 6's full
journeys runs.

upsert_vote and delete_vote now commit (the codebase's "Commits."
convention), so the answer only goes once the vote is durable, and the
vote-update notification is queued after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(webapp): the server has an owner (D68, IS-397, lane 9 A)
Some checks failed
CI / Backend migration, tests, and audit (pull_request) Failing after 6m6s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m46s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Bot tests and audit (pull_request) Successful in 4m52s
CI / Bot/backend version sync (pull_request) Successful in 30s
CI / Docker image build (pull_request) Successful in 17s
CI / Does this change touch the recording path? (pull_request) Successful in 27s
CI / Feedback site tests (pull_request) Successful in 2m0s
CI / Frontend tests, audit, and build (pull_request) Successful in 19m57s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 13m36s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 18m56s
b2293c526d
Migration f1a3c5e7b9d2 adds users.is_server_owner (partial unique index)
and server_ownership_offers, backfilled from the earliest
admin.setup.finished actor if still an admin, otherwise the
earliest-created admin, otherwise nobody. The owner cannot be demoted,
deleted, or delete their own account without handing the server on
first; the offer flow mirrors a campaign's own (offer/accept/decline/
cancel, a bell notification, the outcome told to the previous owner);
the CLI set_owner moves ownership without an offer for a vanished
owner.

Also in this PR: D56's member-search-scope write, About this server's
GET/PUT, the admin campaign list's delivery_failing_since/
owner_last_seen_at/uses_own_ai fields plus a members read for the
hand-on picker, notifying both owners on an admin's campaign
reassignment (campaign_handed_on), the audit log's name filters
(actor/campaign/category) and per-page name resolution, and one cheap
GET /admin/overview for the future Admin index.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brings in #838, #842, #847, #848, #849, #850 and #852.

- Migrations: lane 6's first migration, b8c0d2e4f6a7, is re-chained from
  e5f7a9b1c3d4 onto main's head c4e6a8b0d2f1, so the chain is
  e5f7 -> a2c4 -> c4e6 -> b8c0 -> f6a8 -> a7b9. c4e6's one-head pin becomes
  still-in-the-chain; test_drop_scene_text_migration takes main's side;
  test_notifications' history names both sides.
- vote_service: main's versions (ON CONFLICT upsert, enqueue), per #847 and
  #850. Lane 6's own fixes (9f1b3c01, f8875f46: a best-effort vote
  notification, a commit before the route answers) are superseded by #851's
  fix in flight (#854), and their two tests are dropped with them, along
  with the Fixed bullet that described them.
- campaigns.py: the Instant NPC draw's refill goes through enqueue (#850)
  and still returns lane 6's result_id.
- prep_service.draft_plan queued the plan with a direct .delay() from an
  async def; it now uses await enqueue(...) (#850).
- CHANGELOG: both sides kept, and a stray "=======" lane 6's last merge
  left behind is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner's answer to P12: the old pinned runs that P4's migration left
pinned, the ones that failed or never finished, stay ordinary unkept runs
and clear 30 days after they were made, like any other. With nothing left
to read or write it, the column goes.

- Migration d6f8b0c2e4a7, after a7b9c1d3e5f6, drops the column; its
  downgrade re-adds it as NOT NULL DEFAULT false, and f6a8b0c2d4e5's own
  downgrade then re-pins what a live item keeps. The one-head pin moves to
  test_drop_generation_pinned_migration; test_planning_migration's becomes
  still-in-the-chain; test_notifications' history names it.
- The sweep's exemption goes from kept_clause, so the failed runs clear.
- Removed with it: the model column, set_pinned, the list's pinned
  filter, POST .../workbench/history/{id}/pin and /unpin (the History
  panel that called them is gone; nothing in the frontend did), the
  pinned field on History's rows, the demo seed's pinned=False, and the
  frontend's unused pinResult/unpinResult.
- Tests: the retired routes are 404s, a failed run clears 30 days after
  it was made, the sweep takes an old failed run, and the migration's
  up, down, up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(design): lane 6's checklist defers the eight Foundry rows (#732, P2)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m0s
CI / Bot tests and audit (pull_request) Successful in 2m16s
CI / Backend lint (ruff) (pull_request) Successful in 1m28s
CI / Feedback site tests (pull_request) Successful in 39s
CI / Bot/backend version sync (pull_request) Successful in 1m14s
CI / Docker image build (pull_request) Successful in 59s
CI / Does this change touch the recording path? (pull_request) Successful in 42s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m35s
CI / Frontend tests, audit, and build (pull_request) Successful in 38m38s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
19b7b2ce84
P2 was answered as recommended: draw none of Foundry until the connection
exists (v5.x), as D113 ruled for Table mode, and move the eight Foundry
rows to deferred so the lane can be finished. The pages already leave it
out and test for it; the checklist still counted the rows as drawn.

IS-277, IS-282 to IS-286, IS-288 and IS-350 are deferred to v5.x; IS-349
stays drawn without its pairing. coverage/lane5.py: 148 drawn, 40
replaced, 9 behaviour, 16 elsewhere, 10 deferred; none unclassified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cap preview, health verdict, notice publish, media in the backup

Six server-side pieces from the lane 9 survey's §3.2/§3.3, all built
against the bot identity check and D67 removals already on main
(#849) and the backup-credentials work already on main (#848):

- D139/A2: drop the stored instance-wide Discord bot token. The bot
  never read it — it signs in with its own environment's
  DISCORD_BOT_TOKEN — so the setup "Discord" step now settles on a
  successful identity check (admin_bot_identity) instead of a typed
  token, and an invite preview's "records sessions" reads "a bot URL
  is set and the campaign has a guild". bot_token is dropped from
  PUT /api/admin/settings/bot's request and bot_token_configured from
  its response.
- A10/D147: POST …/llm/test, …/whisper/test and
  …/notifications/test-email accept an optional body and probe
  exactly what is typed, saved or not. The saved secret is only
  reused for the saved address; a different typed address with a
  blank key sends no key and says so. The email test now runs
  inline with a short timeout and reports {sent, error} instead of
  queuing a Celery task nobody ever saw the outcome of again (#380's
  failure mode).
- A7/D144: GET/PUT /api/admin/settings/plan-pricing over PlanPricing's
  four fields. The PUT is hosted only (501, NotHostedError); the GET
  answers on every instance since reading defaults back prices
  nothing.
- A6/D143: uploaded media joins the backup, on by default
  (include_media, unlike include_audio) — the worker now mounts
  media:/app/media:ro. Migration b3d5f7a9c1e4 adds
  backup_logs.media_archive_path (recorded and read back the same way
  as the audio archive's, but never derived — no naming scheme
  predates the column) and, in the same migration, deletes the
  now-orphaned stored bot-token row.
- The instance retention cap preview (IS-311's confirm): GET
  /api/admin/settings/retention/preview diffs the sweep's own
  selection under today's cap against a proposed one, per campaign,
  skipping the trash (#405).
- The AI queue's last_finished_at and a server-computed verdict
  (free/busy/stuck, ADMIN-14) — a provider that has never finished
  anything is busy, never stuck, so a first-ever request in flight is
  never a false alarm.
- The rightsholder-notice contact gets a published flag: the public
  GET answers as fully withdrawn while it is false, and the new
  admin-only GET (beside the existing PUT) always sees the real
  address.

Migration chain: a2c4e6f8b0d1 -> c4e6a8b0d2f1 -> b3d5f7a9c1e4 (new
head). Verified upgrade/downgrade/upgrade on a fresh Postgres.

Not in scope: A1 (retention semantics) — the owner hasn't decided it.
Server PR A (owner/audit/overview) is a parallel PR on the same base;
expect the orchestrator to re-chain at merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
backup media checkbox (lane 9 B2, D139/A2, A6)

Setup.jsx's Discord step (IS-256) no longer asks for a token — there
is nothing to type any more (D139/A2) — and its "Check the bot
answers" button becomes "Check the bot is signed in", calling the
new getBotIdentity instead of pingBot and reporting who the bot is
signed in as, or why it isn't, using the server's own detail text.

The legacy Admin backup form (BackupSection) gets an "Include
uploaded media" checkbox beside "Include audio recordings", checked
by default (A6).

e2e/seed/seed.py's bot setup no longer sends bot_token (silently
ignored by the server now) and instead calls
GET /api/admin/settings/bot/identity to settle the setup "Discord"
step, the same way a real bot signing in would — the fake bot's own
/identity already answers ready. J1 (e2e/j1-first-campaign.spec.js)
walks this seed's Discord settlement indirectly and needs a run to
confirm; not run here per the delegation's rules (no
scripts/journeys.sh).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(webapp): A10's withheld key probes instead of refusing; drop the
Some checks failed
CI / Backend migration, tests, and audit (pull_request) Failing after 2m50s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 54s
CI / Backend lint (ruff) (pull_request) Successful in 33s
CI / Feedback site tests (pull_request) Successful in 40s
CI / Bot/backend version sync (pull_request) Successful in 24s
CI / Does this change touch the recording path? (pull_request) Successful in 17s
CI / Docker image build (pull_request) Successful in 26s
CI / Bot tests and audit (pull_request) Successful in 3m50s
CI / Frontend tests, audit, and build (pull_request) Successful in 18m58s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
d7cf303ff7
orphaned Discord token input from legacy Admin.jsx

Two follow-ups on review of the lane 9 B2 server PR:

1. A different typed address no longer short-circuits llm/test,
   whisper/test and notifications/test-email when it cannot reuse the
   stored secret. Self-hosted Ollama, llama.cpp and mail relays are
   usually keyless, so refusing the probe outright meant a GM could
   never test a fresh address at all. The probe now always runs, with
   no key (never the stored one — confirmed by inspecting the
   outgoing request, not just the resolved config), and a `detail`
   note — "No key was sent — type it to test this address." — is
   attached to the result, on success and on failure alike, only when
   there was a stored secret the address mismatch is why it wasn't
   reused. A server with nothing stored at all reports plainly, with
   no note: that is the ordinary state of a keyless endpoint, not a
   warning.

   Applied to all three A10 endpoints (llm/test, whisper/test,
   notifications/test-email) for consistency — they share the same
   `_typed_api_key` helper and the same design flaw, and the email
   endpoint's own scenario (a self-hosted, unauthenticated relay) is
   the same shape as Ollama's.

2. Removed the "Discord Bot Token" input, its form state and its
   place in the save payload from the legacy Admin.jsx Discord
   section. D139 dropped the field everywhere on the server; an input
   that saves nothing and reads nothing back is exactly what D20/D36
   rule out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge origin/main into lane 9 A (#855): scope="function" for the branch's get_db, and the inventory's metadata keys
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Backend lint (ruff) (pull_request) Successful in 35s
CI / Feedback site tests (pull_request) Successful in 53s
CI / Docker image build (pull_request) Successful in 25s
CI / Bot/backend version sync (pull_request) Successful in 1m7s
CI / Does this change touch the recording path? (pull_request) Successful in 31s
CI / Bot tests and audit (pull_request) Successful in 2m12s
CI / Backend migration, tests, and audit (pull_request) Failing after 27m1s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
41bbc62c51
- CHANGELOG: kept lane 9 A's entry and main's D131-D178 line.
- Every Depends(get_db) the branch adds in admin.py and users.py is now
  scope="function" (#854), so test_commit_before_response's walk passes.
- test_admin_campaign_inventory: EXPECTED_KEYS gains delivery_failing_since,
  owner_last_seen_at and uses_own_ai, each commented as metadata rather
  than campaign content.

f1a3c5e7b9d2 already sat on c4e6a8b0d2f1; one head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CHANGELOG: kept B2's entries, lane 9 A's and main's D131-D178 line.
- admin.py: B2's plan-pricing endpoints and lane 9 A's /admin/overview both
  appended at the end; kept both.
- b3d5f7a9c1e4 now revises f1a3c5e7b9d2 (it was written on c4e6a8b0d2f1).
  The one-head pin is test_media_archive_path_and_bot_token_removal_migration's;
  test_server_owner_migration's becomes test_this_migration_is_still_in_the_chain.
  c4e6's own chain test keeps f1a3 as its successor; test_notifications'
  history docstring gains both steps.
- B2's four new Depends(get_db) (admin.py, content_notices.py) are
  scope="function".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CHANGELOG: kept lane 6's entries and lane 9 A/B2's.
- campaigns.py: kept lane 6's get_workbench_result (returns _history_row);
  the pin/unpin endpoints main had scoped stay deleted (P12, d6f8b0c2e4a7).
- b8c0d2e4f6a7 now revises b3d5f7a9c1e4 (it was on c4e6a8b0d2f1). The one-head
  pin stays with test_drop_generation_pinned_migration; the B2 migration's pin
  becomes test_this_migration_is_still_in_the_chain; c4e6's chain test keeps
  f1a3 as its successor; test_notifications' history docstring gains lane 6's
  steps after B2's.
- Lane 6's 24 Depends(get_db) (campaigns.py, planning.py, prep.py) are
  scope="function".
- vote_service, votes.py and bot.py are byte-identical to main (#854).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Conflicts, both sides kept: CHANGELOG; docs/API.md (Discussions beside
  Prep Sheets and Planning, and the contents); main.py (discussions, prep
  and planning routers); demo/engine.py (the discussions and prep/Planning
  seed methods); useContents.js (a session address takes talk and prep);
  leaveGuardCallers.test.js (NewThread and the prep sheet).
- 7d15c0a1b2e3 now revises d6f8b0c2e4a7 (it was on e5f7a9b1c3d4). The one-head
  pin returns to test_discussion_notifications_migration; lane 6's
  test_drop_generation_pinned_migration becomes "still in the chain", and
  test_drop_scene_text_migration keeps a2c4e6f8b0d1 as its successor.
- ck_notifications_kind / _subject_type: 7f2d0c1a3b4e was written on
  7e5afe7a1b2c's lists, so an upgrade would have dropped lane 9 A's
  server_ownership_offer and campaign_handed_on (and the server_ownership_offer
  subject type) from the CHECKs. Its _BEFORE lists are now f1a3c5e7b9d2's, so
  the CHECK is the union and the downgrade narrows to exactly f1a3's. The
  model's enum and SUBJECT_TYPES follow chain order; CATEGORY_FOR_KIND has
  every kind. test_server_owner_migration, test_safety_migration and
  test_notifications hold their lists as history; the discussions migration
  test checks a server-owner row survives its downgrade.
- Lane 7's 20 Depends(get_db) in discussions.py are scope="function". Lane 7
  adds no direct .delay()/.apply_async(): it notifies through main's
  notification_service.notify, which already goes off-loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(backend): the retention test leaves the media snapshot out (#859, A6)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 59s
CI / Backend lint (ruff) (pull_request) Successful in 30s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Bot tests and audit (pull_request) Successful in 2m0s
CI / Bot/backend version sync (pull_request) Successful in 26s
CI / Docker image build (pull_request) Successful in 28s
CI / Does this change touch the recording path? (pull_request) Successful in 33s
CI / Frontend tests, audit, and build (pull_request) Successful in 17m52s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
05efd1f987
The full suite at the top of the merge train failed
test_retention_never_deletes_a_manual_dump_or_the_one_just_taken: B2 turns
the media snapshot on by default, and once an earlier test has left a file
in the media root the run also writes questboard_media_<ts>.tar.gz, which the
test's listing of the scheduled files did not expect. Order-dependent, so
B2's targeted run passed. The test is about the dumps; it now says
include_media: false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge train/2-859 into lane 6 (#858): the retention test's media fix
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 57s
CI / Bot tests and audit (pull_request) Successful in 2m4s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Feedback site tests (pull_request) Successful in 48s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 33s
CI / Does this change touch the recording path? (pull_request) Successful in 29s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m41s
CI / Frontend tests, audit, and build (pull_request) Successful in 19m32s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
c429ab0898
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge train/3-858 into lane 7 (#846): the retention test's media fix
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 59s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Feedback site tests (pull_request) Successful in 45s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Bot tests and audit (pull_request) Successful in 2m5s
CI / Docker image build (pull_request) Successful in 34s
CI / Does this change touch the recording path? (pull_request) Successful in 29s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
4ed619bc14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(webapp): a server handover clears the old owner before setting the new (#855)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m57s
CI / Backend lint (ruff) (pull_request) Successful in 1m17s
CI / Bot tests and audit (pull_request) Successful in 2m3s
CI / Bot/backend version sync (pull_request) Successful in 27s
CI / Docker image build (pull_request) Successful in 20s
CI / Does this change touch the recording path? (pull_request) Successful in 30s
CI / Feedback site tests (pull_request) Successful in 2m2s
CI / Backend migration, tests, and audit (pull_request) Successful in 13m58s
CI / Frontend tests, audit, and build (pull_request) Successful in 41m31s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 19m15s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 33m6s
7d12eb3be0
uq_users_one_server_owner is a partial unique index, checked row by row and
not deferrable, and one flush writes both rows in primary-key order - so the
handover failed whenever the new owner's id sorted first (CI hit it; local runs
happened not to). The CLI's set_owner no longer clears its own target either.
A parametrized test forces both id orders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge branch 'train/1-855' into train/2-859
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m2s
CI / Bot/backend version sync (pull_request) Successful in 26s
CI / Docker image build (pull_request) Successful in 19s
CI / Backend lint (ruff) (pull_request) Successful in 1m11s
CI / Does this change touch the recording path? (pull_request) Successful in 21s
CI / Bot tests and audit (pull_request) Successful in 2m1s
CI / Feedback site tests (pull_request) Successful in 1m54s
CI / Frontend tests, audit, and build (pull_request) Successful in 18m18s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Failing after 55s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
3e87535fc7
Merge branch 'train/2-859' into train/3-858
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 49s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Bot/backend version sync (pull_request) Successful in 25s
CI / Feedback site tests (pull_request) Successful in 40s
CI / Docker image build (pull_request) Successful in 17s
CI / Does this change touch the recording path? (pull_request) Successful in 25s
CI / Bot tests and audit (pull_request) Successful in 5m7s
CI / Backend migration, tests, and audit (pull_request) Successful in 14m40s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
baae63ac1c
Merge branch 'train/3-858' into train/4-846
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m17s
CI / Bot tests and audit (pull_request) Successful in 4m9s
CI / Backend lint (ruff) (pull_request) Successful in 1m14s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 22s
CI / Feedback site tests (pull_request) Successful in 1m50s
CI / Does this change touch the recording path? (pull_request) Successful in 28s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
846b997e71
fix(e2e): the seed reads the inline test email's answer (D147, #859)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Bot tests and audit (pull_request) Successful in 2m3s
CI / Backend lint (ruff) (pull_request) Successful in 33s
CI / Feedback site tests (pull_request) Successful in 46s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 29s
CI / Does this change touch the recording path? (pull_request) Successful in 27s
CI / Backend migration, tests, and audit (pull_request) Successful in 14m33s
CI / Frontend tests, audit, and build (pull_request) Successful in 19m33s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 13m24s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 18m49s
6bb1da69bd
B2 made the admin Test Email send inline and answer 200 {sent, error}
instead of queueing (202). The seed still expected the 202 and failed
the journeys job before any test ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge branch 'train/2-859' into train/3-858
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Bot tests and audit (pull_request) Successful in 2m7s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Feedback site tests (pull_request) Successful in 48s
CI / Bot/backend version sync (pull_request) Successful in 27s
CI / Does this change touch the recording path? (pull_request) Successful in 26s
CI / Docker image build (pull_request) Successful in 31s
CI / Frontend tests, audit, and build (pull_request) Successful in 19m25s
CI / Backend migration, tests, and audit (pull_request) Successful in 43m8s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 35m42s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 37m13s
2805a3e766
Merge branch 'train/3-858' into train/4-846
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 59s
CI / Backend lint (ruff) (pull_request) Successful in 32s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Bot/backend version sync (pull_request) Successful in 30s
CI / Docker image build (pull_request) Successful in 31s
CI / Does this change touch the recording path? (pull_request) Successful in 24s
CI / Bot tests and audit (pull_request) Successful in 5m21s
CI / Backend migration, tests, and audit (pull_request) Successful in 16m36s
CI / Frontend tests, audit, and build (pull_request) Failing after 20m50s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
183b8d9cb5
fix(webapp): the bell knows lane 9's two ownership kinds (#846)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 53s
CI / Backend lint (ruff) (pull_request) Successful in 1m7s
CI / Bot tests and audit (pull_request) Successful in 1m59s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 32s
CI / Does this change touch the recording path? (pull_request) Successful in 47s
CI / Feedback site tests (pull_request) Successful in 1m59s
CI / Backend migration, tests, and audit (pull_request) Successful in 16m28s
CI / Frontend tests, audit, and build (pull_request) Successful in 48m33s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 14m46s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 24m23s
2c4f2ff68b
Lane 7's test asserts the bell's NOTE_KIND covers every kind the server
defines; lane 9 A (#855) added server_ownership_offer and
campaign_handed_on after lane 7 was written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.