- 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>
5 KiB
title: "Claude ↔ User Question Channel" status: active audience: [maintainer, contributor] language: nl last_updated: 2026-05-03 related: project-structure.md
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):
get_worker_settings→min_quota_pctbash scripts/worker-quota-probe.sh→{ pct, reset_at_iso }worker_heartbeat(last_quota_pct, last_quota_check_at, is_low = pct < min_quota_pct)- Als
is_low: log "wachten tot quota-reset om HH:MM", slaap totreset_at_iso + 5s, herhaal stap 2 - Anders:
wait_for_jobaanroepen
De solo-store houdt lowQuotaTokenIds: Set<string> bij. De NavBar toont een stand-by badge wanneer lowQuotaTokenIds.size >= connectedWorkers && connectedWorkers > 0.