Files
quicproquo/docs/specs/fapp-protocol.md
Christian Nennemann 56331632fd feat(fapp): add security model + profile_url for verification
docs/specs/fapp-security.md:
- Full threat model for patient protection
- 3-level verification roadmap (transparency → endorsements → registry)
- UI warning mockups
- Technical implementation plan
- Honest assessment of limitations

SlotAnnounce changes:
- Added profile_url field for therapist verification
- New with_profile() constructor
- profile_url included in signature

docs/specs/fapp-protocol.md:
- Added Security & Anti-Fraud section
- Link to full security spec
2026-04-01 07:56:19 +02:00

180 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FAPP — Free Appointment Propagation Protocol
## Purpose
Decentralized psychotherapy appointment discovery over the QuicProQuo mesh network.
In Germany, finding a psychotherapist takes 36 months. The KV (Kassenärztliche Vereinigung) system artificially limits slot visibility. FAPP enables licensed therapists to directly announce free appointment slots into a decentralized mesh, where patients can discover and reserve them without a central registry.
## Privacy Model
- **Therapist identity is public.** Therapists are licensed professionals (Approbation). Their mesh identity is linked to a hashed Approbationsurkunde number. This is intentional — patients need to verify they are booking with a real therapist.
- **Patient queries are anonymous.** Patients never reveal their identity when searching. SlotQuery messages carry no identifying information. Only when a patient decides to reserve a slot do they establish an E2E-encrypted channel to the therapist — and even then, the mesh sees only encrypted traffic to the therapist's address.
## Capability Flags
FAPP extends the announce.rs capability bitfield:
| Flag | Value | Description |
|------|-------|-------------|
| `CAP_FAPP_THERAPIST` | `0x0100` | Node is a licensed therapist publishing slots |
| `CAP_FAPP_RELAY` | `0x0200` | Node caches and relays FAPP slot announcements |
| `CAP_FAPP_PATIENT` | `0x0400` | Node can issue anonymous slot queries |
## Message Types
### 1. SlotAnnounce
Therapist publishes free time slots into the mesh.
**Fields:**
- `id: [u8; 16]` — Unique announcement ID
- `therapist_address: [u8; 16]` — MeshAddress of the therapist node
- `fachrichtung: Vec<Fachrichtung>` — Therapy specializations offered
- `modalitaet: Vec<Modalitaet>` — Session modalities (Praxis, Video, Hybrid)
- `kostentraeger: Vec<Kostentraeger>` — Accepted payment/insurance types
- `location_hint: String` — PLZ (postal code) only, never exact address
- `slots: Vec<TimeSlot>` — Available time slots
- `approbation_hash: [u8; 32]` — SHA-256 of the therapist's Approbation number
- `sequence: u64` — Monotonically increasing per therapist (dedup/supersede)
- `ttl_hours: u16` — Time-to-live in hours (default: 168 = 7 days)
- `timestamp: u64` — Unix seconds at creation
- `signature: [u8; 64]` — Ed25519 signature over all fields except signature and hop_count
- `hop_count: u8` — Current propagation hop count
- `max_hops: u8` — Maximum propagation hops
**Propagation:** Like MeshAnnounce — flooded to neighbors, deduped by `(therapist_address, sequence)`. Higher sequence supersedes lower. Expired announcements (timestamp + ttl_hours exceeded) are dropped.
### 2. SlotQuery
Patient requests available slots matching criteria. Anonymous — no patient identity attached.
**Fields:**
- `query_id: [u8; 16]` — Random query identifier for response correlation
- `fachrichtung: Option<Fachrichtung>` — Filter by specialization
- `modalitaet: Option<Modalitaet>` — Filter by modality
- `kostentraeger: Option<Kostentraeger>` — Filter by insurance type
- `plz_prefix: Option<String>` — Filter by PLZ prefix (e.g. "80" for München area)
- `earliest: Option<u64>` — Earliest acceptable slot (Unix seconds)
- `latest: Option<u64>` — Latest acceptable slot (Unix seconds)
- `slot_type: Option<SlotType>` — Filter by appointment type
- `max_results: u8` — Maximum number of results requested
- `hop_count: u8` — Current hop count
- `max_hops: u8` — Maximum query propagation hops
- `return_path: Vec<[u8; 16]>` — Onion-style return path (mesh addresses)
**Propagation:** Forwarded like announces but with shorter TTL. Relay nodes with cached SlotAnnounces can respond directly.
### 3. SlotResponse
Matching slots returned to the querier via the return path.
**Fields:**
- `query_id: [u8; 16]` — Correlates to the original SlotQuery
- `matches: Vec<SlotAnnounce>` — Matching slot announcements (full, so patient can verify signatures)
### 4. SlotReserve
Patient claims a specific slot. E2E encrypted to the therapist.
**Fields:**
- `slot_announce_id: [u8; 16]` — ID of the SlotAnnounce being reserved
- `slot_index: u16` — Index into the SlotAnnounce's slots vector
- `patient_ephemeral_key: [u8; 32]` — X25519 ephemeral public key for reply encryption
- `encrypted_contact: Vec<u8>` — Patient contact info, encrypted to therapist's key
### 5. SlotConfirm
Therapist confirms or rejects a reservation.
**Fields:**
- `slot_announce_id: [u8; 16]` — Original SlotAnnounce ID
- `slot_index: u16` — Slot index
- `confirmed: bool` — Whether the reservation is accepted
- `encrypted_details: Vec<u8>` — Appointment details, encrypted to patient's ephemeral key
## Data Model
### Fachrichtung (Therapy Specialization)
| Variant | Description |
|---------|-------------|
| `Verhaltenstherapie` | Cognitive behavioral therapy (CBT) |
| `TiefenpsychologischFundiert` | Psychodynamic therapy |
| `Analytisch` | Psychoanalysis |
| `Systemisch` | Systemic therapy |
| `KinderJugend` | Child and adolescent psychotherapy |
### Modalitaet (Session Modality)
| Variant | Description |
|---------|-------------|
| `Praxis` | In-person at the therapist's practice |
| `Video` | Video session (Videosprechstunde) |
| `Hybrid` | Either in-person or video |
### Kostentraeger (Insurance/Payment)
| Variant | Description |
|---------|-------------|
| `GKV` | Gesetzliche Krankenversicherung (statutory health insurance) |
| `PKV` | Private Krankenversicherung (private health insurance) |
| `Selbstzahler` | Self-pay |
### SlotType (Appointment Type)
| Variant | Description |
|---------|-------------|
| `Erstgespraech` | Psychotherapeutische Sprechstunde (initial consultation) |
| `Probatorik` | Probatorische Sitzungen (trial sessions) |
| `Therapie` | Regular therapy session |
| `Akut` | Akutbehandlung (acute/crisis treatment) |
### TimeSlot
- `start_unix: u64` — Start time in Unix seconds
- `duration_minutes: u16` — Duration (typically 50 or 25 minutes)
- `slot_type: SlotType` — Type of appointment
## Security & Anti-Fraud
> **See [fapp-security.md](fapp-security.md) for the full security model.**
### Patient Protection
Patients are vulnerable. FAPP must protect against fraudulent "therapists":
| Threat | Mitigation |
|--------|------------|
| Fake Therapist | `profile_url` for cross-verification, UI warnings |
| Impersonation | Ed25519 signatures, endorsement system (planned) |
| Data Harvesting | Anonymous queries, no patient identity in protocol |
| Financial Fraud | "Never pay upfront" warnings, reputation (planned) |
### Verification Levels
| Level | Mechanism | Trust |
|-------|-----------|-------|
| 0 | None — only mesh signature | Low |
| 1 | Endorsement by trusted relay | Medium |
| 2 | Registry verification (KBV) | High |
**Current implementation:** Level 0 with `profile_url` for transparency.
### Anti-Spam
1. **Approbation hash binding.** The `approbation_hash` field contains SHA-256 of the therapist's Approbation number. Creates accountability — therapist identity tied to real credential.
2. **Signature verification.** All SlotAnnounces are Ed25519-signed. Relay nodes reject unsigned or invalid announcements.
3. **Rate limiting.** Relay nodes enforce max 10 SlotAnnounces per hour per therapist_address.
4. **Sequence-based dedup.** Monotonic counter; relays only accept sequence >= last seen.
5. **TTL enforcement.** Expired announcements are garbage collected. Default 7 days.
6. **Hop limit.** max_hops field (default 8) prevents infinite propagation.
## Wire Format
All FAPP messages use CBOR serialization (ciborium), consistent with MeshEnvelope and MeshAnnounce.
## No Central Registry
Slots live exclusively in the mesh. Relay nodes with `CAP_FAPP_RELAY` cache active SlotAnnounces and respond to queries. There is no central database, no API server, no single point of failure. The mesh IS the registry.