feat(vorlagen): Versionierung, Unveraenderlichkeit und JSON-Austausch
Prüfung / pruefung (push) Canceled after 0s
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s

Eine veroeffentlichte Vorlagen-Version ist fuer immer unveraenderlich — daran
haengt, dass ein archivierter Lauf in zehn Jahren noch genau die Schritte zeigt,
mit denen er gestartet wurde. Die Regel steht deshalb zweimal: im Service, der
die verstaendliche Meldung gibt, und in der Datenbank, die die Garantie gibt.
Vier Trigger fangen Schreibzugriffe ab, die nicht durch den Service kommen —
Migrationen, psql, der Seed und ab Phase 4 die Connectoren — und versperren den
Rueckweg nach ENTWURF, der alles andere ausgehebelt haette.

- Service: anlegen, Kopfdaten bearbeiten, Entwurf als tiefe Kopie der letzten
  veroeffentlichten Version, speichern, veroeffentlichen, verwerfen,
  duplizieren, archivieren
- Hoechstens ein Entwurf je Vorlage, durchgesetzt vom partiellen Index; zwei
  gleichzeitige Klicks auf „Bearbeiten“ bekommen denselben Entwurf statt eines
  Fehlers
- Optimistisches Sperren ueber geaendertAm: wer auf einem ueberholten Stand
  speichert, wird abgewiesen statt die Arbeit des anderen spurlos zu ersetzen
- JSON-Export und -Import samt Format-Dokumentation; der Export ist nachsichtig,
  der Import streng
- src/lib/validierung.ts als einzige Zod-Quelle mit deutschen Meldungen, per
  ESLint-Regel erzwungen

Der unabhaengige Review fand drei Blocker, alle an der Mandantengrenze: der
Suchfilter brachte ein zweites OR mit und ueberschrieb die Kundenbedingung,
Historie und Aufbau einer Version filterten gar nicht. Zu queries/vorlagen.ts
gab es keinen Test — jetzt gibt es tests/vorlagen-sicht.test.ts.

428 Tests gruen (vorher 336).

