Zuletzt aktualisiert:

iOS-Crash-Reporting-Vertrag (MetricKit, selbstgehostet)

SILV-344 (Sub von SILV-127, Jaqueline-Rollout). Eigener Endpoint, kein Drittanbieter-SDK (Sentry o. Ä.) — selbstgehostet im Projekt-Geist. Macht Crashes/Hangs auf Familien-Geräten sichtbar, ohne Kabel/Xcode (Jaquelines iPhone hängt nie am Rechner).

Auth (gelockt, sicherheitsbegründet)

Session-Cookie der eingeloggten App (ss_session, ADR-16) — der globale auth_guard schützt /api/* ohnehin, kein Sonder-Handling. KEIN X-Internal-Token im Client: der ist ein Server-Secret; ihn ins App-Binary zu legen würde ihn leaken, und ein Public-Internet-Client ist die falsche Trust-Boundary. Die native App trägt das ss_session-Cookie aus dem App-Group-Keychain. Ohne Anmeldung → 401 (kein offener Endpoint).

Datensparsamkeit (gelockt)

Nur Crash-/Hang-Stack + Versionenkeine Nutzerdaten/Diary-Inhalte. MetricKit-Diagnose-Payloads enthalten Stacks/Symbole/Metriken, keinen Nutzerinhalt; was gesendet wird, bestimmt der Client. payload wird als JSON-Text gespeichert und bei 256 KB hart gedeckelt.

Endpoints

Methode + Pfad Tut
POST /api/ios-crashes Diagnose-Payload annehmen. Body s. u. Return {ok, id, type}. Best-effort (MetricKit liefert verzögert + gebündelt beim nächsten App-Start).
GET /api/ios-crashes?limit=50 Sichtbarkeit für Dennis: jüngste Reports schlank (ohne payload-Body, dafür payload_bytes + user_name). limit 1..200.
GET /api/ios-crashes/{id} Voller Report inkl. payload (Stack als JSON-Text). 404 unbekannt.

POST-Body (IosCrashIn, alle Felder optional außer type-Default)

{
  "type": "crash",          // crash|hang|cpu_exception|disk_write_exception|app_launch|diagnostic
                            //   unbekannter Wert → serverseitig auf "diagnostic" normalisiert (rät nicht)
  "app_version": "1.4.0",
  "build": "142",
  "os_version": "iOS 26.0",
  "device_model": "iPhone14,5",   // optional, nicht-personenbezogen
  "occurred_at": "2026-06-21T08:30:00",  // ISO, clientseitig gesammelt (optional)
  "payload": { … }          // schlankes MXDiagnosticPayload.jsonRepresentation()
                            //   bzw. Crash-/Hang-Stack; dict|list|string; ≤256 KB (sonst gekappt)
}

user_id setzt der Server aus der Session (welches Familien-Gerät; null bei internem Token). received_at = Server-Zeit. Kein harter Dedup (MetricKit ist verzögert/gebündelt → best-effort); weicher Schlüssel zur manuellen Sicht = occurred_at + type + build.

Wo landen die Reports

Tabelle ios_crashes in app/data/silverscale.db (im 03:30-DB-Backup + Off-Site-Backup enthalten). Ansehen:

TOK=$(grep -E '^INTERNAL_API_TOKEN=' .env | cut -d= -f2-)
curl -s -H "X-Internal-Token: $TOK" http://127.0.0.1:9300/api/ios-crashes | python3 -m json.tool
curl -s -H "X-Internal-Token: $TOK" http://127.0.0.1:9300/api/ios-crashes/<id>   # voller Stack

(Der X-Internal-Token-Weg ist nur die VPS-interne Sicht; die App selbst sendet per Session-Cookie.) Eine UI-Fläche ist bewusst nicht Teil von SILV-344 — erst Sichtbarkeit, UI später bei Bedarf.

Client (MacClaude, SILV-127)

MXMetricManager-Subscriber registrieren; didReceive diagnosticPayloadsPOST /api/ios-crashes mit dem schlanken JSON (best-effort, beim nächsten App-Start). Auth = vorhandenes ss_session-Cookie der App.