Skip to content

fix: resolve the five review-pass nits (#269) - #299

Merged
richardmhope merged 5 commits into
mainfrom
fix/269-nits
Aug 1, 2026
Merged

fix: resolve the five review-pass nits (#269)#299
richardmhope merged 5 commits into
mainfrom
fix/269-nits

Conversation

@richardmhope

Copy link
Copy Markdown
Collaborator

Resolves all five verified defects from the July review pass, one commit each.

  1. Audit action collisiongeneral.py emitted audit.settings_updated, the audit router's own action, so SIEM rules couldn't tell "audit forwarding rewired" from "rate limits changed". Now general.settings_updated.
  2. Leaked httpx clientsreset_provider_cache (fired on every LLM- and proxy-settings save) dropped the adapter without closing its SDK client. Both adapters gain aclose(); the sync reset spawns the close onto the running loop and discards the coroutine when no loop exists.
  3. Summary double-firePOST /report/summary now dedupes via queue_summary_pipeline (same shape as queue_llm_pipeline's guard; second click returns already-running), and generate_executive_summary gains the assess-path IntegrityError recovery: the race loser rolls back — discarding its buffered WS event — and returns the winner's row instead of dying as a logged pipeline failure.
  4. Demo seeding — the start inject is now picked deterministically (ordered, and only one the demo member's group can see) instead of unordered .first(), which on a multi-team start node could grab another team's copy and 409 the whole demo load on an empty audience; activation now arms release_at_minutes timers via schedule_exercise_injects (none of the bundled samples define one, so this is future-proofing, not a behaviour change for them).
  5. Blank participate shell — a non-member (or deleted exercise) now gets a visible "Exercise unavailable" card with a route back to the dashboard instead of a permanently blank page; the WS connect and clock are skipped. CSP-safe (component state + existing expression forms only).

Verification: 745 passed / 2 skipped locally (the Playwright suite runs in CI's ui job, including a new non-member regression test), ruff, pyright 0 errors, djlint clean, biome clean on the changed JS. New regression tests: provider-cache close, summary race + endpoint dedupe, multi-team demo seeding, non-member participate page.

Closes #269

🤖 Generated with Claude Code

richardmhope and others added 5 commits August 1, 2026 11:09
general.py reused "audit.settings_updated" — the audit router's action — so a
SIEM rule or ?action= filter could not tell "someone rewired audit forwarding"
from "someone changed rate limits". Now "general.settings_updated".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
reset_provider_cache dropped the adapter without closing its SDK client, and it
fires on every LLM- and proxy-settings save — each reset leaked the client's
httpx pool (FDs/sockets) in a process meant to run indefinitely. Both adapters
gain aclose(); the reset stays sync and spawns the close onto the running loop,
discarding the coroutine when there is none to leak into.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POST /report/summary spawned run_summary_pipeline with no in-flight guard, so a
double-click bought two provider calls racing into ExecutiveSummary's
exercise_id unique constraint, the loser dying as a logged pipeline failure.
queue_summary_pipeline mirrors queue_llm_pipeline's guard, and
generate_executive_summary gains the assess-path IntegrityError recovery:
roll back (discarding the buffered WS event), return the winner's row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es (#269)

create_sample_demo_exercise released the start node's physical inject chosen by
unordered .first(); on a multi-team start node that can be another team's copy,
whose audience is empty (only the demo member's group is enrolled), and
release_inject 409s the whole demo load. It also activated without
schedule_exercise_injects, so a sample's release_at_minutes timers never armed
until a restart or pause/resume. Latent with the bundled single-team-start
samples; a trap for the next sample added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When the exercise API 403s (non-member) or 404s, participantView.init() left
exercise=null with loading=false — both the spinner and the content hid, giving
a fully blank page while a WS connect was still attempted. Authz is unchanged
(the page is shell-only; the API scopes data): init now records a load error,
renders a visible dead end with a route back to the dashboard, and skips the
clock and WS entirely. Covered by a Playwright regression in the CI ui job.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@richardmhope
richardmhope merged commit d903a26 into main Aug 1, 2026
7 checks passed
@richardmhope
richardmhope deleted the fix/269-nits branch August 1, 2026 01:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant