# #009 M4 – Vorlagen-Versionierung im Service **Status:** erledigt **Meilenstein:** v0.1.0 **Labels:** phase-1, backend **Geschätzt:** 6 h **Tatsächlich:** 1 h 20 min ## Ziel Die harte Regel der Anwendung: eine veröffentlichte Version ist für immer unveränderlich, damit ein Archiv-Lauf für immer nachvollziehbar bleibt. Diese Regel sitzt in der Service-Schicht, nicht in der Oberfläche. ## Aufgaben - [x] Services für `Vorlage`, `VorlagenVersion`, `VorlagenAbschnitt`, `VorlagenSchritt`, `VorlagenVariable` - [x] `versionVeroeffentlichen`: setzt `status = VEROEFFENTLICHT`, `veroeffentlichtAm`, `veroeffentlichtVonId`, Änderungsnotiz, `Protokoll`-Eintrag - [x] `entwurfHolenOderErzeugen`: tiefe Kopie der letzten veröffentlichten Version als neuer Entwurf mit fortlaufender `version`; kopiert Variablen, Abschnitte und Schritte samt `konfig` - [x] Höchstens ein Entwurf je Vorlage — durchgesetzt vom partiellen Eindeutigkeitsindex `VorlagenVersion_ein_entwurf`, der zweite gleichzeitige Aufruf bekommt denselben Entwurf - [x] Jede Schreiboperation auf einer veröffentlichten Version wirft; zentral in `versionMussEntwurfSein()` — **und** zusätzlich in der Datenbank per Trigger - [x] Läufe verweisen ausschließlich auf veröffentlichte Versionen — `veroeffentlichteVersionPruefen()` für #013 - [x] `vorlageDuplizieren`: neue Vorlage mit Entwurf als Kopie - [x] JSON-Export und -Import mit Zod-Schema, Format in `docs/vorlagen-format.md` dokumentiert (Version des Formats, Abschnitte, Schritte, Variablen, `konfig`) - [x] Rechte: `REDAKTEUR` und `ADMIN` dürfen anlegen, bearbeiten, veröffentlichen; `TECHNIKER` und `LESER` nicht ## Akzeptanzkriterien - [x] Test: nach dem Veröffentlichen schlägt jede Änderung an Version, Abschnitten, Schritten und Variablen fehl - [x] Test: „Bearbeiten“ erzeugt eine tiefe Kopie — Änderungen am Entwurf lassen die veröffentlichte Version unberührt, auch in verschachtelten `konfig`-Feldern - [x] Test: ein zweiter Entwurf wird abgelehnt - [x] Test: ein Lauf auf Version 1 bleibt nach Veröffentlichen von Version 2 unverändert und zeigt weiter die Schritte aus Version 1 - [x] Test: Export und anschließender Import ergeben eine inhaltsgleiche Vorlage - [x] Test: `TECHNIKER` darf nicht veröffentlichen - [x] Test: Veröffentlichen schreibt einen `Protokoll`-Eintrag ## Beim Bauen aufgefallen - **Die Lesemodelle haben die Mandantengrenze verloren, sobald jemand suchte.** Der Suchfilter brachte einen zweiten `OR`-Schlüssel in dasselbe Objekt; in JavaScript gewinnt der letzte, also ersetzte er die Kundenbedingung. Ein einzelner Buchstabe im Suchfeld gab einem gebundenen Techniker alle Vorlagen aller Kunden. `versionsHistorie` und `versionMitAufbau` haben überhaupt nicht gefiltert — damit lagen Abschnittstitel, Schritte und PowerShell-Befehle fremder Kunden offen. Ursache ist banal: zu `src/server/queries/vorlagen.ts` gab es keinen einzigen Test. Es gibt jetzt `tests/vorlagen-sicht.test.ts`, und die Kundenbedingung steht in einem `AND`-Eintrag, wo kein weiterer Filter sie überschreiben kann. - **Die Unveränderlichkeit hing allein am Service.** Migrationen, `psql`, ein Seed-Skript und ab Phase 4 die Connectoren schreiben daran vorbei — und genau diese Regel ist der Grund, warum ein Archiv-Lauf in zehn Jahren noch etwas wert ist. Vier Trigger setzen sie jetzt in der Datenbank durch, einer davon verbietet den Rückweg `VEROEFFENTLICHT → ENTWURF`, der sonst alles andere ausgehebelt hätte. - **Prisma will handgeschriebene Datenbankobjekte löschen.** `prisma migrate dev` erzeugte für die neue Migration sieben `DROP`-Anweisungen gegen Trigger, zusammengesetzte Fremdschlüssel und Trigramm-Indizes — aus Prismas Sicht sind sie Abweichungen von der Vorgabe. Wer sie einmal übersieht, entfernt stillschweigend die Mandantengrenze. `tests/datenbankregeln.test.ts` zählt diese Objekte jetzt namentlich auf und macht jeden Verlust laut. - **Die Trigger haben schlechte Test-Fixtures entlarvt.** Mehrere legten eine Version als `VEROEFFENTLICHT` an und füllten sie *danach* — also genau das, was die Regel verbietet. Der Service hatte das nie gesehen, weil die Fixtures direkt über Prisma schrieben. - **Zwei Redakteure am selben Entwurf überschrieben sich spurlos.** Entwürfe werden bewusst nicht protokolliert, die Arbeit wäre also auch nicht wiederherstellbar gewesen. `entwurfSpeichern` nimmt jetzt den erwarteten Stand (`geaendertAm`) und weist ein Speichern auf einem überholten Stand ab. - **`Zod` war achtmal einzeln eingebunden**, die deutschen Meldungen hingen davon ab, dass jede Datei daran denkt. Jetzt gibt es `src/lib/validierung.ts` als einzige Quelle, und eine ESLint-Regel weist einen direkten `zod`-Import ab. - **Der Doku-Link eines Schritts nahm jedes Schema an**, auch `javascript:alert(1)`. Er landet in der Abarbeitungsansicht als `href` — über eine importierte Datei wäre das fremder Code im Browser des Technikers. Erlaubt sind jetzt nur `http` und `https`. - **Der Export verwarf bei jeder Beanstandung das ganze `konfig`.** Ein `shell: "zsh"` aus einer späteren Fassung kostete lautlos auch den `befehl` — in genau der Datei, die jemand zum Umzug braucht. Exportiert wird jetzt der rohe Inhalt; abgewiesen wird beim Import, laut und mit Pfad. - **Zwei gleichzeitige Testläufe gegen dieselbe Datenbank** (mein Lauf und der des Review-Agenten) ergaben 62 falsche Fehlschläge, weil `TRUNCATE` dem anderen die Daten unter den Füßen wegzieht. Das war mein Fehler im Auftrag an den Agenten; `CLAUDE.md` nennt jetzt den Befehl für eine eigene Review-Datenbank.