Scrum4Me/docs/architecture/claude-question-channel.md

3.7 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.