feat: FAPP — Free Appointment Propagation Protocol for psychotherapy discovery

This commit is contained in:
2026-03-31 09:29:41 +00:00
parent e2c04cf0c3
commit cbfa7e16c4
4 changed files with 1261 additions and 0 deletions

154
docs/specs/fapp-protocol.md Normal file
View File

@@ -0,0 +1,154 @@
# 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
## Anti-Spam
1. **Approbation hash binding.** The `approbation_hash` field contains SHA-256 of the therapist's Approbation number. While mesh nodes cannot verify this against a registry, it creates accountability — a therapist's identity is tied to a real credential.
2. **Signature verification.** All SlotAnnounces are Ed25519-signed. Relay nodes reject unsigned or invalid announcements.
3. **Rate limiting.** Relay nodes enforce a maximum announcement rate per therapist address (e.g., max 10 SlotAnnounces per hour per therapist_address).
4. **Sequence-based dedup.** Each therapist maintains a monotonic sequence counter. Relay nodes only accept announces with sequence >= last seen for that therapist.
5. **TTL enforcement.** Expired announcements are garbage collected. Default TTL is 7 days.
6. **Hop limit.** SlotAnnounces have a max_hops field (default 8) to prevent 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.