Zwei Festlegungen, beide in docs/13-entscheidungen.md begründet.
case.delete ist gestrichen. Der Lebenszyklus kennt ARCHIVED und SEALED, und
eine Akte mit Verlaufseinträgen liesse sich ohnehin nicht hart löschen, weil
CaseEntry nach Regel 2 unlöschbar ist und der Fremdschlüssel das Kaskadieren
blockiert. Das ist die konsequente Fortsetzung von Regel 2 eine Ebene höher.
Niemand klassifiziert eine Akte höher als die eigene Freigabestufe. Sonst
entstehen Akten, die ihr Urheber nicht mehr sehen kann, und die Frage "wer
konnte das lesen" wird im Nachhinein schwer beantwortbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lesen war geregelt, Schreiben nicht: bisher stand nirgends, dass jemand mit
Stufe 2 keine Stufe-4-Akte anlegen darf. Ohne die Regel erzeugt man Akten, die
man selbst nicht mehr sehen kann, und umgeht die Sichtbarkeitsprüfung.
Festgehalten in docs/03-auth-und-rechte.md, als Aufgaben in Phase 2:
Klassifizierung nie über der eigenen Stufe, Ändern und Löschen nur mit
Lesezugriff, Umklassifizierung mit SYSTEM-Eintrag und AuditLog. Eine
Herabstufung ist der heikelste Vorgang im System — sie macht Inhalte für Leute
sichtbar, die sie vorher nicht sehen durften.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Testläufe hatten dort 70 erfundene Rollen, 112 Nutzer und 40 Akten
hinterlassen. Die Rollenverwaltung zeigte deshalb "Gast" siebzehnmal, echte
Rollen doppelt und in willkürlicher Reihenfolge.
Aufräumen allein reicht nicht: CaseEntry, CaseEntryVersion und AuditLog lassen
sich nach harter Regel 2 nicht löschen. Was ein Test dort anlegt, bleibt für
immer stehen — in Phase 2 hätte das die Fallliste unbrauchbar gemacht.
Tests laufen deshalb gegen <datenbank>_test auf demselben Server. pnpm test legt
sie an, migriert und seedet sie vorher. Über TEST_DATABASE_URL lässt sich ein
anderes Ziel setzen.
Dazu die Abschnitte der Rollenverwaltung in der Reihenfolge, in der die
Abteilung ihre Rollen aufzählt: Ränge, Zugehörigkeit, Zugang. Innerhalb eines
Abschnitts entscheidet die Position auf dem Discord-Server, oben die höchste.
Damit steht die Hierarchie an einer Stelle, die ohnehin gepflegt wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auth.js übernimmt nur den OAuth-Ablauf mit state und PKCE, Scope identify. Das
Zugangstoken des Nutzers wird danach verworfen: die Rollen holt der Bot mit
seinem eigenen Token. Die Sitzung steht in der Datenbank, im Cookie liegt nur
userId und sessionId. Ohne die Zeile in Session ist das Cookie wertlos, und nur
so lässt sich eine Sitzung zurückziehen.
Wer sich anmelden darf, steckt in lib/auth/signInPolicy.ts statt im Callback:
das ist die Regel, an der alles hängt, und sie gehört einzeln prüfbar. Kein
Guild-Mitglied, kein Zugang. Keine Rolle mit grantsMembership, kein Zugang.
Notfall-Admins umgehen die Rolle, aber nicht die Guild-Mitgliedschaft.
RoleMapping trägt jetzt grantsMembership, category und isPresent. Der Worker
spiegelt jede Rolle der Guild hierher, damit im Adminbereich nichts von Hand
eingetragen werden muss. Gelöschte Rollen werden nicht entfernt, sondern auf
isPresent = false gesetzt: beim Umzug auf den echten Server bleiben sie als
Vorlage stehen.
Der Worker gleicht über Gateway-Ereignisse und zusätzlich alle fünf Minuten
voll ab. Verliert jemand seine AfiS-Rolle, beendet er dessen Sitzungen sofort
und setzt isActive auf false — der Datensatz bleibt, damit Autorenangaben in
alten Akten gültig bleiben. Das passiert im Worker und nicht in der Web-App,
weil es dort erst beim nächsten Request des Betroffenen griffe.
Zwei Widersprüche der Dokumente aufgelöst, festgehalten in
docs/13-entscheidungen.md: der Ausschluss schlägt auch den Admin (das
Codebeispiel in 03-auth-und-rechte.md übergeht ihn, die Reihenfolge eine Zeile
darüber nicht), und versiegelte Akten fallen im Where-Fragment heraus statt in
einer Prüfung danach.
45 Tests, davon die Negativfälle aus docs/03 gegen eine echte Datenbank: Stufe 2
sieht die Stufe-4-Akte nicht und findet sie auch nicht in der Trefferzahl, ein
Ausgeschlossener mit Stufe 5 kommt nicht heran, ein Rollenentzug beendet die
Sitzungen, und ein Ausfall der Discord-API sieht nicht aus wie eine verweigerte
Mitgliedschaft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Discord-Server hat drei Sorten Rollen, die Dokumente kannten nur eine:
Zugehörigkeit (AfiS, Personalabteilung) als Türsteher, Rangrollen für die
Rechte, Zugangsrollen für die Freigabestufe. Das bestehende RoleMapping trägt
das fast, es fehlt nur der Türsteher. Die beiden Felder dafür kommen mit
Phase 1, die Struktur ist in docs/13-entscheidungen.md festgehalten.
Die Freigabestufen heißen jetzt wie die Zugangsrollen: Unclassified,
Restricted Access, Confidential Access, Sensitive Access, Critical Operations,
Executive Oversight. Fachbegriffe der Domäne bleiben englisch, auch in einer
deutschen Oberfläche.
Die Bezeichnungen sind damit Daten in ClearanceLevelConfig, nicht mehr
Oberflächentext in lib/i18n/de.ts: sie sind im Adminbereich änderbar, und was
der Leiter ändern kann, gehört nicht in eine Übersetzungsdatei. StampMark
bekommt den Namen hereingereicht.
Vier neue Tests prüfen den Zustand, den der Seed herstellt, gegen die
Datenbank statt gegen eine Konstante im Code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CLAUDE.md und README.md verweisen auf docs/, die Dateien lagen aber im
Wurzelverzeichnis. Verschoben.
docs/13-entscheidungen.md hält fest, wo die Dokumente sich widersprechen und
wie es aufgelöst wurde. Der wichtigste Punkt: 04-datenmodell.md verlangt eine
Regel, die jedes UPDATE auf CaseEntryVersion verwirft, 05-akten-und-vorlagen.md
verlangt, dass eine Umklassifizierung rückwirkend auf alle Versionen wirkt.
Beides zusammen geht nicht. Aufgelöst über einen Trigger, der die Tabelle
anhängend hält, aber das Anheben von minClearance zulässt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>