Skip to content

Lessons aus realen Prilog-Vorfällen

Jede Regel hier ist aus einem konkreten Fall entstanden. Datum, Symptom, Was-wir-jetzt-tun. Wachsendes Dokument — bei jedem neuen Vorfall einen Eintrag ergänzen.

L-001 · „LIVE" ohne Hand-Test

Datum: 2026-05-31 (Zeugnis Sprint 5/6) Symptom: Sprint wurde als LIVE gestempelt nach: TSC clean + Tests grün + Deploy erfolgreich. User klickte zum ersten Mal als Lehrer auf „Anton Schmidt" und bekam 404. Backend war korrekt, Frontend war korrekt — aber die Klick-Pfade waren nie zusammen durchgegangen worden. Plus: ID-Slices statt Klarnamen überall (Anton war als cmp6s0aor000576qg… zu sehen).

Was wir jetzt machen:

  • LIVE-Stempel nur nach Phase 6 (UX-Audit) der Pflicht-Checkliste
  • Mindestens 1× per Hand pro betroffene Rolle durchgeklickt
  • Real-DB-Probe gegen Live-Tenant
  • Klarnamen statt IDs — id.slice(0, n)… nur als font-mono-Fallback

Memory-Eintrag: feedback_live_acceptance_check.md

L-002 · Stundenplan-Bridge ohne Lehrer-Zuordnung → alles still

Datum: 2026-05-31 (Zeugnis Sprint 7) Symptom: Auf leander hatte 8b 0 InstructionGroups. Die Stundenplan- Bridge fand 0 Lehrer-Zuordnungen → alle 900 SubjectEntries hatten teacherId=null. Lehrer öffnete Editor → kein Fehler, aber er konnte nicht schreiben (Sicht-Resolver verweigerte 403, aber als 404 dargestellt).

Was wir jetzt machen:

  • Stiller Hang = immer Bug. Diagnose-Banner im Editor wenn teacherId=null: „Kein Lehrer zugeordnet — Stundenplan-Bridge fand nichts, bitte Klassenleitung fragen"
  • setup-status-Endpoint mit Coverage-Check: „N/M Lehrer zugeordnet" — wenn < 50 % → rot, sichtbar im DSGVO-Tab

L-003 · 0 approved TextRules → Generator macht nichts

Datum: 2026-05-31 Symptom: Generator-Button erzeugte leeren Text. User sah keinen Fehler, dachte „komisch", schrieb weiter mit leerem Vorschlag.

Was wir jetzt machen:

  • Generator zeigt Diagnose-Banner wenn 0 approved TextRules: „Generator wird leer liefern — erst im Tab Textbausteine welche anlegen + freigeben"
  • Default-Seed beim Modul-Install muss alle Voraussetzungen abdecken oder explizit als „leer-zum-Ausfüllen" markiert sein

L-004 · 404 statt verständlicher Diagnose

Datum: 2026-05-31 Symptom: Sicht-Resolver gab {error:'not_found'} zurück (statt 403, um IDs nicht enumerierbar zu machen). Frontend zeigte Toast „HTTP 404" ohne weitere Hilfe.

Was wir jetzt machen:

  • Backend-Asserter liefern strukturierte Diagnose: { error, message, reasons[], remedies[] }. 404 bleibt für Enumeration-Schutz, aber Body erklärt verständlich.
  • Frontend: AccessDeniedBanner-Komponente rendert die Diagnose ausformuliert mit Reasons + „Was du tun kannst"-Liste
  • Toast für error='no_visibility' unterdrückt (Banner reicht)

L-005 · UserAccount.admin gesetzt, JWT bleibt alt

Datum: 2026-05-31 Symptom: „Zum Admin machen"-Button setzte UserAccount.admin=true, aber die JWT-Rolle wurde nur aus order.adminUsername abgeleitet. Folge: User klickte „Admin", nichts passierte, weil JWT noch ['member'].

Was wir jetzt machen:

  • deriveRoles(tenantId, localPart, adminUsername) prüft beide Quellen (adminUsername + UserAccount.admin)
  • Toast nach „Zum Admin machen": 12s Info „User muss sich neu einloggen, damit das JWT die admin-Rolle bekommt"
  • Generell: jede DB-Änderung, die JWT-Inhalte betrifft, braucht einen Re-Login-Hinweis

L-006 · Import erzeugt nur Person, keine UserAccounts

