Lane 10 PR 8: J2 and J4 walk outside the app; a mail app's Unsubscribe works (#736) #945

Merged
claude-bot merged 6 commits from build/lane10-8-journeys into main 2026-09-30 04:57:48 +00:00
Collaborator

Lane 10 PR 8 (#736). It also fixes #887 item 2. The journeys assert what reaches the fake bot and Mailpit; how it looks in Discord is the bot's golden tests' job (test_faces.py).

App bug found and fixed: one-click unsubscribe never worked. List-Unsubscribe pointed at the SPA page /email/stop?t=. nginx answers a POST there with 405, so a mail app's own Unsubscribe (RFC 8058) stopped nothing, silently.

  • The header now names POST /api/email/stop/one-click?t= (the token is the credential, rate-limited to 10/min). The footer link is still the page.
  • A GET on that address 303s to the page and stops nothing.
  • Unit tests: test_email_templates.py, and two in test_email_routes.py. They fail on main.
  • J2 POSTs List-Unsubscribe=One-Click to the header's address and sees the grid turn off.

J2 (a J2-only player, Ines, with a verified Discord link, so Priya's grid is never touched):

  • Discord group:
    • session_proposed carries number and closes_at;
    • session_confirmed carries number and confirmed_time;
    • a notification_dm goes to Ines's Discord id, and its line matches her bell row;
    • a ❔ "maybe" through the bot API shows on Priya's session page;
    • session_cancelled carries the reason.
  • Email, no Discord:
    • To is only Ines's address;
    • the time shows BST/GMT;
    • both List-Unsubscribe headers are there;
    • I'm in → Confirm → "You're in for Session N", and the session page agrees;
    • Stop → Undo → Stop, then the run-out page, then the grid has Email off while DM stays on.
  • axe on the answer page, the stop page (and its states), and the grid with the DM column.
  • #887 item 2: the bell check pages /api/me/notifications, so no longer depends on the first 20 rows.
  • Not asserted: the survey expected session_confirmed to carry rsvp, but on main only reminders do, and e2e runs no beat.

J4: Start's /record/start carries the campaign's real channel_id, and the reply's announced_in is "channel".

J4b, new (j4b-test-the-bot.spec.js):

  • Test the bot from Session 3's link (D149): launch, running ("Hears Priya… · someone not linked"), four stages Done, a Priya line, the summary, and audio_deleted.
  • Nothing else happens: no other bot request, no /record/start, Priya's bell unchanged, and the session is still confirmed.
  • Close deletes the test. A kept run appears in the list and deletes through its confirm, with axe.
  • D171: a campaign with a guild and no channel, where the announcement lands nowhere. Start is 200 with announced_in: null, the panel says "Recording — but I couldn't post the announcement", and Stop completes it.

Harness:

  • fake_bot writes runtime-generated near-silent WAVs (−44 dBFS, since pure silence is dropped as "Nobody was heard", #348) into audio_temp/tests/{id}/ and hands off. No audio is committed.
  • announced_in in the status, and POST /__e2e/announce-fails.
  • fakebot mounts audio_temp.
  • J2's and J4b's campaigns are made once per stack, because of the 20/hour create limit.

Checks:

  • Local journeys: J0b, J1, J2, J4, J4b and J5 pass in all three projects (94 passed; a J1 flake before this code passed 12/12 on a re-run).
  • ruff 0.4.4 and eslint are clean.
  • The email and notification pytest modules pass (97).
  • Main (with #942 and #944) is merged in cleanly.

Noticed, not changed:

  • the answer-buttons email has no link to the session page;
  • the email's <h2> repeats "Quest Board";
  • session_record_start's docstring still says the bot "refuses" (stale since D171).

🤖 Generated with Claude Code

Lane 10 PR 8 (#736). It also fixes #887 item 2. The journeys assert what reaches the fake bot and Mailpit; how it looks in Discord is the bot's golden tests' job (`test_faces.py`). **App bug found and fixed: one-click unsubscribe never worked.** `List-Unsubscribe` pointed at the SPA page `/email/stop?t=`. nginx answers a POST there with 405, so a mail app's own Unsubscribe (RFC 8058) stopped nothing, silently. - The header now names `POST /api/email/stop/one-click?t=` (the token is the credential, rate-limited to 10/min). The footer link is still the page. - A GET on that address 303s to the page and stops nothing. - Unit tests: `test_email_templates.py`, and two in `test_email_routes.py`. They fail on main. - J2 POSTs `List-Unsubscribe=One-Click` to the header's address and sees the grid turn off. **J2** (a J2-only player, Ines, with a verified Discord link, so Priya's grid is never touched): - **Discord group:** - `session_proposed` carries `number` and `closes_at`; - `session_confirmed` carries `number` and `confirmed_time`; - a `notification_dm` goes to Ines's Discord id, and its `line` matches her bell row; - a ❔ "maybe" through the bot API shows on Priya's session page; - `session_cancelled` carries the reason. - **Email, no Discord:** - To is only Ines's address; - the time shows BST/GMT; - both `List-Unsubscribe` headers are there; - I'm in → Confirm → "You're in for Session N", and the session page agrees; - Stop → Undo → Stop, then the run-out page, then the grid has Email off while DM stays on. - axe on the answer page, the stop page (and its states), and the grid with the DM column. - **#887 item 2:** the bell check pages `/api/me/notifications`, so no longer depends on the first 20 rows. - **Not asserted:** the survey expected `session_confirmed` to carry `rsvp`, but on main only reminders do, and e2e runs no beat. **J4:** Start's `/record/start` carries the campaign's real `channel_id`, and the reply's `announced_in` is `"channel"`. **J4b, new (`j4b-test-the-bot.spec.js`):** - Test the bot from Session 3's link (D149): launch, running ("Hears Priya… · someone not linked"), four stages Done, a Priya line, the summary, and `audio_deleted`. - Nothing else happens: no other bot request, no `/record/start`, Priya's bell unchanged, and the session is still confirmed. - Close deletes the test. A kept run appears in the list and deletes through its confirm, with axe. - **D171:** a campaign with a guild and no channel, where the announcement lands nowhere. Start is 200 with `announced_in: null`, the panel says "Recording — but I couldn't post the announcement", and Stop completes it. **Harness:** - `fake_bot` writes runtime-generated near-silent WAVs (−44 dBFS, since pure silence is dropped as "Nobody was heard", #348) into `audio_temp/tests/{id}/` and hands off. No audio is committed. - `announced_in` in the status, and `POST /__e2e/announce-fails`. - `fakebot` mounts `audio_temp`. - J2's and J4b's campaigns are made once per stack, because of the 20/hour create limit. **Checks:** - Local journeys: J0b, J1, J2, J4, J4b and J5 pass in all three projects (94 passed; a J1 flake before this code passed 12/12 on a re-run). - ruff 0.4.4 and eslint are clean. - The email and notification pytest modules pass (97). - Main (with #942 and #944) is merged in cleanly. **Noticed, not changed:** - the answer-buttons email has no link to the session page; - the email's `<h2>` repeats "Quest Board"; - `session_record_start`'s docstring still says the bot "refuses" (stale since D171). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
List-Unsubscribe named the /email/stop page, which nginx serves as static
files and answers a POST with 405, so RFC 8058's one-click POST stopped
nothing. The header now names POST /api/email/stop/one-click?t=, and a GET
of it redirects to the page. Found by J2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/lane10-8-journeys
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m41s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Bot tests and audit (pull_request) Successful in 1m57s
CI / Does this change touch the recording path? (pull_request) Successful in 25s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 49s
CI / Bot/backend version sync (pull_request) Successful in 1m10s
CI / Frontend (shard 1/2) (pull_request) Successful in 21m25s
CI / End-to-end journeys (phone) (pull_request) Successful in 7m44s
CI / End-to-end journeys (desktop) (pull_request) Successful in 8m29s
CI / Frontend (shard 2/2) (pull_request) Successful in 4m47s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 12m43s
CI / Backend migration, tests, and audit (pull_request) Successful in 18m58s
48521a9ab3
Sign in to join this conversation.
No description provided.