docs(issues): Phase 3 schneiden, und Verweise zeigen wieder richtig
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

Vier Meilensteine fuer v0.3.0, 34 h geschaetzt: bedingte Schritte und
Abschnitte (M23), Vier-Augen-Freigabe (M24), PDF-Uebergabeprotokoll (M25),
weitere Schritt-Typen (M26). Die Reihenfolge ist begruendet: M23 zuerst, weil
es als einziges am Datenmodell und an der Abschlussregel arbeitet; M26 zuletzt,
weil UNTERSCHRIFT erst im PDF einen Zweck hat.

Jedes Issue nennt die Entscheidungen, die zu treffen sind, mit Empfehlung und
Begruendung. Bei M23 ist die entscheidende: eine Bedingung auf einer Variable
liesse sich beim Start festschreiben wie bei #031, eine auf einem
Schrittergebnis nicht — die Antwort entsteht erst waehrend der Arbeit.

Beim Nummernvergeben einen Fund: vier Codestellen, sechs Changelog-Zeilen und
der Doku-Pruefer nannten dreistellig die Tracker-Nummern 39 und 40 — gemeint
waren die Issue-Dateien 025 und 026. Solange es diese Dateien nicht gab, fiel es
niemandem auf; mit Phase 3 waere es eine falsche Spur geworden, und zwar in
Kommentaren, die eine Codestelle begruenden.

