Protokoll im Adminbereich, ein Knopf für das ganze Blatt

Die restlichen zwei Änderungswünsche.

**Protokoll.** „Wer was wo macht" stand seit dem ersten Tag in AuditLog —
harte Regel 3 sorgt dafür. Was fehlte, war die Ansicht darauf. /admin/protokoll
ist genau das und keine zweite Aufzeichnung: filterbar nach Person, Aktion,
Art und Zeitraum, rückwärts blätternd.

Nur für die Leitung. Eine Zeile nennt Aktenzeichen und Namen, und schon die
Existenz einer Zeile verrät, dass es einen Vorgang gibt; ein gefiltertes
Protokoll für alle wäre ein Weg, die Sichtbarkeitsregeln von hinten
aufzurollen. Aus demselben Grund werden Kennungen ohne Sichtbarkeitsfilter zu
Aktenzeichen aufgelöst — ein Protokoll aus UUIDs liest niemand, und eines, das
niemand liest, ist keine Kontrolle.

**Speichern.** Jedes Feld trug seinen eigenen Knopf: ein Arbeitsvertrag mit
zwölf Feldern hiess zwölfmal speichern. Der Knopf steht jetzt am Blatt.

Geschrieben wird trotzdem je Feld und nicht als Transaktion, und das folgt aus
dem Datenmodell: Abschnitte sind einzeln versioniert, weil mehrere Leute
gleichzeitig an einer Akte arbeiten. Als Transaktion blockierte ein einziges
zwischenzeitlich geändertes Feld das ganze Blatt — elf gute Änderungen verloren,
weil jemand das zwölfte angefasst hat. So geht durch, was durchgeht, und die
Rückmeldung sagt, was offen blieb und wo. Lichtbilder bleiben ausgenommen: ein
Upload mit Prüfkette ist keine Feldänderung.

lib/audit.ts liegt jetzt als lib/audit/write.ts neben lib/audit/query.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Harlekin
2026-09-03 15:16:10 +02:00
co-authored by Claude Opus 5
parent ef377f0123
commit 5f9f5d99fb
40 changed files with 1005 additions and 42 deletions
+42
View File
@@ -20,6 +20,8 @@ Wo eine Entscheidung fachlich anders ausfallen kann, steht das ausdrücklich dab
| `04-datenmodell.md` nennt zugleich `CREATE SEQUENCE case_number_2026` und die Tabelle `CaseCounter(year, lastNumber)` mit `SELECT … FOR UPDATE` | `CaseCounter`. Eine Sequenz pro Jahr müsste jedes Jahr von Hand angelegt werden, und der Abteilungsleiter ist ausdrücklich nicht technisch |
| Das rohe SQL benutzt `search_vector` und `updated_at` in Schlangenschrift, die Prisma-Modelle Höckerschrift | Prisma-Voreinstellung behalten: Tabellen in Höckerschrift-Pascal, Spalten in Höckerschrift. Das SQL quotet entsprechend (`"searchVector"`, `"updatedAt"`) |
| Kein Modell für Kanäle, obwohl `ChatMessage.channelId` existiert und `06-chat-und-realtime.md` Kanäle mit eigener Mindeststufe verlangt | Modell `Channel` ergänzt, dazu `ChatReaction` für den festen Reaktionssatz |
> Nachtrag: `Channel` ist inzwischen wieder entfernt, siehe „Freie Kanäle sind entfallen" weiter unten.
| `/api/health` soll den Discord-Gateway prüfen, aber die Web-App fragt nie live bei Discord an | Modell `ServiceHeartbeat`. Der Worker schreibt alle 30 Sekunden seinen Zustand, `web` liest ihn |
## Der wichtigste Widerspruch
@@ -1003,3 +1005,43 @@ Profilbild. Das Bild kommt von Discord — dort liegt es ohnehin, die Anmeldung
dieselbe Stelle, und ein eigener Zwischenspeicher wäre ein Dienst mehr im Stack für ein Bild
von 64 Pixeln. Dafür steht `cdn.discordapp.com` in der Inhaltsrichtlinie. Wer kein Bild hat,
bekommt seine Initialen; ein Platzhaltergesicht lädt nichts nach und sagt nichts.
### Das Protokoll ist eine Ansicht, keine neue Spur
„Wer was wo macht" stand vom ersten Tag an in `AuditLog` — harte Regel 3 aus CLAUDE.md sorgt
dafür, dass jede schreibende Aktion und jeder Lesezugriff ab Stufe 3 eine Zeile erzeugt. Was
fehlte, war die Ansicht darauf. `/admin/protokoll` ist genau das und **nicht** eine zweite
Aufzeichnung.
Zwei Festlegungen:
**Nur die Leitung.** Eine Protokollzeile nennt Aktenzeichen und Namen, und schon die Existenz
einer Zeile verrät, dass es einen Vorgang gibt. Ein „gefiltertes Protokoll für alle" wäre ein
Weg, die Sichtbarkeitsregeln von hinten aufzurollen: aus den Lücken liesse sich lesen, was man
nicht sehen darf. Deshalb `requirePermission('admin')` und sonst nichts.
**Kennungen werden aufgelöst, ohne Sichtbarkeitsfilter.** Aus `Case:0192…` wird
`AfiS-2026-0042`. Ohne das wäre die Ansicht eine Spalte UUIDs, die niemand liest — und ein
Protokoll, das niemand liest, ist keine Kontrolle. Das ist die Ausnahme, die es geben muss,
damit die Leitung prüfen kann, ob die Regeln eingehalten wurden. Sie ist genau deshalb auf die
Leitung beschränkt.
Zeilen, deren Ziel sich nicht auflösen lässt — ein Exportauftrag etwa —, bleiben mit ihrer
rohen Kennung stehen. Ein Protokoll mit Lücken wäre keins.
### Ein Knopf für das ganze Blatt
Jedes Feld trug seinen eigenen Speichern-Knopf. Einen Arbeitsvertrag mit zwölf Feldern
auszufüllen hiess zwölfmal speichern, und wer eines vergass, merkte es erst beim Nachlesen.
Der Knopf steht jetzt am Blatt.
Gespeichert wird trotzdem **je Feld und nicht als Transaktion**. Das ist kein halber Umbau,
sondern folgt aus dem Datenmodell: Abschnitte sind einzeln versioniert, weil an einer Akte
mehrere Leute gleichzeitig arbeiten. Wäre das Speichern eine Transaktion, blockierte ein
einziges zwischenzeitlich geändertes Feld das ganze Blatt — elf gute Änderungen gingen
verloren, weil jemand anderes das zwölfte angefasst hat. Stattdessen geht durch, was durchgeht,
und die Rückmeldung sagt, was offen blieb und wo. Der Fassungsvergleich steht weiterhin am
betroffenen Feld.
Lichtbilder bleiben ausgenommen: ein Bild wird hochgeladen und geprüft, das ist ein eigener
Vorgang mit eigener Prüfkette und keine Feldänderung.