v4.3.0 follow-ups: small findings from the build, bundled #534

Closed
opened 2026-09-06 09:54:18 +00:00 by claude-bot · 5 comments
Collaborator

Five small findings surfaced while building v4.3.0 (#514), none of which belonged in the issue that found it. Bundled here rather than filed as five issues; split any that grows.

  • Beat Planner is now redundant (#374 / #305). The Session Prep panel carries the same notes editor; the standalone Beat Planner survives as the roomier version. Decide whether to keep it as a leaf under Session Prep or remove it. Deleting a GM's known workspace was out of scope for #374.
  • audio_processing_started_at column (#381). The processing timer anchors on sessions.updated_at because the pipeline records no start time; an unrelated edit resets it, so it can under-report. A dedicated column set when process_audio takes the job would make the timer exact. One migration.
  • One entry-type taxonomy (#396). TYPE_LABELS for wiki entry types exists in four copies (WikiArticle, CampaignStoryline, WikiProposals, and as LORE_TYPE_LABELS in CampaignDetail). Extract to vocabulary.js or a sibling module so the search results and the next rename touch one file.
  • Campaign invite link versus the instance registration gate (#387). An instance with INVITE_CODE set still refuses a brand-new player who follows a campaign /join/<code> link, because the campaign code and the instance code are different secrets and the login return path carries only the former. The GM guide names this; a fix would let a valid campaign invite satisfy the registration gate for that sign-up.
  • The recording panel's SSE stream on every GM session-page view (#386). The panel opens the live stream (one Redis pub/sub connection for its lifetime) whenever a GM views any session page. Gating it by status reintroduced the visibility holes #386 removed, so it is ungated. Negligible at self-hosted scale; revisit (lazy open on scroll-into-view, or a shared per-tab stream) if a deployment grows.
Five small findings surfaced while building v4.3.0 (#514), none of which belonged in the issue that found it. Bundled here rather than filed as five issues; split any that grows. - [ ] **Beat Planner is now redundant** (#374 / #305). The Session Prep panel carries the same notes editor; the standalone Beat Planner survives as the roomier version. Decide whether to keep it as a leaf under Session Prep or remove it. Deleting a GM's known workspace was out of scope for #374. - [ ] **`audio_processing_started_at` column** (#381). The processing timer anchors on `sessions.updated_at` because the pipeline records no start time; an unrelated edit resets it, so it can under-report. A dedicated column set when `process_audio` takes the job would make the timer exact. One migration. - [ ] **One entry-type taxonomy** (#396). `TYPE_LABELS` for wiki entry types exists in four copies (`WikiArticle`, `CampaignStoryline`, `WikiProposals`, and as `LORE_TYPE_LABELS` in `CampaignDetail`). Extract to `vocabulary.js` or a sibling module so the search results and the next rename touch one file. - [ ] **Campaign invite link versus the instance registration gate** (#387). An instance with `INVITE_CODE` set still refuses a brand-new player who follows a campaign `/join/<code>` link, because the campaign code and the instance code are different secrets and the login return path carries only the former. The GM guide names this; a fix would let a valid campaign invite satisfy the registration gate for that sign-up. - [ ] **The recording panel's SSE stream on every GM session-page view** (#386). The panel opens the live stream (one Redis pub/sub connection for its lifetime) whenever a GM views any session page. Gating it by status reintroduced the visibility holes #386 removed, so it is ungated. Negligible at self-hosted scale; revisit (lazy open on scroll-into-view, or a shared per-tab stream) if a deployment grows.
Author
Collaborator

Picking up two of the five: the audio_processing_started_at column (#381) and the one entry-type taxonomy (#396). Checked first that neither has been done: the column doesn't exist, and the label map now has six copies, not four — WikiNewEntry.jsx and LorePicker.jsx (ENTRY_TYPE_LABELS) have their own too.

The invite-link-vs-registration-gate item is being investigated separately; Beat Planner and the SSE stream stay as they are for now.

Picking up two of the five: the `audio_processing_started_at` column (#381) and the one entry-type taxonomy (#396). Checked first that neither has been done: the column doesn't exist, and the label map now has **six** copies, not four — `WikiNewEntry.jsx` and `LorePicker.jsx` (`ENTRY_TYPE_LABELS`) have their own too. The invite-link-vs-registration-gate item is being investigated separately; Beat Planner and the SSE stream stay as they are for now.
Author
Collaborator

Progress: two items in review.

  • audio_processing_started_at: #640
  • the entry-type taxonomy: #641

The item as written is accurate. The campaign code reaches the OIDC callback only inside next_url in the Redis state (routers/auth.py:124-129). The gate (auth.py:204-211) compares invite_code (always "") with INVITE_CODE, and only after the full IdP round trip. The refusal redirects to /auth-error and discards next_url.

It's worse than that, though. bb77ceb removed the invite-code field from the login page ("site-level invite code gate is redundant") but left the backend gate in place. Nothing in the frontend sends invite_code any more. With INVITE_CODE set, every new sign-up is refused, not just ones arriving from a campaign link. The only way in is hand-typing /auth/login?invite_code=…. The refusal text points at a step that no longer exists.

Proposed fix (no migration, no bot-contract change):

  • In login, when next matches the /join/<code> allowlist, store the campaign code in the OIDC state.
  • At the callback, a new user passes the gate if the instance code matches, or if that code resolves to a live, non-archived campaign, checked at callback time.
  • Recommended: add them to that campaign in the same transaction, so every account admitted this way shows up in the issuing GM's member list.

This needs an owner decision first. Any signed-in user can create a campaign and get a reusable, non-expiring invite code. So "a campaign invite satisfies the gate" effectively means any existing user can admit anyone to the instance, and a leaked /join/ link becomes open registration until the GM regenerates it. How much that matters depends on whether the IdP is closed. The options:

  • A. Any valid campaign invite admits.
  • B. Only invites from campaigns with an admin as GM.
  • C. A toggle (setting or env) on top of A.
  • D. Per-invite expiry or usage caps. Needs a migration and UI.
  • E. Accounts created via invite start pending admin approval.
  • Or retire INVITE_CODE entirely, as bb77ceb's message implied.

Adjacent findings:

  • hmac.compare_digest raises on a non-ASCII invite_code inside the callback's try. The user sees "A database error occurred", and the broad except at auth.py:254-258 logs nothing.
  • docs/API.md:119 documents a 403 where the code returns a 302, and docs/ROADMAP-COMPLETED.md:16 says the gate was removed. Both are wrong.
  • The OIDC callback has no tests at all: not the gate, not first-user-admin, not profile refresh.

Leaving this item open pending that decision.

Progress: two items in review. - `audio_processing_started_at`: #640 - the entry-type taxonomy: #641 ## Invite link vs registration gate: investigated, bug confirmed, and wider than described **The item as written is accurate.** The campaign code reaches the OIDC callback only inside `next_url` in the Redis state (`routers/auth.py:124-129`). The gate (`auth.py:204-211`) compares `invite_code` (always `""`) with `INVITE_CODE`, and only after the full IdP round trip. The refusal redirects to `/auth-error` and discards `next_url`. **It's worse than that, though.** bb77ceb removed the invite-code field from the login page ("site-level invite code gate is redundant") but left the backend gate in place. Nothing in the frontend sends `invite_code` any more. **With `INVITE_CODE` set, every new sign-up is refused**, not just ones arriving from a campaign link. The only way in is hand-typing `/auth/login?invite_code=…`. The refusal text points at a step that no longer exists. **Proposed fix (no migration, no bot-contract change):** - In `login`, when `next` matches the `/join/<code>` allowlist, store the campaign code in the OIDC state. - At the callback, a new user passes the gate if the instance code matches, or if that code resolves to a live, non-archived campaign, checked at callback time. - Recommended: add them to that campaign in the same transaction, so every account admitted this way shows up in the issuing GM's member list. **This needs an owner decision first.** Any signed-in user can create a campaign and get a reusable, non-expiring invite code. So "a campaign invite satisfies the gate" effectively means *any existing user can admit anyone to the instance*, and a leaked `/join/` link becomes open registration until the GM regenerates it. How much that matters depends on whether the IdP is closed. The options: - **A.** Any valid campaign invite admits. - **B.** Only invites from campaigns with an admin as GM. - **C.** A toggle (setting or env) on top of A. - **D.** Per-invite expiry or usage caps. Needs a migration and UI. - **E.** Accounts created via invite start pending admin approval. - **Or** retire `INVITE_CODE` entirely, as bb77ceb's message implied. **Adjacent findings:** - `hmac.compare_digest` raises on a non-ASCII `invite_code` inside the callback's `try`. The user sees "A database error occurred", and the broad `except` at `auth.py:254-258` logs nothing. - `docs/API.md:119` documents a 403 where the code returns a 302, and `docs/ROADMAP-COMPLETED.md:16` says the gate was removed. Both are wrong. - The OIDC callback has no tests at all: not the gate, not first-user-admin, not profile refresh. Leaving this item open pending that decision.
Author
Collaborator

Status of the five items:

  • audio_processing_started_at column (#381): merged in #640.
  • One entry-type taxonomy (#396): merged in #641.
  • Campaign invite link vs the registration gate (#387): moved to #643. The owner chose option D, so invites get expiry, usage limits and per-campaign join approval, and existing codes expire after 7 days. That issue carries the design and the gate fix. The wider hosted-billing context is recorded in #642.
  • Beat Planner redundancy (#374 / #305): not started. Waiting on the v4.5.0 UX direction.
  • Recording panel SSE stream on every GM session view (#386): not started. Explicitly a "revisit if a deployment grows" item, and #642 (hosted) is where that growth would come from.

Leaving this open for the last two.

Status of the five items: - [x] **`audio_processing_started_at` column** (#381): merged in #640. - [x] **One entry-type taxonomy** (#396): merged in #641. - [ ] **Campaign invite link vs the registration gate** (#387): moved to **#643**. The owner chose option D, so invites get expiry, usage limits and per-campaign join approval, and existing codes expire after 7 days. That issue carries the design and the gate fix. The wider hosted-billing context is recorded in **#642**. - [ ] **Beat Planner redundancy** (#374 / #305): not started. Waiting on the v4.5.0 UX direction. - [ ] **Recording panel SSE stream on every GM session view** (#386): not started. Explicitly a "revisit if a deployment grows" item, and #642 (hosted) is where that growth would come from. Leaving this open for the last two.
Author
Collaborator

Checked each of the five against main (2b077778), 2 October.

  1. Beat Planner redundant (#374/#305): done. The standalone planner is gone (no BeatPlanner anywhere in frontend or backend); pages/prep/Beats.jsx says "the old Beat Planner folds in here, #534".
  2. audio_processing_started_at: done (#640). Column at app/models/session.py, set in routers/bot.py, routers/sessions.py, routers/admin.py; the timer reads it in pages/session/read.js.
  3. One entry-type taxonomy: done (#641). src/entryTypes.js, no local copies left, entryTypes.test.js.
  4. Invite link vs INVITE_CODE gate (#387): moved to #643, which is closed. Not re-verified here.
  5. Recording panel's event stream on every GM session view (#386): partly. It no longer opens on every GM session page: useRecording is used only by RecordingPanel.jsx and GmTable.jsx, and the panel is mounted only on a confirmed or live session. Within those two views it still opens unconditionally: no lazy open, no shared stream per tab. The last word here was "revisit if a deployment grows".

So only item 5 is left, as a deliberate deferral. Leaving this open for the owner to close or keep for that.

Checked each of the five against main (2b077778), 2 October. 1. **Beat Planner redundant (#374/#305): done.** The standalone planner is gone (no `BeatPlanner` anywhere in frontend or backend); `pages/prep/Beats.jsx` says "the old Beat Planner folds in here, #534". 2. **`audio_processing_started_at`: done** (#640). Column at `app/models/session.py`, set in `routers/bot.py`, `routers/sessions.py`, `routers/admin.py`; the timer reads it in `pages/session/read.js`. 3. **One entry-type taxonomy: done** (#641). `src/entryTypes.js`, no local copies left, `entryTypes.test.js`. 4. **Invite link vs INVITE_CODE gate (#387): moved** to #643, which is closed. Not re-verified here. 5. **Recording panel's event stream on every GM session view (#386): partly.** It no longer opens on every GM session page: `useRecording` is used only by `RecordingPanel.jsx` and `GmTable.jsx`, and the panel is mounted only on a confirmed or live session. Within those two views it still opens unconditionally: no lazy open, no shared stream per tab. The last word here was "revisit if a deployment grows". So only item 5 is left, as a deliberate deferral. Leaving this open for the owner to close or keep for that.
Author
Collaborator

Closing. Items 1 to 4 are done or moved (see the 2 Oct check). Item 5, the recording panel's event stream, is split into #1247 under the hosted-service milestone, since growth is what would make it matter.

Closing. Items 1 to 4 are done or moved (see the 2 Oct check). Item 5, the recording panel's event stream, is split into #1247 under the hosted-service milestone, since growth is what would make it matter.
Sign in to join this conversation.
No milestone
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#534
No description provided.