Behoben, und pruefe:doku prueft es ab jetzt: jeder dreistellige Verweis muss auf
eine Issue-Datei zeigen. Gegenprobe mit einer erfundenen Nummer schlaegt an.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-10-11 15:36:27 +02:00
co-authored by Claude Opus 5
parent 60aaf746cf
commit 2f247b3617
13 changed files with 373 additions and 17 deletions
+6 -6
View File
@@ -44,15 +44,15 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
- Der Healthcheck `/api/gesundheit` nennt im Fehlerfall keinen Grund mehr. Der Pfad ist ohne
Anmeldung erreichbar, und eine Meldung von Prisma oder `pg` nennt Host, Port, Benutzernamen und
Datenbanknamen. Nach außen steht jetzt nur noch `{"status":"fehler"}` mit Status 503; die
Begründung geht ins Protokoll des Prozesses (#039)
Begründung geht ins Protokoll des Prozesses (#025)
- Die Konvention „nie auf rohe Hexwerte“ nennt ihre sechs begründeten Ausnahmen beim Namen —
Druckansichten, QR-Code und `themeColor` entscheiden nicht nach unserem Farbschema. Die Regel war
strenger formuliert als gemeint (#040)
strenger formuliert als gemeint (#026)
- `CLAUDE.md` und `README.md` nennen den LDAP-Testcontainer als Teil des Tors vor dem Commit. Ohne
ihn überspringt die Suite **22 Tests** — den ganzen Verzeichnis-Abgleich —, sagt das auf stderr,
und der Lauf sieht trotzdem grün aus (#040)
und der Lauf sieht trotzdem grün aus (#026)
- Im README fehlte `format:pruefen` in den Qualitätsschranken, obwohl `CLAUDE.md` es seit #010
verlangt (#040)
verlangt (#026)
### Behoben
@@ -235,9 +235,9 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
eingespielte Kopie bekommt **keinen** Schlüssel: sie soll nicht den Platz des Originals einnehmen
(#021)
- `pnpm pruefe:doku` vergleicht die Schranken-Befehle in `CLAUDE.md` und `README.md` und verlangt von
beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#040)
beiden den LDAP-Container. Genau diese Abweichung war unbemerkt entstanden (#026)
- Fünf Tests für den Healthcheck, darunter einer, der belegt, dass die Begründung ins Protokoll
geschrieben und nicht bloß verschluckt wird (#039)
geschrieben und nicht bloß verschluckt wird (#025)
## [0.1.0] – 2026-09-26
+1 -1
View File
@@ -257,7 +257,7 @@ geprüft. In CI bricht sie stattdessen ab, absichtlich.
| `mailpit` | **3 Tests** (nachgezählt: 36 statt 33) | der echte SMTP-Versand in `tests/benachrichtigungen.test.ts` — die vier Anlässe, die Passwort-Prüfung über alle Rumpftexte und „nichts zweimal“ |
`ldap` fiel bei der Abnahme zu #019 auf, `mailpit` kam mit #034 dazu. Beide Zahlen sind **gemessen**,
nicht geschätzt — in #040 hätte beinahe eine geratene in dieser Datei gestanden.
nicht geschätzt — in #026 hätte beinahe eine geratene in dieser Datei gestanden.
`format:pruefen` gehört dazu, seit der Review in #010 darauf gestoßen ist: `lint-staged` formatiert
beim Commit automatisch nach, und das Ergebnis wäre ein Commit mit Änderungen, die niemand gesehen
+9 -4
View File
@@ -11,10 +11,15 @@ Nicht im MVP umsetzen. Hier gesammelt, damit das Datenmodell nichts verbaut.
## Aufgaben
- [ ] PDF-Übergabeprotokoll mit Unterschrift
- [ ] Bedingte Schritte und Abschnitte
- [ ] Vier-Augen-Freigabe
- [ ] Weitere Schritt-Typen
Geschnitten am 2026-10-11, nach dem Tag von v0.2.0. Vier Meilensteine, 34 h geschätzt; die
Reihenfolge ist nicht beliebig.
| | Issue | Warum an dieser Stelle |
|---|---|---|
| M23 | [Bedingte Schritte und Abschnitte](039-bedingte-schritte.md) | zuerst, weil es als einziges am Datenmodell und an der Abschlussregel arbeitet — und weil der echte Fall im Seed steht: „Mitarbeiter-Onboarding“ trägt beide Konto-Abschnitte |
| M24 | [Vier-Augen-Freigabe](040-vier-augen-freigabe.md) | danach, weil der Abschluss dann einmal angefasst ist |
| M25 | [PDF-Übergabeprotokoll](041-pdf-uebergabeprotokoll.md) | nach M24, damit das Blatt den Freigeber nennen kann |
| M26 | [Weitere Schritt-Typen](042-weitere-schritt-typen.md) | zuletzt, weil `UNTERSCHRIFT` erst im PDF einen Zweck hat |
## Vorleistung im MVP
+95
View File
@@ -0,0 +1,95 @@
# #039 M23 – Bedingte Schritte und Abschnitte
**Status:** offen
**Im Tracker:** –
**Meilenstein:** v0.3.0
**Labels:** phase-3, backend, frontend
**Geschätzt:** 12 h **Tatsächlich:** –
## Ziel
Eine Checkliste enthält heute **alles**, und was nicht zutrifft, wird von Hand auf
`NICHT_ZUTREFFEND` gesetzt. Der echte Fall steht im Seed: „Mitarbeiter-Onboarding“ trägt **beide**
Konto-Abschnitte — „Konto anlegen (Active Directory)“ mit 5 Schritten und „Konto anlegen (Entra
ID)“ mit 4 —, obwohl in jedem einzelnen Lauf nur einer gilt. Die Variable `konto_typ` sagt es
bereits; niemand wertet sie aus.
Neun Schritte, die ein Techniker bei jedem Onboarding wegklickt. Die Abschnitts-Aktion aus #013
macht es erträglich, aber sie ist ein Pflaster: der Fortschrittsbalken zählt die Schritte mit, und
im Archiv steht später eine Checkliste mit einem Abschnitt, den niemand je gebraucht hat.
Setzt [#031](031-abteilungsabschnitte.md) voraus: `geltendeAbschnitte` und `nurGeltende` sind die
Stelle, an der das sitzt.
## Zu entscheiden
- [ ] **Wann wird ausgewertet?** Das ist die Entscheidung, an der alles hängt.
Für Abteilungen (#031) wird beim **Start festgeschrieben** — die Abteilung des Laufs steht
dann fest, und ein archivierter Lauf muss genau die Schritte zeigen, die damals galten.
Für Bedingungen reicht das **nicht**: eine Bedingung auf einer Variable (`konto_typ`) ließe
sich beim Start auswerten, eine Bedingung auf einem **Schrittergebnis** („wenn Schritt 3 =
nein, dann Abschnitt B“) nicht — die Antwort entsteht erst während der Arbeit.
Empfehlung: **laufend auswerten, Ergebnis mitschreiben.** Die Auswertung läuft bei jeder
Änderung an einem Schrittergebnis; was dabei herauskommt, wird in den Lauf geschrieben (wie
`geltendeAbschnitte`). Damit gilt für das Archiv weiter „der Lauf weiß, was galt“, und der
Ereignisstrom aus #033 trägt die Änderung ohnehin schon zur zweiten Sitzung.
- [ ] **Woran darf eine Bedingung hängen?** Empfehlung: an einer **Variable** des Laufs und an
einem **Schrittergebnis** derselben Version. Nicht an Gerätetyp, Abteilung oder Rolle — für
Abteilungen gibt es #031, und je mehr Quellen, desto schwerer zu erklären, warum ein Schritt
fehlt.
- [ ] **Wie wird sie gespeichert?** Empfehlung: ein Json-Feld `bedingung` an `VorlagenAbschnitt`
und `VorlagenSchritt`, mit einem engen Schema — `{ art: "variable", schluessel, operator,
wert }` oder `{ art: "schritt", schrittId, zustand }`. Json, weil `konfig` es schon so macht
und ein neuer Operator dann keine Migration braucht.
**Eine Bedingung, kein Ausdrucksbaum.** Kein `und`/`oder`, keine Verschachtelung: das wäre
eine kleine Sprache, die niemand im Editor pflegen kann, und ein Abschnitt, der aus
unerklärlichen Gründen fehlt, ist schlimmer als einer zu viel.
- [ ] **Was passiert mit einem Schrittergebnis, das schon Daten trägt**, wenn die Bedingung
umschlägt? Empfehlung: die Zeile **bleibt** und wird nur ausgeblendet. Löschen hieße, eine
Notiz zu verlieren, die jemand geschrieben hat; und schlägt die Bedingung zurück, ist sie
wieder da.
## Aufgaben
- [ ] Migration: `bedingung Json?` an `VorlagenAbschnitt` und `VorlagenSchritt`, dazu ein CHECK, der
sicherstellt, dass eine Bedingung auf einen Schritt **derselben Version** zeigt (sonst zeigt
sie nach dem Veröffentlichen ins Leere)
- [ ] `src/lib/bedingungen.ts`, framework-frei: Schema, Auswertung, und die Liste der Schritte und
Abschnitte, die gelten. **Dieselbe Stelle für alle drei Schichten**, wie
`src/lib/geltende-abschnitte.ts`
- [ ] Auswertung bei jedem Schreiben an einem `Schrittergebnis`; das Ergebnis in den Lauf
- [ ] Abschlussregel: ein ausgeblendeter Pflichtschritt hält den Abschluss **nicht** auf (dieselbe
Regel wie bei #031, dieselbe Funktion)
- [ ] Fortschrittsbalken und Zählwerte rechnen nur mit dem, was gilt
- [ ] Editor: Bedingung an Abschnitt und Schritt setzen, mit Auswahl der Variable bzw. des Schritts
**aus derselben Version**; eine Vorschau, die sagt „gilt, wenn …“
- [ ] Warnung im Editor bei einer Bedingung auf einen Schritt, der **nach** dem bedingten steht —
sie wäre beim Abarbeiten noch offen, und der Abschnitt erschiene erst rückwirkend
- [ ] Abarbeitung: ausgeblendete Abschnitte verschwinden, ohne die Nummerierung zu verschieben
(`01 · Vorbereitung` kommt aus der Position und wird nie gespeichert)
- [ ] Druckansicht und Archiv zeigen, was galt
- [ ] Export/Import: die Bedingung wandert mit, und ein Verweis auf einen Schritt wird dabei auf die
**neue** Kennung umgeschrieben — sonst zeigt sie in der zweiten Installation ins Leere
- [ ] Seed: „Mitarbeiter-Onboarding“ bekommt die Bedingung auf `konto_typ`, die beiden
Konto-Abschnitte bleiben drin und schließen sich gegenseitig aus. Das ist die Gegenprobe am
echten Fall.
- [ ] Anleitung Kapitel 04 (Vorlagen): wann eine Bedingung das richtige Mittel ist und wann eine
zweite Vorlage
## Akzeptanzkriterien
- [ ] Test: ein Lauf mit `konto_typ = AD` hat die 5 AD-Schritte und **keinen** der 4 Entra-Schritte
- [ ] Test: dasselbe umgekehrt, und bei nicht gesetzter Variable gelten **beide** — eine Bedingung,
die nicht entschieden werden kann, blendet nichts aus
- [ ] Test: eine Bedingung auf einem Schrittergebnis schlägt um, **während** der Lauf läuft, und der
Abschnitt erscheint
- [ ] Test: ein ausgeblendeter Pflichtschritt hält den Abschluss nicht auf
- [ ] Test: ein Schrittergebnis mit Notiz bleibt stehen, wenn seine Bedingung umschlägt, und ist
danach wieder da
- [ ] Test: Export und Import einer Vorlage mit Bedingungen ergeben dieselbe Struktur, und der
Schritt-Verweis zeigt auf den richtigen Schritt der **neuen** Version
- [ ] Test: ein archivierter Lauf zeigt genau die Schritte, die bei seinem Abschluss galten
- [ ] Am Handy kein horizontales Scrollen
+68
View File
@@ -0,0 +1,68 @@
# #040 M24 – Vier-Augen-Freigabe
**Status:** offen
**Im Tracker:** –
**Meilenstein:** v0.3.0
**Labels:** phase-3, backend, frontend
**Geschätzt:** 8 h **Tatsächlich:** –
## 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
+68
View File
@@ -0,0 +1,68 @@
# #041 M25 – PDF-Übergabeprotokoll mit Unterschrift
**Status:** offen
**Im Tracker:** –
**Meilenstein:** v0.3.0
**Labels:** phase-3, backend, frontend
**Geschätzt:** 8 h **Tatsächlich:** –
## Ziel
Wer ein Notebook übergibt, braucht ein Blatt, das der Empfänger unterschreibt — und das danach
auffindbar ist. Heute gibt es die Druckansicht eines Laufs (`/drucken/lauf/[id]`, #015): sie ist
hell, vollständig und als Vorstufe genau dafür gebaut. Was fehlt, ist die Datei.
Ein Browser-Ausdruck genügt nicht: er hängt am Drucker-Dialog des Betrachters, trägt dessen
Kopfzeilen und lässt sich nicht an ein Ticket anhängen.
## Zu entscheiden
- [ ] **Wie entsteht das PDF?** Empfehlung: **Chromium aus `@playwright/test`**, wie
`scripts/anleitung-pdf.ts` es für die Anleitung tut. Keine neue Abhängigkeit, keine zweite
Auslegung des Layouts — gedruckt wird dieselbe Seite, die die Druckansicht zeigt.
**Grenze, die genannt gehört:** damit braucht der Server einen Chromium. Im Compose-Stand ist
das eine Ergänzung am Image, nativ ein Paket. Die Alternative (`pdf-lib` oder ein
Dienst) wäre ein zweites Layout, das auseinanderläuft — der Preis ist höher.
- [ ] **Unterschrift auf Papier oder auf dem Gerät?** Empfehlung: **beides, in dieser Reihenfolge.**
Zuerst ein Unterschriftsfeld im PDF (eine Linie, Name, Datum) — das löst den Fall heute und
kostet fast nichts. Eine Unterschrift mit dem Finger auf dem Handy ist ein eigener Schritt-Typ
und gehört zu [#042](042-weitere-schritt-typen.md); sie landet dann als Bild im PDF.
Eine digitale **Signatur** im kryptografischen Sinn ist ausdrücklich **nicht** gemeint und
wäre eine eigene Phase.
- [ ] **Wird das PDF gespeichert oder bei jedem Abruf erzeugt?** Empfehlung: **bei jedem Abruf
erzeugt**, nicht gespeichert. Ein gespeichertes PDF ist eine zweite Wahrheit neben dem Lauf;
wird eine Notiz später berichtigt, stimmt es nicht mehr. Für den Fall „wir brauchen das Blatt
von damals“ gibt es das Archiv, das den Lauf unveränderlich hält.
**`{{generiertes_passwort}}` erscheint auch hier nicht** — die Druckansicht zeigt
`[Passwort – nicht gespeichert]`, und das PDF übernimmt das.
## Aufgaben
- [ ] `GET /api/laeufe/[id]/protokoll.pdf` — Route Handler, prüft sein Recht **selbst**
(`tests/zugang.test.ts` erzwingt das seit #038)
- [ ] Erzeugung über Chromium, gegen die eigene Druckansicht; eine Seitenvorlage mit Kopf (Kunde,
Lauf, Vorlage, Version), Inhalt und Fuß (Bearbeiter, Freigeber aus
[#040](040-vier-augen-freigabe.md), Unterschriftsfeld)
- [ ] Ein **Chromium-Vorrat** im Prozess: ihn je Anfrage zu starten kostet Sekunden. Eine Instanz,
die nach Leerlauf schließt — und eine Obergrenze gleichzeitiger Erzeugungen, wie
`SSE_MAX_STROEME` in #033
- [ ] Knopf „Protokoll als PDF“ an Lauf und Archiv
- [ ] `Dockerfile` und `deploy/install.sh` um Chromium ergänzen, **mit gemessener Bildgröße** im
Changelog — eine Ergänzung am Image, die niemand bemerkt, ist eine böse Überraschung beim
nächsten Rollout
- [ ] `docs/betrieb/` sagt, was die Erzeugung braucht und was passiert, wenn Chromium fehlt:
**eine Meldung, kein stilles leeres PDF**
- [ ] Anleitung Kapitel 02 und 10 (Archiv)
## Akzeptanzkriterien
- [ ] Test: das PDF enthält Titel, Vorlage mit Version, alle geltenden Schritte mit Zustand, Notizen
und Kommentare — geprüft am extrahierten Text, nicht am Vorhandensein der Datei
- [ ] Test: es enthält **kein** erzeugtes Passwort, über alle Schritt-Typen
- [ ] Test: ein Lauf eines fremden Kunden ist über die Adresse nicht abrufbar (wie #032 nach #036)
- [ ] Test: ohne Recht `401`, mit falschem Kunden `404`
- [ ] Test: fehlt Chromium, kommt eine Meldung mit Hinweis auf die Betriebsdoku — kein leeres PDF
- [ ] Test: zwei gleichzeitige Erzeugungen laufen, die dritte wartet oder wird abgewiesen (die
Obergrenze gilt)
- [ ] Ein Blatt ist ausgedruckt und gegengesehen worden — das ist eine Abnahme von Hand und steht
als solche im Devlog
+62
View File
@@ -0,0 +1,62 @@
# #042 M26 – Weitere Schritt-Typen
**Status:** offen
**Im Tracker:** –
**Meilenstein:** v0.3.0
**Labels:** phase-3, backend, frontend
**Geschätzt:** 6 h **Tatsächlich:** –
## Ziel
`SchrittTyp` hat heute sechs Werte: `HAKEN TEXT ZAHL AUSWAHL DATUM BEFEHL`. Das Enum und das
Json-Feld `konfig` sind seit #006 genau dafür gebaut, dass neue Typen ohne Migration der
bestehenden dazukommen.
Die Frage ist nicht, **wie** ein Typ dazukommt, sondern **welche** — und das Issue nennt sie
absichtlich noch nicht als beschlossen: ein Typ, der im Editor steht und in keiner Vorlage
auftaucht, ist ungenutzter und damit ungeprüfter Code.
## Zu entscheiden
- [ ] **Welche Typen?** Vorschlag, nach Nutzen geordnet, jeder mit dem Fall, der ihn verlangt:
| Typ | Der Fall | Ohne ihn |
|---|---|---|
| `DATEI` | „Foto des Typenschilds“ als **Pflicht** | ein Anhang ist seit #032 möglich, aber nie verlangt — ein Lauf schließt ohne ihn ab |
| `UNTERSCHRIFT` | Übergabe mit dem Finger auf dem Handy | das Blatt muss gedruckt und wieder eingescannt werden ([#041](041-pdf-uebergabeprotokoll.md)) |
| `MEHRFACHAUSWAHL` | „welche Lizenzen zugewiesen“ | heute ein Textfeld, also nicht auswertbar |
| `URL` | „Link zum Ticket“ | ein Textfeld ohne Prüfung und ohne Klick |
| `UEBERSCHRIFT` | eine Zwischenüberschrift ohne Zustand | ein `HAKEN`, den jemand abhaken muss, damit der Lauf fertig wird |
**Empfehlung: `DATEI` und `UNTERSCHRIFT`**, und die drei anderen nur, wenn eine Seed-Vorlage
sie wirklich benutzt. `UEBERSCHRIFT` ist der zweifelhafteste: ein Schritt ohne Zustand bricht
die Annahme „jeder Schritt hat ein `Schrittergebnis`“, und die steckt an vielen Stellen.
- [ ] **Bleibt `Schrittergebnis.wert` ein Json?** Ja — es ist seit #006 eines, genau dafür. Eine
Mehrfachauswahl ist ein Array, eine Unterschrift ein Verweis auf einen `Anhang`.
- [ ] **Wie verträgt sich `DATEI` mit dem Archiv-Schreibschutz?** Ein Pflicht-Anhang muss vor dem
Abschluss da sein; danach kommt keiner mehr weg (#032). Das ist bereits so und braucht nur
einen Test.
## Aufgaben
- [ ] Je gewählten Typ: Enum-Wert, Zod-Schema für `konfig` und `wert`, Darstellung im Editor,
Darstellung in der Abarbeitung, Darstellung im Druck und im PDF
- [ ] Die Abschlussregel kennt den neuen Typ: was zählt als „erledigt“? Bei `DATEI` ein Anhang, bei
`UNTERSCHRIFT` ein Bild, bei `MEHRFACHAUSWAHL` mindestens eine Wahl
- [ ] `UNTERSCHRIFT`: Zeichenfläche am Handy (Pointer-Ereignisse, nicht Touch-spezifisch), Ergebnis
als PNG über den Anhänge-Weg aus #032 — **keine neue Ablage**
- [ ] Export/Import und die Vorlagen-Austauschform um die neuen Typen erweitern, nachsichtig: eine
Datei mit einem unbekannten Typ wird abgewiesen **mit Nennung des Typs**, nicht stillschweigend
verändert
- [ ] Mindestens eine Seed-Vorlage benutzt jeden neuen Typ — sonst ist er ungeprüfter Code
- [ ] Anleitung Kapitel 04, mit dem Satz, wann ein Typ das richtige Mittel ist
## Akzeptanzkriterien
- [ ] Test je Typ: anlegen, abarbeiten, abschließen, drucken
- [ ] Test: ein `DATEI`-Pflichtschritt ohne Anhang hält den Abschluss auf
- [ ] Test: eine Unterschrift landet als `Anhang` und nicht als Json-Blob in `wert`
- [ ] Test: ein Lauf einer **älteren** Version verhält sich unverändert — die neuen Typen tauchen
dort nicht auf, und keine Migration hat Daten angefasst
- [ ] Test: eine Austauschdatei mit unbekanntem Typ wird abgewiesen und nennt den Typ
- [ ] Am Handy bedienbar, bei 390 px geprüft — besonders die Zeichenfläche
+5 -1
View File
@@ -43,4 +43,8 @@ Diese Dateien sind die Quelle; `pnpm issues:sync` spiegelt sie in den Gitea-Trac
| #036 | [Reviewbefunde aus der Durchsicht von Phase 2](036-reviewbefunde-phase-2.md) | erledigt | v0.2.0 | 4 h | 3 h 40 min |
| #037 | [Zwei Nachzügler aus der Durchsicht von Phase 2](037-anhaenge-lesemodell.md) | erledigt | v0.2.0 | 2 h | 1 h 50 min |
| #038 | [Die E2E-Abläufe laufen in keinem Tor und waren monatelang rot](038-e2e-im-tor.md) | erledigt | v0.2.0 | 3 h | 1 h 35 min |
| | **Summe** | | | **238 h** | **73 h 55 min** |
| #039 | [M23 – Bedingte Schritte und Abschnitte](039-bedingte-schritte.md) | offen | v0.3.0 | 12 h | – |
| #040 | [M24 – Vier-Augen-Freigabe](040-vier-augen-freigabe.md) | offen | v0.3.0 | 8 h | – |
| #041 | [M25 – PDF-Übergabeprotokoll mit Unterschrift](041-pdf-uebergabeprotokoll.md) | offen | v0.3.0 | 8 h | – |
| #042 | [M26 – Weitere Schritt-Typen](042-weitere-schritt-typen.md) | offen | v0.3.0 | 6 h | – |
| | **Summe** | | | **272 h** | **73 h 55 min** |
+55 -1
View File
@@ -141,7 +141,7 @@ function feld(text, name) {
// README fehlte `format:pruefen`, und beide nannten den LDAP-Container nicht, ohne
// den die Suite 22 Tests still ueberspringt. Beides fiel erst bei der Abnahme zu
// #019 auf -- ein gruener Lauf sieht genauso aus. Geprueft wird deshalb der
// Befehlsblock selbst, nicht seine Umgebung (#040).
// Befehlsblock selbst, nicht seine Umgebung (#026).
{
/** Der erste eingezaeunte Block nach einer Ueberschrift. */
const blockNach = (text, ueberschrift) => {
@@ -197,6 +197,60 @@ function feld(text, name) {
}
}
// ---- 7. Jeder (#NNN)-Verweis zeigt auf eine Issue-Datei --------------------
{
/*
* Verweise wie `(#032)` stehen in Kommentaren, im Changelog und in CLAUDE.md
* und sind die einzige Bruecke zwischen einer Codestelle und ihrer
* Begruendung. Zeigt einer ins Leere, ist die Begruendung verloren — und
* schlimmer: zeigt er auf die **falsche** Datei, fuehrt er jemanden in die
* Irre, der ihm vertraut.
*
* Genau das war passiert: vier Codestellen und sechs Changelog-Zeilen nannten
* dreistellig die **Tracker**-Nummern 39 und 40 — gemeint waren die
* Issue-Dateien 025 und 026. Solange es keine Dateien mit diesen Nummern gab,
* fiel es niemandem auf; mit Phase 3 waere es eine falsche Spur geworden.
* Verbindlich ist die **Dateinummer**; die Tracker-Nummer steht in der
* Issue-Datei unter „Im Tracker“.
*
* Diese Datei nennt deshalb selbst keine Nummer in der dreistelligen Form —
* sonst meldete die Pruefung ihren eigenen Kommentar.
*/
const verz = "docs/issues";
const nummern = new Set(
readdirSync(join(wurzel, verz))
.filter((n) => /^\d{3}-.+\.md$/.test(n))
.map((n) => n.slice(0, 3)),
);
const sammeln = (ordner) =>
readdirSync(ordner, { withFileTypes: true }).flatMap((eintrag) => {
if (["node_modules", ".git", ".next", "generated", "test-results"].includes(eintrag.name)) {
return [];
}
const pfad = join(ordner, eintrag.name);
if (eintrag.isDirectory()) return sammeln(pfad);
return /\.(ts|tsx|mjs|md|sql)$/.test(eintrag.name) ? [pfad] : [];
});
for (const datei of sammeln(wurzel)) {
const kurz = datei.replace(`${wurzel}/`, "");
// Der Devlog zitiert Tracker-Nummern aus Pull Requests; dort gilt die Regel nicht.
if (kurz === "docs/devlog.md") continue;
const text = readFileSync(datei, "utf8");
const gemeldet = new Set();
for (const treffer of text.matchAll(/#(\d{3})\b/g)) {
const nummer = treffer[1];
if (nummern.has(nummer) || gemeldet.has(nummer)) continue;
gemeldet.add(nummer);
melde(
`${kurz} verweist auf #${nummer}, aber ${verz}/${nummer}-*.md gibt es nicht. ` +
`Gemeint ist vielleicht eine Tracker-Nummer — verbindlich ist die Dateinummer.`,
);
}
}
}
// ---- Ergebnis --------------------------------------------------------------
if (befunde.length > 0) {
console.error(`\nDokumentation und Code laufen auseinander (${befunde.length}):\n`);
+1 -1
View File
@@ -10,7 +10,7 @@ import { prisma } from "@/lib/db";
* Datenbanknamen. Nach aussen genuegt, **ob** es geht; warum nicht, steht im
* Protokoll des Prozesses (`docker compose logs web`,
* `journalctl -u laufzettel`). Dasselbe Muster wie in
* `src/server/actions/verzeichnis.ts` (#039).
* `src/server/actions/verzeichnis.ts` (#025).
*/
export async function GET(): Promise<Response> {
try {
+1 -1
View File
@@ -149,7 +149,7 @@ export async function tokenHolen(zugang: GraphZugang, jetzt = Date.now()): Promi
/*
* Die ausführliche Meldung ins Protokoll des Prozesses, eine kurze nach
* draußen. Dieselbe Trennung wie beim LDAP-Bind-Fehler (#016) und beim
* Healthcheck (#039): eine Meldung, die Mandant und Anwendungs-ID nennt,
* Healthcheck (#025): eine Meldung, die Mandant und Anwendungs-ID nennt,
* gehört nicht in eine Oberfläche.
*/
console.error(
+1 -1
View File
@@ -46,7 +46,7 @@ export function alsScimAntwort(fehler: unknown): Response {
/*
* Alles andere ist ein Fehler bei uns, nicht beim Anbieter. Die Meldung geht
* ins Protokoll des Prozesses und **nicht** in die Antwort: eine
* Datenbankmeldung nennt Host, Port und Benutzernamen (#039).
* Datenbankmeldung nennt Host, Port und Benutzernamen (#025).
*/
console.error(
"SCIM-Anfrage fehlgeschlagen:",
+1 -1
View File
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
/**
* Der Healthcheck (#039).
* Der Healthcheck (#025).
*
* Dieser Pfad ist der einzige, der ohne Anmeldung **und** mit Datenbankzugriff
* antwortet. Geprueft wird deshalb nicht, dass er funktioniert — das tun der