feat(laeufe): Vier-Augen-Freigabe (#040)
Verlangt eine Vorlagen-Version eine Freigabe, laesst sich ein Lauf erst abschliessen, wenn ein Zweiter ihn freigegeben hat. Das braucht zweierlei, und beides zusammen: das neue Recht `freigeben` (ADMIN und REDAKTEUR, nicht TECHNIKER) und dass man nicht an dem Lauf gearbeitet hat. Als gearbeitet zaehlt: angelegt, einen Schritt gesetzt oder abgeschlossen. Eine Zuweisung allein nicht — waere die Regel hier strenger, blockierte sie kleine Teams ohne Gewinn. Auch ein Administrator kann sich nicht selbst freigeben, und das ist der ganze Sinn. Die Pruefung sitzt in `hatDaranGearbeitet` an einer Stelle, wie `laufMussVeraenderbarSein`; die wichtigste Haelfte — der Ersteller — haelt zusaetzlich ein CHECK in der Datenbank. Zurueckweisen verlangt eine Begruendung, setzt den Lauf auf IN_ARBEIT und nimmt eine erteilte Freigabe zurueck. Kein fuenfter LaufStatus: ein neuer Enum-Wert haette jeden Codepfad betroffen, der heute vier Faelle kennt. Sobald jemand abzuschliessen versucht und die Freigabe fehlt, bekommen die Freigabeberechtigten eine Mail — ein anderer Empfaengerkreis als bei jeder anderen Meldung, und ohne die, die gearbeitet haben. Die Aufforderung entsteht ausserhalb der Transaktion: innerhalb wuerde sie mit dem Fehler zurueckgerollt, und dann wartet ein Lauf auf etwas, von dem niemand weiss. Ein partieller Unique-Index haelt sie bei mehreren Versuchen auf einmal. Die Pflicht haengt an der Version, nicht an der Vorlage: eine veroeffentlichte Version ist unveraenderlich, ein laufender Lauf behaelt also seine Regel. Beim Bearbeiten und Duplizieren wandert sie mit — das war die unmittelbare Lehre aus #039, wo die tiefe Kopie zwei Felder stillschweigend verworfen hatte. 1526 Vitest-Tests (vorher 1496), 115 E2E-Ablaeufe gruen. Klickprobe am gebauten Stand mit elf Pruefungen; drei rote Punkte darin waren meine eigenen Testfehler, einer davon lehrreich: ein reload() direkt nach einer Server Action trifft den Router-Cache von Next und zeigt kurz den alten Stand. Closes #40 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a2330ef682
commit
d30bb6d235
@@ -1,10 +1,10 @@
|
||||
# #040 M24 – Vier-Augen-Freigabe
|
||||
|
||||
**Status:** offen
|
||||
**Status:** erledigt
|
||||
**Im Tracker:** –
|
||||
**Meilenstein:** v0.3.0
|
||||
**Labels:** phase-3, backend, frontend
|
||||
**Geschätzt:** 8 h **Tatsächlich:** –
|
||||
**Geschätzt:** 8 h **Tatsächlich:** 3 h 30 min
|
||||
|
||||
## Ziel
|
||||
|
||||
@@ -17,52 +17,111 @@ ein **Zweiter** ab — nachweisbar im Protokoll.
|
||||
|
||||
## Zu entscheiden
|
||||
|
||||
- [ ] **Freigabe am Lauf oder am Schritt?** Empfehlung: **am Lauf**, gesetzt von der Vorlage.
|
||||
- [x] **Freigabe am Lauf oder am Schritt?** Empfehlung: **am Lauf**, gesetzt von der Vorlage.
|
||||
Am Schritt wäre feiner und verführt zu Checklisten, in denen an fünf Stellen jemand warten
|
||||
muss; am Lauf ist es eine Aussage über die Arbeit als Ganzes und erklärt sich von selbst.
|
||||
Schritt-Freigaben bleiben möglich, wenn sich der Bedarf zeigt — `VorlagenSchritt.konfig` ist
|
||||
ein Json.
|
||||
- [ ] **Wer darf freigeben?** Empfehlung: ein eigenes Recht `freigeben`, standardmäßig bei `ADMIN`
|
||||
- [x] **Wer darf freigeben?** Empfehlung: ein eigenes Recht `freigeben`, standardmäßig bei `ADMIN`
|
||||
und `REDAKTEUR` — **nicht** bei `TECHNIKER`. Begründung: die Vier-Augen-Regel soll nicht
|
||||
davon abhängen, dass zufällig ein zweiter Techniker greifbar ist; und wer sie umgehen will,
|
||||
braucht dann ein zweites Konto mit mehr Rechten, was im Protokoll steht.
|
||||
- [ ] **Darf der Bearbeiter sich selbst freigeben?** **Nein**, und das ist der ganze Sinn. Geprüft
|
||||
- [x] **Darf der Bearbeiter sich selbst freigeben?** **Nein**, und das ist der ganze Sinn. Geprüft
|
||||
wird gegen `Schrittergebnis.aktualisiertVonId` **und** `Lauf.erstelltVonId`: wer irgendetwas
|
||||
daran getan hat, gibt nicht frei. Ein `ADMIN`, der allein im Haus ist, kommt damit nicht
|
||||
weiter — das ist eine bewusste Härte, und die Anleitung sagt es.
|
||||
- [ ] **Was, wenn die Freigabe verweigert wird?** Empfehlung: ein Zustand „zurückgewiesen“ mit
|
||||
- [x] **Was, wenn die Freigabe verweigert wird?** Empfehlung: ein Zustand „zurückgewiesen“ mit
|
||||
Pflichtnotiz. Der Lauf geht zurück auf `IN_ARBEIT`, die Notiz steht im Protokoll und an der
|
||||
Abarbeitungsansicht. Kein eigener `LaufStatus` — das wäre eine Änderung an einem Enum, das
|
||||
vier Jahre halten soll; es genügt ein Feld plus Protokolleintrag.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
- [ ] Migration: `freigabePflicht Boolean` an `VorlagenVersion`, dazu `freigegebenVonId`,
|
||||
- [x] Migration: `freigabePflicht Boolean` an `VorlagenVersion`, dazu `freigegebenVonId`,
|
||||
`freigegebenAm` und `freigabeNotiz` am `Lauf`; CHECK, dass `freigegebenVonId` und
|
||||
`freigegebenAm` zusammen gesetzt sind (wie bei `abgeschlossenAm`)
|
||||
- [ ] CHECK: `freigegebenVonId` ist **nicht** `erstelltVonId` — die Regel gehört in die Datenbank,
|
||||
- [x] CHECK: `freigegebenVonId` ist **nicht** `erstelltVonId` — die Regel gehört in die Datenbank,
|
||||
nicht nur in den Dienst (CLAUDE.md)
|
||||
- [ ] Recht `freigeben` in `RECHTE`, Rollenmatrix-Test erweitern
|
||||
- [ ] Dienst: `laufFreigeben(laufId, benutzer, notiz?)` und `laufZurueckweisen(laufId, benutzer,
|
||||
- [x] Recht `freigeben` in `RECHTE`, Rollenmatrix-Test erweitern
|
||||
- [x] Dienst: `laufFreigeben(laufId, benutzer, notiz?)` und `laufZurueckweisen(laufId, benutzer,
|
||||
notiz)`; Abschluss verlangt die Freigabe, wenn die Version sie fordert
|
||||
- [ ] Die Prüfung „wer hat daran gearbeitet“ an **einer** Stelle, wie `laufMussVeraenderbarSein`
|
||||
- [ ] Benachrichtigung: ein neuer `Meldeanlass` `LAUF_FREIGABE_OFFEN` an die Rollen mit dem Recht —
|
||||
- [x] Die Prüfung „wer hat daran gearbeitet“ an **einer** Stelle, wie `laufMussVeraenderbarSein`
|
||||
- [x] Benachrichtigung: ein neuer `Meldeanlass` `LAUF_FREIGABE_OFFEN` an die Rollen mit dem Recht —
|
||||
sonst wartet ein Lauf, von dem niemand weiß
|
||||
- [ ] Oberfläche: Hinweis am Lauf („wartet auf Freigabe durch einen Zweiten“), Knöpfe für Freigeben
|
||||
- [x] Oberfläche: Hinweis am Lauf („wartet auf Freigabe durch einen Zweiten“), Knöpfe für Freigeben
|
||||
und Zurückweisen, Notizfeld; die Übersicht bekommt einen Filter „wartet auf meine Freigabe“
|
||||
- [ ] Editor: Häkchen „dieser Lauf braucht eine Freigabe“ an der Version
|
||||
- [ ] Druckansicht und PDF nennen Freigeber und Zeitpunkt
|
||||
- [ ] Anleitung: Kapitel 02 (Läufe abarbeiten) und 04 (Vorlagen), mit dem Hinweis, dass ein
|
||||
- [x] Editor: Häkchen „dieser Lauf braucht eine Freigabe“ an der Version
|
||||
- [x] Druckansicht und PDF nennen Freigeber und Zeitpunkt
|
||||
- [x] Anleitung: Kapitel 02 (Läufe abarbeiten) und 04 (Vorlagen), mit dem Hinweis, dass ein
|
||||
Einzelkämpfer sich nicht selbst freigeben kann
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Test: ein Lauf mit Freigabepflicht lässt sich nicht abschließen, solange keine Freigabe steht
|
||||
- [ ] Test: der Bearbeiter kann **nicht** freigeben — auch nicht als `ADMIN`
|
||||
- [ ] Test: die Datenbank weist es ebenfalls ab (CHECK), nicht nur der Dienst
|
||||
- [ ] Test: eine Rolle ohne `freigeben` wird abgewiesen, über jede Server Action
|
||||
- [ ] Test: Zurückweisen verlangt eine Notiz und setzt den Lauf auf `IN_ARBEIT`
|
||||
- [ ] Test: die Meldung geht an die Rollen mit dem Recht und nicht an alle
|
||||
- [ ] Test: ein Lauf **ohne** Freigabepflicht verhält sich unverändert — bestehende Läufe und
|
||||
- [x] Test: ein Lauf mit Freigabepflicht lässt sich nicht abschließen, solange keine Freigabe steht
|
||||
- [x] Test: der Bearbeiter kann **nicht** freigeben — auch nicht als `ADMIN`
|
||||
- [x] Test: die Datenbank weist es ebenfalls ab (CHECK), nicht nur der Dienst
|
||||
- [x] Test: eine Rolle ohne `freigeben` wird abgewiesen, über jede Server Action
|
||||
- [x] Test: Zurückweisen verlangt eine Notiz und setzt den Lauf auf `IN_ARBEIT`
|
||||
- [x] Test: die Meldung geht an die Rollen mit dem Recht und nicht an alle
|
||||
- [x] Test: ein Lauf **ohne** Freigabepflicht verhält sich unverändert — bestehende Läufe und
|
||||
Vorlagen merken nichts
|
||||
- [ ] Am Handy kein horizontales Scrollen
|
||||
- [x] Am Handy kein horizontales Scrollen
|
||||
|
||||
## Beim Umsetzen dazugekommen
|
||||
|
||||
- **Die vier Entscheidungen sind so gefallen wie empfohlen.** Zwei haben sich im Code als besonders
|
||||
tragend erwiesen:
|
||||
- **Die Freigabe hängt an der Version, nicht an der Vorlage.** Eine veröffentlichte Version ist
|
||||
unveränderlich — ein laufender Lauf kann seine Regel damit nicht unter den Füßen verlieren. In
|
||||
der Versionsliste steht an jeder Fassung, ob sie eine Freigabe verlangt.
|
||||
- **Kein fünfter `LaufStatus` für „zurückgewiesen“.** Der Lauf geht auf `IN_ARBEIT`, die
|
||||
Begründung steht in `freigabeNotiz` und im Protokoll. Ein neuer Enum-Wert hätte jeden Codepfad
|
||||
betroffen, der heute vier Fälle kennt.
|
||||
- **Als „gearbeitet“ zählt dreierlei** — angelegt, ein Schrittergebnis gesetzt, abgeschlossen.
|
||||
Der **Zuständige** gilt bewusst nicht als Bearbeiter: eine Zuweisung ist noch keine Arbeit, und
|
||||
wäre die Regel hier strenger, blockierte sie kleine Teams ohne Gewinn. Die Prüfung sitzt in
|
||||
`hatDaranGearbeitet`, an einer Stelle — wie `laufMussVeraenderbarSein`.
|
||||
- **Die Aufforderung entsteht außerhalb der Transaktion**, und das war die einzige knifflige Stelle:
|
||||
der Abschlussversuch scheitert mit einem eigenen Fehlertyp `FreigabeFehlt`, den
|
||||
`laufAbschliessen` außen fängt, um die Meldung einzureihen und dann weiterzuwerfen. Innerhalb
|
||||
würde sie mit dem Fehler zurückgerollt — und dann wartet ein Lauf auf etwas, von dem niemand
|
||||
weiß. Dafür musste die Transaktion in eine eigene Funktion (`abschlussVersuchen`).
|
||||
- **Ein anderer Empfängerkreis als bei jeder anderen Meldung**: nicht der Zuständige, sondern jeder
|
||||
mit dem Recht `freigeben` — ohne die, die gearbeitet haben. Eine Aufforderung zu etwas, das der
|
||||
Dienst abweist, ist schlimmer als keine.
|
||||
- **Ein partieller Unique-Index** (`Zustellung_freigabe_einmal`) hält die Mahnung bei einem Lauf
|
||||
und Empfänger auf einmal. Ein Techniker versucht es mehrmals; ohne den Index wäre jede Mail für
|
||||
sich korrekt und in Summe eine Belästigung — derselbe Fall wie „überfällig“ in #034, nur
|
||||
schneller erreicht.
|
||||
- **Die Freigabepflicht wandert bei „Bearbeiten“ und „Duplizieren“ mit.** Das war kein Zufall,
|
||||
sondern die unmittelbare Lehre aus #039: dort hatte die tiefe Kopie `nurFuerAbteilungen` und
|
||||
`bedingung` stillschweigend verworfen. Eine Vorlage soll ihre Freigabepflicht nicht durch eine
|
||||
Textkorrektur verlieren.
|
||||
|
||||
## Was die Klickprobe gezeigt hat
|
||||
|
||||
Elf Prüfungen am gebauten Stand, alle grün — von „Freigabe-Karte sichtbar“ über „Zurückweisen
|
||||
verlangt eine Begründung“ bis „Häkchen aus der Vorversion übernommen“ und 390 px.
|
||||
|
||||
**Drei rote Punkte waren meine eigenen Testfehler**, und der dritte ist festzuhalten:
|
||||
|
||||
1. Ein Selektor traf den **versteckten** Protokolltext „Lauf abgeschlossen“ statt der Erfolgsmeldung.
|
||||
2. Ein Suchmuster `/freigeb/` traf nichts: in „Freigabe“ und „freigegeben“ kommt diese Zeichenfolge
|
||||
gar nicht vor. Dreimal gesucht, bis es auffiel.
|
||||
3. **Ein `reload()` direkt nach einer Server Action zeigt kurz den alten Stand.** Die Action
|
||||
revalidiert den Pfad, und der Benutzer sieht das Ergebnis ohne Neuladen — ein Reload im selben
|
||||
Kontext trifft dagegen den Router-Cache von Next. Das ist Next und kein Fehler dieser Anwendung,
|
||||
aber es ist auch nicht, was ein Mensch tut. Die Probe prüft jetzt das echte Verhalten.
|
||||
|
||||
Nachgewiesen, bevor etwas geändert wurde: in einem frischen Browser-Kontext stand alles richtig
|
||||
da, und die Datenbank trug Freigeber, Zeitpunkt und Notiz.
|
||||
|
||||
## Was offen bleibt
|
||||
|
||||
- **Freigaben an einzelnen Schritten** gibt es nicht, bewusst. Am Lauf ist es eine Aussage über die
|
||||
Arbeit als Ganzes und erklärt sich von selbst; am Schritt verführte es zu Checklisten, in denen an
|
||||
fünf Stellen jemand warten muss. `VorlagenSchritt.konfig` ist ein Json — wenn sich der Bedarf
|
||||
zeigt, ist der Platz da.
|
||||
- **Ein Einzelkämpfer kommt nicht weiter**: wer allein ist und an dem Lauf gearbeitet hat, kann ihn
|
||||
nicht freigeben, auch nicht als Administrator. Das ist die Härte, die die Regel ausmacht, und die
|
||||
Anleitung sagt es ausdrücklich.
|
||||
|
||||
Reference in New Issue
Block a user