Files
Laufzettel/docs/issues/040-vier-augen-freigabe.md
Florian WildeandClaude Opus 5 d30bb6d235
Prüfung / pruefung (push) Waiting to run
Prüfung / e2e (push) Blocked by required conditions
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>
2026-10-24 01:14:04 +02:00

8.1 KiB
Raw Permalink Blame History

#040 M24 – Vier-Augen-Freigabe

Status: erledigt Im Tracker: – Meilenstein: v0.3.0 Labels: phase-3, backend, frontend Geschätzt: 8 h Tatsächlich: 3 h 30 min

Ziel

Manche Arbeiten soll nicht derselbe abschließen, der sie gemacht hat: ein Firewall-Rollout, ein Restore-Test, das Löschen eines Kontos. Heute kann jeder TECHNIKER jeden Lauf abschließen und archivieren.

Gebraucht wird: eine Vorlage kann sagen „dieser Lauf braucht eine Freigabe“, und dann schließt ihn ein Zweiter ab — nachweisbar im Protokoll.

Zu entscheiden

  • 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 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 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 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, 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, nicht nur in den Dienst (CLAUDE.md)
  • Recht freigeben in RECHTE, Rollenmatrix-Test erweitern
  • 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 — sonst wartet ein Lauf, von dem niemand weiß
  • 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 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 Vorlagen merken nichts
  • 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.