Datum: 2026-05-31 Symptom: Schule importierte 50 Lehrer via ude-migration. Alle landeten als Person ohne UserAccount. Daher konnte sich niemand einloggen, keine TeacherQualifikation gepflegt werden, Teaching- Section blieb leer.

Was wir jetzt machen:

  • Bulk-Tool „Logins erzeugen" für Personen ohne Account
  • Sidebar-Filter „Ohne Login" + „Mit Login" mit Live-Counts
  • Bei externen Personen in Kontakte-Detail: erklärender Hinweis statt schweigender Section („Person hat keinen Prilog-Login, …")

L-007 · /api/api/ durch doppelten Prefix

Datum: 2026-05-31 Symptom: Neue fetch-Aufrufe machten ${env.platformBaseUrl}/api/.... In Production ist platformBaseUrl='/api'/api/api/... → 404. In Dev verbarg sich der Bug, weil platformBaseUrl='https://api.prilog.chat/api'/api/api/... → ebenfalls 404, aber Symptom war anders.

Was wir jetzt machen:

  • Konvention: immer ${env.platformBaseUrl}/platform/v1/... (ohne zusätzliches /api/)
  • Wenn möglich: requestJson-Helper nutzen statt direkte fetch-Calls
  • In Tests: einmal gegen Production-Konfiguration durchspielen, nicht nur gegen Dev

L-008 · Migration referenziert Tabellen-Namen falsch

Datum: 2026-05-31 Symptom: Migration 0130 hatte REFERENCES "Tenant"("id") (mit großem T). Echte Tabelle heißt tenants (Plural, lowercase). Migration scheiterte auf Live-DB.

Was wir jetzt machen:

  • Vor PR-Merge: Migration einmal auf einer Test-DB (Kopie der Live-DB) durchspielen
  • In Migration-Files konsequent die Prisma @@map-Namen verwenden
  • Tipp: grep "REFERENCES" prisma/migrations/0001_baseline/migration.sql als Referenz nutzen

L-009 · Schema-Migration ohne Backfill für bestehende Daten

Datum: 2026-05-30 (mehrere Fälle) Symptom: Neue NOT NULL-Spalte hinzugefügt, vergessen dass bestehende Reihen leer sind → Migration scheiterte oder die App gibt undefined- Errors auf bestehenden Daten.

Was wir jetzt machen:

  • Jede Migration mit neuer Pflicht-Spalte enthält Backfill-SQL unmittelbar danach (z. B. UPDATE x SET col = ... WHERE col IS NULL)
  • Erst danach ALTER COLUMN SET NOT NULL

L-010 · CI fällt aus (GitHub-Billing) — manueller Deploy

Datum: 2026-05-31 Symptom: GitHub-Actions deaktiviert wegen Billing-Problem. Web-Client- Artifact wurde nicht gebaut. Reconcile-Sweep überschreibt sonst um 03:30 mit altem stable-Tarball.

Was wir jetzt machen:

  • Tarball-Build lokal, scp ins prilog-artifacts-Repo, promote-web-client.sh, Reconcile-Sweep manuell
  • Memory-Eintrag (feedback_webclient_promote_stable.md) erklärt den Pfad
  • Backlog: CI auf anderen Provider migrieren oder lokaler Fallback automatisieren

L-011 · nginx-Pfad-Drift bei docs.prilog.chat

Datum: 2026-05-31 Symptom: docs.prilog.chat lieferte 404 obwohl Files lokal builden + rsynct werden. nginx-root war auf /var/www/prilog_docs/docs/.vitepress/dist konfiguriert, Files lagen flach in /var/www/prilog_docs/.

Was wir jetzt machen:

  • Nach Site-Refactor immer einmal die Live-URL prüfen (curl -sI ...), nicht nur den lokalen Build
  • Memory reference_docs_hosting.md aktuell halten

L-012 · Mehrere Identitäts-Konzepte parallel (Person vs UserAccount vs UserDirectoryEntry)

Datum: laufend (CRM-Foundation-Migration) Symptom: Es gibt 3 Konzepte für „Person ist in unserem System":

  • Person (CRM-Datensatz, optional ohne Login)
  • UserAccount (CRM-v2-Login)
  • UserDirectoryEntry (Legacy-Login)

Je nachdem welcher Endpoint angesteuert wird, wird das eine oder andere abgefragt → Inkonsistenzen.

Was wir jetzt machen:

  • Plan zur Vereinheitlichung in prilog_docs/umsetzung/crm-foundation/
  • Bis Migration durch ist: jeder neue Endpoint mit isCrmV2(tenantId)- Branch nutzen
  • Long-term: nur noch Person + UserAccount, UserDirectoryEntry archivieren

L-013 · TypeScript-Casing-Fehler in Prisma-Enums

Datum: 2026-05-31 Symptom: Code prüfte person.kind !== 'person', aber Prisma-Enum liefert 'PERSON' (Großbuchstaben). TSC fing es nach prisma generate, aber Strict-Compare lieferte vorher false positives.

Was wir jetzt machen:

  • Bei Prisma-Enums immer den generierten Type verwenden (import { PersonKind } from '@prisma/client')
  • prisma generate vor jedem TSC-Lauf

L-014 · Tabellen-Drift bei Tenant-Migration (Backup-Stille 14 Tage)

Datum: 2026-06-01 (Tenant-Board-Anomalie auf shared-2) Symptom: Im Admin-Portal-Tenant-Board waren die Backup-Dots der zwei shared-2-Tenants rot. Diagnose: 9 (rssw) bzw. 14 (test-2) Tage kein verified-Backup, jeder nächtliche Cron-Lauf gleicher Fehler: „tenant-box <slug> existiert nicht".

Root Cause: Bei der Tenant-Migration (shared-1 → shared-2) wurde server_orders.shared_host_id korrekt auf 5 aktualisiert, abertenant_box_registry.shared_host_id blieb auf 1. Der Backup-Cron liest tenant_box_registry → versuchte Backup über Agent auf shared-1 → fand tenant-box dort nicht (sie war auf shared-2 umgezogen) → status='failed'.

Detection-Schwäche: Die Anomalie wurde nicht durch einen Alert bemerkt, sondern erst beim manuellen Tenant-Board-Blick des Owners — 14 Tage nach Beginn. Ohne diese zufällige Sichtung wäre shared-2 mit veralteten Backups in einen tatsächlichen Crash gegangen.

Was wir jetzt machen:

  1. Code-Fix: tenant-migration.service.ts Schritt 6b' aktualisiert tenant_box_registry.shared_host_id mit. — durch Schritt 4 obsolet, Code-Block ist im 0132-Commit wieder entfernt worden.
  2. Drift-Recovery: SQL-One-Shot in der Live-DB hat die zwei stehengebliebenen Einträge korrigiert; danach manuell tenant-box-daily-backup-Cron getriggert → alle drei Tenants verified.
  3. Alert-Cron: Neuer backup-stale-watch-Cron (täglich 07:30 UTC) prüft pro Tenant das jüngste verified-Backup. >24h → Mail an ADMIN_ALERT_EMAIL.
  4. Drift physisch eliminiert (Migration 0132_tenant_box_registry_drift_eliminated, 2026-06-01): tenant_box_registry.shared_host_id/synapse_port/minio_port ganz gedroppt. Truth wandert vollständig nach server_orders (inklusive minio_port). Alle Reader joinen jetzt, alle UNIQUE-Constraints sitzen auf server_orders. Drift mathematisch ausgeschlossen. Konzept: umsetzung/tenant-box-registry-drift-eliminiert.md.

Manifest-Konsequenz:

  • Schema-Drift zwischen zwei Tabellen mit „Wahrheit auf einer Seite" ist ein Anti-Pattern. Plan A: redundante Spalte ganz weg, JOIN. Plan B: Trigger in der DB. Plan C: beide Updates in derselben Transaktion. App- Sync ist die schwächste der drei Optionen — wer immer eine zusätzliche Stelle vergessen kann, vergisst sie.
  • Jede neue „läuft im Hintergrund"-Operation braucht einen Alert für den Failure-Pfad. Status-Polls auf der Karte allein reichen nicht — niemand öffnet das Tenant-Board jeden Tag.

Wie pflegen

Pro Vorfall:

  1. Inkrementeller ID (L-014, L-015, …)
  2. Datum + 1-Zeilen-Symptom
  3. „Was wir jetzt machen" — die abgeleitete Regel oder das Code-Pattern
  4. Falls passt: Memory-Eintrag verlinken

Wachstum ist gewollt — das ist die Inschrift, was schon mal schiefging.