79 lines
3.7 KiB
Markdown
79 lines
3.7 KiB
Markdown
---
|
|
title: "Claude ↔ User Question Channel"
|
|
status: active
|
|
audience: [maintainer, contributor]
|
|
language: nl
|
|
last_updated: 2026-05-03
|
|
related: [project-structure.md](./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
|
|
|
|
```mermaid
|
|
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`.
|
|
|
|
---
|
|
|