Schliesst #009

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-25 08:09:21 +02:00
co-authored by Claude Opus 5
parent ecc3c579df
commit abacad1cb7
41 changed files with 4006 additions and 104 deletions
+72 -4
View File
@@ -11,11 +11,12 @@ als Unterpunkte mit Aufgabe und Dauer.
| #006 | M1 – Datenmodell | 1 h 10 min |
| #007 | M2 – Auth, Rollen, Benutzer | 1 h 35 min |
| #008 | M3 – Betriebsmodus und Kunden | 1 h 20 min |
| | **Summe** | **5 h 30 min** |
| #009 | M4 – Vorlagen-Versionierung im Service | 1 h 20 min |
| | **Summe** | **6 h 50 min** |
| Meilenstein | Tatsächlich | Status |
|---|---|---|
| v0.1.0 | 5 h 30 min | in Arbeit |
| v0.1.0 | 6 h 50 min | in Arbeit |
---
@@ -43,7 +44,7 @@ als Unterpunkte mit Aufgabe und Dauer.
- **Deutsch im Code, auch für Bezeichner.** Der Auftragstext verlangte Englisch; Florian hat sich
für Einheitlichkeit mit ProxLedger entschieden. Verbindliche Namensabbildung in `CLAUDE.md`.
- **Kein Auth.js.** v5 steht seit Jahren auf Beta (`5.0.0-beta.32`), authjs.dev führt das Projekt
im Wartungsmodus („now part of Better Auth"), die Maintainer nennen „no immediate plans for v5",
im Wartungsmodus („now part of Better Auth“), die Maintainer nennen „no immediate plans for v5“,
und `@auth/prisma-adapter` deklariert Prisma nur bis 6 — wir fahren Prisma 7. Stattdessen
`openid-client` + `jose` wie in ProxLedger. Dieselbe Überlegung wie dort, nur ein Jahr später
noch deutlicher.
@@ -246,7 +247,7 @@ als Unterpunkte mit Aufgabe und Dauer.
- **Die Sichtbarkeit gehört in `kundeErmitteln`,** nicht in eine zweite Funktion daneben. Sonst
müsste jeder künftige Service daran denken — und #009 ff. übernehmen diese Signatur.
- **Lesemodelle filtern selbst.** Sie verlangen den Benutzer, statt sich auf den Aufrufer zu
verlassen. `undefined` heißt „keine Einschränkung", `[]` heißt „sieht nichts"; die Umsetzung
verlassen. `undefined` heißt „keine Einschränkung“, `[]` heißt „sieht nichts“; die Umsetzung
steht an einer Stelle, und beide Fälle haben einen Test.
- **`pg_advisory_xact_lock` statt Serializable.** Der Wechsel ist selten und kurz; ein Schloss auf
Transaktionsebene ist verständlicher als eine Isolationsstufe, die man beim Lesen übersieht, und
@@ -271,3 +272,70 @@ als Unterpunkte mit Aufgabe und Dauer.
- #009 Vorlagen-Versionierung: Veröffentlichen, tiefe Kopie als Entwurf, höchstens ein Entwurf,
Unveränderlichkeit, JSON-Export und -Import.
---
## 2026-09-25 · Sitzung 5 · 07:00–08:20
**Issue:** #009 (M4)
**Version:** 0.1.0
### Erledigt
- `src/server/services/vorlagen.ts`: Anlegen, Kopfdaten bearbeiten, Entwurf holen (tiefe Kopie),
Speichern, Veröffentlichen, Verwerfen, Duplizieren, Archivieren
- `src/server/services/vorlagen-austausch.ts` mit `docs/vorlagen-format.md`: ein Zod-Schema für
Editor, Export und Import — eine Vorlage zieht als Datei zwischen zwei Installationen um
- `src/lib/validierung.ts` als einzige Quelle für Zod samt deutscher Meldungen, mit ESLint-Regel
- Migration `vorlagen_unveraenderlich`: vier Trigger, die die Unveränderlichkeit in der Datenbank
durchsetzen — nicht nur im Service
- Unabhängiger Review: drei Blocker (alle an der Mandantengrenze), acht weitere Befunde, alle
abgearbeitet
- **428 Tests** grün (vorher 336), davon neu: Mandantensicht, Datenbankregeln, Gleichzeitigkeit,
Austausch mit unsauberen Daten
### Entscheidungen
- **Die Regel steht zweimal: im Service und in der Datenbank.** Der Service gibt die verständliche
Meldung, die Datenbank die Garantie. Alles, was nicht durch den Service geht — Migrationen,
`psql`, der Seed, ab Phase 4 die Connectoren —, läuft sonst an der einen Regel vorbei, die das
Archiv überhaupt erst etwas wert macht. Die doppelte Haltung kostet einen Trigger und spart die
Annahme, dass jeder künftige Schreibweg daran denkt.
- **Ein Entwurf je Vorlage kommt vom partiellen Index**, nicht von einer Prüfung davor. Zwei
gleichzeitige Klicks auf „Bearbeiten“ laufen damit unvermeidlich in `P2002` — und genau dort
wiederholt der Service und gibt beiden denselben Entwurf zurück, statt einen Fehler zu zeigen,
den niemand versteht.
- **Optimistisches Sperren über `geaendertAm`,** nicht über eine Zählerspalte. Der Zeitstempel ist
ohnehin da, und `updateMany` mit Bedingung erledigt den Vergleich in derselben Anweisung.
- **Unbekannte `konfig`-Schlüssel überleben, unbekannte Werte bekannter Schlüssel nicht.** Ein
neues Feld aus einer späteren Fassung darf einen Umzug nicht verhindern; eine unbekannte `shell`
aber entscheidet darüber, wie ein Wert maskiert wird — sie stillschweigend zu verwerfen hieße,
einen bash-Befehl nach PowerShell-Regeln zu maskieren, und das kann auf einem Server Daten
kosten. Also laut abweisen, mit Pfad und erlaubten Werten.
- **Der Export ist nachsichtig, der Import streng.** Herausgeben muss man auch, was nicht ins
Schema passt, sonst verliert die Datei Inhalt. Annehmen darf man es nicht.
- **`tests/datenbankregeln.test.ts` als Wächter.** Prisma erzeugt für jede künftige Migration
Löschbefehle gegen die handgeschriebenen Objekte. Der Test zählt sie namentlich auf, damit ein
übersehener `DROP` die Suite rot macht statt still die Mandantengrenze zu entfernen.
### Probleme
- **Die drei Blocker lagen alle an derselben Stelle: den Lesemodellen.** Der Suchfilter brachte
einen zweiten `OR`-Schlüssel mit und überschrieb die Kundenbedingung — ein Buchstabe im Suchfeld
genügte, um einem gebundenen Techniker alle Vorlagen aller Kunden zu zeigen. Historie und Aufbau
einer Version filterten gar nicht. Zu `queries/vorlagen.ts` gab es keinen Test; die Sicht in #008
abzusichern und in #009 nur den Service zu testen war der Fehler.
- **`Prisma.validator`-Objekte mit zwei gleichen Schlüsseln sind kein Typfehler.** Ein
zusammengebautes `where` mit `OR` aus zwei Quellen fällt weder ESLint noch `tsc` auf. Deshalb
liegt die Mandantenbedingung jetzt in `AND`, wo ein zweiter Filter sie nicht erreichen kann.
- **Prisma wollte sieben handgeschriebene Objekte löschen.** Die Migration musste von Hand
geschrieben werden; zusätzlich fehlten `@default(now())` bei den `@updatedAt`-Feldern, weshalb
Prisma bei jedem Lauf `DROP DEFAULT` vorschlug.
- **Zwei gleichzeitige Testläufe gegen `laufzettel_test`** ergaben 62 falsche Fehlschläge. Der
Review-Agent und ich haben uns die Daten weggetruncated; im Auftrag an den Agenten hatte ich die
eigene Datenbank nicht verlangt.
### Nächster Schritt
- #010 Vorlagen-Editor: Liste mit Suche und Filtern, Abschnitte und Schritte per Drag & Drop,
Vorschau, Veröffentlichen, Versionshistorie, Warnung bei unbekannten Platzhaltern.