Scrum4Me/docs/architecture/claude-question-channel.md
Scrum4Me Agent bc6936159d docs(ST-qfpqpxzy): worker-quota-probe.sh + uitgebreide pre-flight runbook + architectuurdoc
- scripts/worker-quota-probe.sh: curl-probe die rate-limit-headers parset en { remaining, limit, pct, reset_at_iso } JSON retourneert
- mcp-integration.md: pre-flight sectie uitgebreid met bash-script voorbeeld, retry-strategie (sleep tot reset+5s), bekende beperking
- claude-question-channel.md: M13 quota-gate flow diagram + pre-flight loop uitleg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 03:55:58 +02:00

5 KiB


Vraag-antwoord-kanaal Claude ↔ user (M11)

Persistent kanaal tussen Claude Code (via MCP) en de actieve Scrum4Me-gebruiker. Wanneer Claude tijdens een implementatie vastloopt op een keuze, schrijft hij een gestructureerde vraag naar claude_questions. Een Postgres-trigger emit op het bestaande scrum4me_changes-kanaal (hergebruik uit M8) met entity: 'question'. De Scrum4Me-app heeft een aparte user-scoped SSE-route die op dit kanaal abonneert, filter't op product-toegang en de notifications-bell in de NavBar voedt. Iedere gebruiker met product-membership kan antwoorden; story-assignee krijgt visuele emphase. Claude leest het antwoord (sync via polling met wait_seconds, of in een latere sessie via get_question_answer) en gaat door.

Sequence

sequenceDiagram
  participant C as Claude (MCP)
  participant DB as Postgres
  participant SC as scrum4me_changes channel
  participant SSE as /api/realtime/notifications
  participant U as Scrum4Me UI (browser)

  C->>DB: INSERT claude_questions (status=open)
  DB->>SC: pg_notify {entity:'question', op:'I', id, ...}
  SC->>SSE: notification (filter: question + product-access)
  SSE->>U: data event → Zustand store upsert → bell badge

  Note over U: Gebruiker klikt bell → Sheet → Modal
  U->>DB: answerQuestion(questionId, answer)<br/>Server Action: atomic updateMany WHERE status='open'
  DB->>SC: pg_notify {entity:'question', op:'U', status:'answered'}
  SC->>SSE: notification
  SSE->>U: data event → store remove → bell badge -1

  Note over C: Optioneel: ask_user_question(wait_seconds) polt elke 2s
  C->>DB: SELECT status FROM claude_questions WHERE id=...
  DB-->>C: status='answered', answer='...'
  C->>C: gaat door met implementatie

Threat-model

Aanval Mitigatie
Race: dubbele submit op zelfde vraag Atomic updateMany WHERE status='open' — één caller ziet count=1, rest count=0 met disambiguatie via second findFirst
Demo-account misbruik requireWriteAccess op MCP-write-tools (PERMISSION_DENIED), early-return op session.isDemo in answerQuestion Server Action, disabled submit + tooltip in AnswerModal
Cross-product leak productAccessFilter op DB-query én SSE-server-side-filter (Set met user's accessible product-IDs)
Cron-endpoint misbruik Authorization: Bearer ${CRON_SECRET} — Vercel injecteert automatisch; faalt 401 als secret niet gezet (geen open endpoint in dev)
Onbeperkte vragen-groei expires_at 24 u + Vercel cron 0 4 * * * (dagelijks; Hobby-plan-limiet) markeert status='expired' → uit notifications-bell
Gevoelige info in logs Logging alleen question_id, nooit vraag- of antwoord-tekst

Waarom hergebruik scrum4me_changes-kanaal

In tegenstelling tot M10 (eigen scrum4me_pairing-kanaal) is M11 een uitbreiding van de bestaande realtime-infra. Voordelen:

  • Eén Postgres-NOTIFY-listener per route i.p.v. twee — minder DB-connecties
  • Solo-realtime + notifications kunnen onafhankelijk evolueren via de entity-key
  • Toekomstige entities (bijv. entity: 'comment', entity: 'mention') hoeven geen nieuw kanaal — alleen een filter-aanpassing in de route die ze wil ontvangen

Risico: een nieuwe entity vergeten te filteren leidt tot lekkage. Mitigatie: expliciet if (payload.entity === 'X') return false in elke SSE-route die betrokken-features niet hoort te zien (zoals de solo-route die entity:'question' weert).

Dit patroon (notification-channel via een bestaande pg_notify-stream) is herbruikbaar — zie docs/patterns/claude-question-channel.md.

Worker quota-gate flow (M13)

De worker_heartbeat MCP-tool gebruikt hetzelfde scrum4me_changes-kanaal met een nieuw event-type worker_heartbeat:

Worker                     scrum4me-mcp              Postgres             SSE-route          NavBar
  |                             |                        |                    |                 |
  |--worker_heartbeat(15,low)-->|                        |                    |                 |
  |                             |--pg_notify('scrum4me_changes', {type:'worker_heartbeat',is_low:true})-->|
  |                             |                        |                    |--data:{...}---->|
  |                             |                        |                    |                 |--stand-by badge

Pre-flight loop (vóór elke wait_for_job):

  1. get_worker_settingsmin_quota_pct
  2. bash scripts/worker-quota-probe.sh{ pct, reset_at_iso }
  3. worker_heartbeat(last_quota_pct, last_quota_check_at, is_low = pct < min_quota_pct)
  4. Als is_low: log "wachten tot quota-reset om HH:MM", slaap tot reset_at_iso + 5s, herhaal stap 2
  5. Anders: wait_for_job aanroepen

De solo-store houdt lowQuotaTokenIds: Set<string> bij. De NavBar toont een stand-by badge wanneer lowQuotaTokenIds.size >= connectedWorkers && connectedWorkers > 0.