diff --git a/.planning/quick/260909-cx0-dateisicherung-nachruesten-und-versionsa/260909-cx0-PLAN.md b/.planning/quick/260909-cx0-dateisicherung-nachruesten-und-versionsa/260909-cx0-PLAN.md
new file mode 100644
index 0000000..c627773
--- /dev/null
+++ b/.planning/quick/260909-cx0-dateisicherung-nachruesten-und-versionsa/260909-cx0-PLAN.md
@@ -0,0 +1,435 @@
+---
+quick_id: 260909-cx0
+slug: dateisicherung-nachruesten-und-versionsa
+date: 2026-09-09
+status: planned
+relates_to: 06-desktop-client-ci-cd, 07-dkv-fleet-module
+windows_ref: 17
+severity: medium
+
+phase: quick-260909-cx0
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - docker-compose.yml
+ - docker-compose.prod.yml
+ - docs/anleitung-betrieb.md
+ - CLAUDE.md
+ - .planning/research/STACK.md
+autonomous: true
+requirements: [WINDOWS-17]
+
+estimate:
+ tokens: 45000
+ raw_tokens: 45000
+ tasks: 2
+ confidence: low
+
+must_haves:
+ truths:
+ - "Ein Neuerstellen der Container (`up -d --force-recreate api`) auf Basis der Compose-Dateien aus dem Repository laesst hochgeladene Profilbilder und DKV-Exporte bestehen — sie liegen dann ausserhalb der beschreibbaren Container-Schicht."
+ - "Die gerenderte Konfiguration von `docker-compose.yml`, von `docker-compose.prod.yml` und der Kombination Basis + `docker-compose.dev.yml` traegt fuer den Dienst `api` je genau einen Mount mit dem Ziel `/app/user-files`."
+ - "Der Speicherort ist ein benanntes Volume, kein Host-Verzeichnis — der API-Prozess laeuft als uid 1001 und darf ohne Zutun des Betreibers hineinschreiben."
+ - "Das Betriebshandbuch (Kapitel 6) beschreibt den Speicher als dauerhaft, nennt die Mount-Zeile woertlich zum Uebernehmen in die abweichende Serverdatei und behauptet nicht mehr, `pgdata` sei der einzige dauerhafte Datenspeicher."
+ - "Das Betriebshandbuch sagt ausdruecklich, dass die Aenderung im Repository die laufende Installation NICHT erreicht und der Betreiber sie in `/opt/tessera/docker-compose.yml` selbst eintragen muss."
+ - "CLAUDE.md nennt fuer jede aufgefuehrte Technik die Fassung, die `pnpm install` tatsaechlich aufloest beziehungsweise die in den Compose-/Dockerfiles steht."
+ - "Technik, die empfohlen, aber nie eingebaut wurde, steht in CLAUDE.md nicht mehr als Bestandteil des Systems, sondern sichtbar als nicht uebernommene Empfehlung mit Angabe dessen, was stattdessen laeuft."
+ - "CLAUDE.md und `docs/anleitung-entwicklung.md` widersprechen einander in keiner Versionsangabe mehr."
+ - "Keine Abhaengigkeit wurde aktualisiert: `package.json` (Wurzel und je App) und `pnpm-lock.yaml` sind gegenueber dem Ausgangsstand unveraendert."
+ artifacts:
+ - docker-compose.yml
+ - docker-compose.prod.yml
+ - docs/anleitung-betrieb.md
+ - CLAUDE.md
+ - .planning/research/STACK.md
+ key_links:
+ - "`apps/api/Dockerfile:23-26` (Verzeichnis `/app/user-files` gehoert uid 1001 `nestjs`) plus `:36` (`USER nestjs`) <-> Wahl der Mount-Art: ein leeres benanntes Volume uebernimmt diese Eigentuemerschaft beim ersten Anlegen, ein frisch von Docker erzeugtes Host-Verzeichnis gehoert root. Deshalb benanntes Volume."
+ - "`apps/api/src/user/user.controller.ts:40` und `apps/api/src/dkv/dkv-export.service.ts:59` loesen beide vier Ebenen ueber `dist/` hinaus auf und landen im Container auf `/app/user-files` — exakt dem Mount-Ziel. Ein anderes Ziel wuerde am Schreibort vorbei mounten und die Luecke offen lassen."
+ - "Repository-Compose <-> `/opt/tessera/docker-compose.yml`: es gibt keine Verbindung. Der Deploy holt nur Images; die Aenderung wirkt erst, wenn der Betreiber sie dort selbst eintraegt."
+ - "CLAUDE.md-Block `GSD:stack-start source:research/STACK.md` <-> `.planning/research/STACK.md`: der Block ist eine woertliche Kopie der Empfehlung von 2026-06/07. Ohne Vermerk auf beiden Seiten holt eine kuenftige Regeneration die falschen Zahlen zurueck."
+---
+
+
+Zwei kleine, voneinander vollstaendig unabhaengige Punkte. Getrennte Aufgaben, getrennte Commits.
+
+**Punkt A (WINDOWS #17) — hochgeladene Dateien ueberleben kein Neuerstellen der Container.**
+Der Code legt Profilbilder und DKV-Exporte unter `user-files/` ab, aber keine Compose-Datei
+im Repository mountet dieses Verzeichnis. Es existiert damit nur in der beschreibbaren
+Container-Schicht und ist bei jedem `up -d --force-recreate` weg. Bisher ist kein Schaden
+entstanden — es hat noch kein Nutzer ein Profilbild hinterlegt —, aber der erste Upload
+faellt in dieselbe Grube.
+
+**Punkt B — CLAUDE.md nennt Fassungen, die nicht installiert sind.**
+Die Technik-Tabelle ist eine woertliche Kopie der Stack-Empfehlung von 2026-06/07 und
+beschreibt einen Wunschzustand: Next.js 16.2.x statt der installierten 15.5.19, Prisma 7.8.x
+statt 6.19.3, dazu Keycloak, Redis, TanStack Query, shadcn/ui, Playwright, Husky und
+lint-staged, von denen nichts im Projekt existiert. Das neue Entwicklungshandbuch
+(`docs/anleitung-entwicklung.md`) dokumentiert bewusst den echten Stand — beide Dokumente
+widersprechen sich damit schriftlich.
+
+Purpose: Nutzerdaten ueberstehen einen Redeploy, und wer neu dazustoesst, liest in CLAUDE.md,
+was wirklich installiert ist, statt was einmal empfohlen wurde.
+Output: Zwei getrennte, jeweils fuer sich lauffaehige Commits — A (Datenhaltung + Handbuch),
+B (Dokumentationskorrektur).
+
+## Gemessener Ausgangszustand (am Arbeitsbaum geprueft, 2026-09-09)
+
+### Punkt A
+
+| Beleg | Fundstelle |
+|-------|------------|
+| Profilbilder werden nach `user-files/avatars/` geschrieben | `apps/api/src/user/user.controller.ts:40` (`path.resolve(__dirname, '..','..','..','..','user-files','avatars')`), Ablage `:249-266` |
+| DKV-Exporte werden nach `user-files/` geschrieben | `apps/api/src/dkv/dkv-export.service.ts:59` (gleiche Aufloesung), Schreiben `:135` |
+| Im Image ist der Zielpfad `/app/user-files`, angelegt und uebereignet an uid 1001 | `apps/api/Dockerfile:21` (`WORKDIR /app`), `:23-26` (`mkdir -p /app/user-files` + `chown nestjs:nestjs`), `:36` (`USER nestjs`) |
+| `docker-compose.yml` kennt nur ein Volume, `pgdata`; der Dienst `api` hat gar keinen `volumes`-Block | `docker-compose.yml:20-65` (api) und `:92-93` (Top-Level-Volumes) |
+| `docker-compose.prod.yml` ebenso | `docker-compose.prod.yml:22-61` (api) und `:89-90` |
+| `docker-compose.dev.yml` mountet nur Quellcode und Prisma-Schema | `docker-compose.dev.yml:7-10` |
+| Gerenderte Konfiguration traegt fuer `api` keinen Mount auf `/app/user-files` — in allen drei Kombinationen | selbst gemessen mit `docker compose config --format json` fuer Basis, Prod und Basis+Dev: dreimal `FEHLT` |
+| Das Betriebshandbuch beschreibt die Luecke bereits und raet zu manuellem Herauskopieren | `docs/anleitung-betrieb.md:278-290` (Kapitel 6) und `:316` (Fehlertabelle Kapitel 7) |
+
+### Punkt B
+
+Verglichen wurden die Tabellenwerte in `CLAUDE.md:26-97` gegen `package.json` (Wurzel und je App)
+und die in `pnpm-lock.yaml` aufgeloesten Fassungen (`importers:`-Abschnitt), dazu die
+Compose- und Dockerfiles.
+
+| CLAUDE.md sagt | Tatsaechlich installiert (2026-09-09) |
+|---|---|
+| Next.js 16.2.x (`:39`) | **15.5.19** (Vorgabe `^15.3.0`) |
+| Prisma 7.8.x (`:56`) | **6.19.3** — `prisma` und `@prisma/client`, Vorgabe `^6.0.0`; auch `apps/api/Dockerfile:33` nennt 6.19.3 |
+| Keycloak 26.6.x als Identitaetsanbieter (`:64`), `nest-keycloak-connect` (`:65`) | **nicht vorhanden** — kein Keycloak-Dienst in irgendeiner Compose-Datei, kein Paket im Lockfile. Angemeldet wird ueber `@nestjs/jwt` 11.0.2, `passport` 0.7.0, `argon2` 0.44.0, Verzeichnisanbindung ueber `ldapts` 8.1.8 |
+| Redis 7.x als Cache/Sitzungsspeicher (`:58`) | **nicht vorhanden** — kein Dienst, kein Paket |
+| TanStack Query 5.101.x (`:48`) | **nicht installiert** |
+| shadcn/ui CLI v4 (`:43`) | **nicht benutzt** — keine `components.json`, kein `components/ui`-Verzeichnis |
+| Playwright 1.x als E2E-Test (`:88`) | **keine Projektabhaengigkeit** — Browserpruefungen laufen ueber das Playwright-MCP-Werkzeug, im Repository existiert keine E2E-Suite und keine `playwright.config.*` |
+| Husky 9.x (`:96`), lint-staged 15.x (`:97`) | **nicht installiert**, kein `.husky`-Verzeichnis |
+| Vitest 3.x (`:87`) | gemischt: `apps/api` 3.2.6, `apps/web` **4.1.9** |
+| Docker 27.x / Compose 2.x (`:78-79`) | Wirtseigenschaft, vom Repository nicht gesetzt; auf dieser Maschine gemessen: Docker 29.8.0, Compose v5.5.1 |
+| Tauri 2.x (`:72`) | 2.11.1 / CLI 2.11.3 — stimmt; `apps/desktop` ist laut `docs/anleitung-entwicklung.md:38-40` bisher nur Geruest |
+| pnpm 9.x, Turborepo 2.9.x, React 19.x, TypeScript 5.5+, Tailwind 4.3.x, next-themes 0.4.x, next-intl 4.13.x, react-grid-layout 2.2.x, Zustand 5.0.x, NestJS 11.x, Express 5.x, PostgreSQL 16.x, Biome 2.x, Testing Library | stimmen. Genau: pnpm 9.15.0, Turborepo 2.9.18, React/React-DOM 19.2.7, TypeScript 5.9.3, Tailwind 4.3.1, next-themes 0.4.6, next-intl 4.13.0, react-grid-layout 2.2.3, Zustand 5.0.14, NestJS 11.1.27, Express 5.2.1 (mittelbar ueber `@nestjs/platform-express`), `postgres:16-alpine`, Biome 2.5.0, `@testing-library/react` 16.3.2 |
+| (fehlt in der Tabelle) | Node 24 — beide produktiven Dockerfiles ziehen `node:24-alpine` |
+
+## Zur Verteilung auf den Server (bitte woertlich weitergeben, NICHT ausfuehren)
+
+`/opt/tessera/docker-compose.yml` ist **keine** Arbeitskopie dieses Repositorys. Die Datei
+wurde dort von Hand bearbeitet und weicht ab (Sicherung `docker-compose.yml.bak.20260811`).
+Der Deploy holt ausschliesslich Images. Eine Aenderung an den Compose-Dateien im Repository
+erreicht die laufende Installation deshalb **nie**.
+
+Damit die Reparatur auf alpha wirkt, muss der Nutzer dieselben zwei Zeilen selbst in
+`/opt/tessera/docker-compose.yml` eintragen und die Container einmal neu erstellen. Dieser
+Plan sieht dafuer **keine** Handlung vor: kein SSH, kein `docker`-Aufruf gegen irgendeinen
+Server, keine Datei ausserhalb dieses Arbeitsbaums. Das Betriebshandbuch bekommt die
+Anleitung dafuer schriftlich, damit sie nicht in einer Sitzung verloren geht.
+
+Solange das nicht geschehen ist, bleibt der Ledger-Eintrag WINDOWS #17 **offen** — die
+Luecke besteht auf der laufenden Installation weiter. Der Executor schliesst ihn nicht;
+das Schliessen ist die Entscheidung des Nutzers, nachdem er die Serverdatei ergaenzt hat
+(`gsd-tools windows fixed 17`).
+
+## Zur Wahl der Speicherart (Begruendung, wie gefordert)
+
+Gewaehlt: **benanntes Volume** `user-files`, gemountet auf `/app/user-files`.
+
+Ausschlaggebend ist die Eigentuemerschaft. Das Image legt `/app/user-files` an und uebereignet
+es uid 1001 (`apps/api/Dockerfile:23-26`), der Prozess laeuft als dieser Nutzer (`:36`). Ein
+leeres benanntes Volume uebernimmt beim ersten Mounten Inhalt und Eigentuemerschaft des
+Image-Verzeichnisses — Schreiben funktioniert sofort. Ein Host-Verzeichnis, das Docker beim
+Start neu anlegt, gehoert dagegen root; der API-Prozess koennte nicht hineinschreiben, und aus
+einem Datenverlust wuerde ein kaputter Upload. Ein Bind-Mount waere also nur mit einer
+zusaetzlichen Handlung des Betreibers (Verzeichnis anlegen und auf 1001 uebereignen) korrekt —
+genau die Art stiller Voraussetzung, die auf dem Server erfahrungsgemaess untergeht.
+
+Zur Sicherung passt das: das Betriebshandbuch nennt in Kapitel 6 bereits
+`docker compose cp api:/app/user-files ./user-files-backup`, und dieser Befehl funktioniert
+mit einem benannten Volume unveraendert weiter — nur ist er kuenftig eine Sicherung und keine
+Rettung mehr. Zusaetzlich passt die Wahl zum bereits vorhandenen `pgdata`, das dieselbe Form hat.
+Das Handbuch muss dennoch angefasst werden, weil Kapitel 6 heute das Gegenteil behauptet.
+
+
+
+@~/.claude/gsd-core/workflows/execute-plan.md
+@~/.claude/gsd-core/templates/summary.md
+
+
+
+@.planning/STATE.md
+@CLAUDE.md
+
+Dateien, die beim Umsetzen gelesen werden muessen:
+@docker-compose.yml
+@docker-compose.prod.yml
+@docs/anleitung-betrieb.md
+
+Belege, die nicht veraendert werden (nur zum Nachschlagen):
+- `apps/api/Dockerfile` — Zeilen 21-26 und 36 begruenden die Wahl des benannten Volumes.
+- `apps/api/src/user/user.controller.ts:40` und `apps/api/src/dkv/dkv-export.service.ts:59`
+ — beide Schreibpfade, beide landen im Container auf `/app/user-files`.
+- `docs/anleitung-entwicklung.md` — nennt bereits den echten Stand (pnpm 9.15.0, `node:24-alpine`,
+ Biome, Vitest in beiden Apps). Aufgabe B muss dazu passen, nicht davon abweichen.
+- `pnpm-lock.yaml`, Abschnitt `importers:` — die verbindliche Quelle fuer die aufgeloesten
+ Fassungen. Nicht die `package.json`-Vorgaben (`^15.3.0`) als Fassung ausgeben.
+
+Verbindliche Konventionen aus dem Bestand:
+- Die Handbuecher unter `docs/` sind deutsch mit echten Umlauten (`für`, `über`). Neue Saetze
+ dort in derselben Schreibweise. Der Umlaut-Test in `apps/web/src/messages` betrifft nur die
+ Oberflaechentexte, nicht diese Dokumente.
+- Der Technik-Block in CLAUDE.md ist englisch. Er bleibt englisch — dies ist eine
+ Zahlenkorrektur, keine Uebersetzung.
+
+
+
+
+
+ Task 1: user-files dauerhaft speichern und das Betriebshandbuch nachziehen (WINDOWS #17)
+ docker-compose.yml, docker-compose.prod.yml, docs/anleitung-betrieb.md
+
+ Die Docker-CLI ist lokal aufrufbar (`docker compose version` antwortet). Die Pruefung rendert ausschliesslich Konfiguration und startet, baut und stoppt nichts.
+
+ Die Speicherart laesst sich spaeter aendern, aber nicht folgenlos: ein Wechsel auf ein Host-Verzeichnis erfordert einmaliges Umkopieren des Volume-Inhalts und eine Uebereignung an uid 1001.
+
+
+ Beide Compose-Dateien im Repository bekommen denselben Zusatz. Nichts anderes aendern —
+ keine Umgebungsvariablen, keine Ports, keine Healthchecks.
+
+ In `docker-compose.yml`: beim Dienst `api` einen Block `volumes:` einfuegen (Einrueckung wie
+ bei `networks:` desselben Dienstes) mit dem einzigen Eintrag `- user-files:/app/user-files`.
+ Sinnvolle Stelle ist direkt vor `healthcheck:` (heute Zeile 60). Anschliessend im
+ Top-Level-Block `volumes:` (heute Zeile 92-93) neben `pgdata:` einen zweiten Eintrag
+ `user-files:` ergaenzen — ohne Wert, das ist ein benanntes Volume mit Vorgaben.
+
+ In `docker-compose.prod.yml`: identisch, Dienst `api` (Block vor `healthcheck:`, heute
+ Zeile 56) und Top-Level-Block `volumes:` (heute Zeile 89-90).
+
+ `docker-compose.dev.yml` bleibt unveraendert. Grund, gemessen: Compose fuehrt die
+ Mount-Listen ueber das Ziel zusammen, die Kombination Basis + Dev traegt den neuen Mount
+ also automatisch mit — nachgewiesen an der gerenderten Konfiguration. Ein zweiter Eintrag
+ dort waere Doppelpflege.
+
+ Danach `docs/anleitung-betrieb.md`, Kapitel 6, Abschnitt "Was sonst noch an Zustand
+ existiert" (heute Zeile 269-290). Drei Dinge:
+
+ (1) Der erste Aufzaehlungspunkt (`PostgreSQL-Daten`, Zeile 271-277) behauptet, `pgdata` sei
+ der einzige dauerhafte Datenspeicher. Das stimmt nach dieser Aenderung nicht mehr — Satz so
+ umformulieren, dass es zwei benannte Volumes gibt. Den vorhandenen Schreibfehler
+ "daürhafte" dabei mitkorrigieren.
+
+ (2) Der Aufzaehlungspunkt `Hochgeladene Dateien` (Zeile 278-290) wird ersetzt. Er muss
+ kuenftig sagen: die Dateien liegen im benannten Volume `user-files`, gemountet auf
+ `/app/user-files` im Dienst `api`, eingetragen in `docker-compose.yml` und
+ `docker-compose.prod.yml`; sie ueberstehen ein `--force-recreate`; gesichert werden sie
+ weiterhin mit `docker compose cp api:/app/user-files ./user-files-backup`, alternativ ueber
+ eine Sicherung des Volumes; das Volume traegt im `docker volume ls` den Projektnamen als
+ Praefix (`_user-files`). Die Verweise auf `apps/api/src/user/user.controller.ts`
+ und `apps/api/src/dkv/dkv-export.service.ts` als Schreibstellen bleiben erhalten. Zeigen Sie
+ dabei einen kurzen YAML-Ausschnitt mit genau den zwei neuen Zeilen — die Dienst-Zeile in der
+ Form `user-files:/app/user-files` und den Top-Level-Eintrag —, damit ein Betreiber sie
+ kopieren kann. Diese Zeichenkette ist Teil der Abnahmepruefung.
+
+ (3) Ein deutlich abgesetzter Hinweis im selben Abschnitt: `/opt/tessera/docker-compose.yml`
+ auf dem Server ist keine Arbeitskopie des Repositorys, wurde von Hand bearbeitet und wird
+ von einem Deploy nicht angefasst. Wer die Reparatur dort haben will, traegt dieselben zwei
+ Zeilen selbst ein (vorher sichern) und erstellt die Container einmal neu. Bis dahin gilt
+ fuer die laufende Installation weiterhin der alte, verlustbehaftete Zustand; pruefbar mit
+ `docker inspect tessera-api-1` und einem Blick auf `Mounts`.
+
+ Schliesslich Kapitel 7, Fehlertabelle, Zeile 316 ("Avatare/DKV-Exporte nach einem Deploy
+ verschwunden"): die Ursachenspalte trifft nach dieser Aenderung nur noch auf eine
+ Installation zu, deren Compose-Datei den Mount nicht hat. Zeile entsprechend umschreiben
+ und als Abhilfe den Eintrag der zwei Zeilen samt Verweis auf Kapitel 6 nennen, nicht mehr
+ das vorherige "langfristig ergaenzen".
+
+ Ausdruecklich nicht Teil dieser Aufgabe: irgendein Aufruf gegen alpha oder einen anderen
+ Server, `docker compose up`, `pull`, `restart` oder das Anlegen von Verzeichnissen auf
+ einem Host.
+
+
+
+ node -e "const {execFileSync}=require('child_process');const fs=require('fs');const env={...process.env,TESSERA_ENCRYPTION_KEY:'x',DATABASE_URL:'x',JWT_SECRET:'x',DB_PASSWORD:'x',TESSERA_ADMIN_EMAIL:'x',TESSERA_ADMIN_PASSWORD:'x'};const r=a=>JSON.parse(execFileSync('docker',['compose',...a,'config','--format','json'],{env}));const c=(l,cfg)=>{const m=(cfg.services.api.volumes||[]).filter(v=>v.target==='/app/user-files');console.log(l,m.length===1?'OK '+m[0].type+':'+m[0].source:'FEHLT');return m.length===1;};const doc=fs.readFileSync('docs/anleitung-betrieb.md','utf8').includes('user-files:/app/user-files');console.log('Betriebshandbuch nennt die Mount-Zeile',doc?'OK':'FEHLT');const res=[c('basis',r(['-f','docker-compose.yml'])),c('prod',r(['-f','docker-compose.prod.yml'])),c('basis+dev',r(['-f','docker-compose.yml','-f','docker-compose.dev.yml'])),doc];process.exit(res.every(Boolean)?0:1)"
+ Am 2026-09-09 gegen den unveraenderten Arbeitsbaum ausgefuehrt: alle vier Pruefungen melden FEHLT, Rueckgabewert 1. Gegen eine Kopie mit dem hier beschriebenen Zusatz melden die drei Compose-Pruefungen `OK volume:user-files`, Rueckgabewert 0. Das Tor ist also echt rot und wird durch genau diese Aenderung gruen.
+
+
+
+ Alle drei gerenderten Konfigurationen (Basis, Prod, Basis+Dev) tragen fuer den Dienst `api`
+ genau einen Mount vom Typ `volume` mit Quelle `user-files` auf `/app/user-files`; beide
+ Compose-Dateien fuehren `user-files` als benanntes Top-Level-Volume neben `pgdata`;
+ `docker-compose.dev.yml` ist unveraendert; Kapitel 6 des Betriebshandbuchs beschreibt den
+ Speicher als dauerhaft, nennt die Mount-Zeile woertlich, nennt den Sicherungsbefehl und
+ weist auf die abweichende Serverdatei hin; die Fehlerzeile in Kapitel 7 passt dazu; kein
+ Aufruf gegen einen Server wurde ausgefuehrt; WINDOWS #17 bleibt offen.
+
+
+
+
+ Task 2: Versionsangaben in CLAUDE.md auf den installierten Stand bringen
+ CLAUDE.md, .planning/research/STACK.md
+
+ Reine Dokumentationsaenderung, jederzeit zuruecknehmbar.
+
+
+ Nur Text. Keine Abhaengigkeit wird aktualisiert, kein `pnpm add`, kein `pnpm update`,
+ `package.json` und `pnpm-lock.yaml` bleiben unangetastet.
+
+ Zuerst die Zahlen selbst nachschlagen und nicht aus diesem Plan uebernehmen: `pnpm-lock.yaml`,
+ Abschnitt `importers:`, liefert je Arbeitsbereich Vorgabe und aufgeloeste Fassung. Verbindlich
+ ist die aufgeloeste Fassung. Fuer Dienste ausserhalb von npm gelten die Compose- und
+ Dockerfiles (`postgres:16-alpine`, `node:24-alpine`). Die Tabelle im `objective` dieses Plans
+ ist der am 2026-09-09 gemessene Stand und dient als Gegenprobe — weicht Ihr Befund ab,
+ zaehlt Ihr Befund, und die Abweichung gehoert in die Zusammenfassung.
+
+ Dann den Block zwischen `` und ``
+ (CLAUDE.md Zeile 22-169) ueberarbeiten. Der Block bleibt englisch.
+
+ (a) Direkt unter die Ueberschrift `## Technology Stack` einen kurzen Herkunftsvermerk setzen:
+ dass die Tabellen den installierten Stand zeigen, am 2026-09-09 gegen `package.json`,
+ `pnpm-lock.yaml` und die Compose-/Dockerfiles geprueft; dass die urspruengliche Empfehlung
+ von 2026-06/07 in `.planning/research/STACK.md` liegt; und dass eine Regeneration dieses
+ Blocks aus jener Datei die Zahlen wieder verfaelschen wuerde.
+
+ (b) In den Technik-Tabellen jede Versionsangabe auf die tatsaechlich aufgeloeste Fassung
+ setzen. Die Spaltenueberschrift so benennen, dass klar ist, dass dort der Ist-Stand steht.
+ Ergaenzen Sie eine Zeile fuer Node (`node:24-alpine`, aus beiden produktiven Dockerfiles),
+ weil das die verbindliche Laufzeit ist und bisher fehlt. Vitest bekommt beide Fassungen mit
+ Angabe der App, weil sie sich unterscheiden. Bei Docker und Docker Compose gehoert dazu,
+ dass es Wirtseigenschaften sind, die das Repository nicht festlegt — mit der auf der
+ Entwicklungsmaschine gemessenen Fassung und Datum. Bei den Authentifizierungs-Zeilen tritt
+ an die Stelle des nie eingebauten Identitaetsanbieters, was wirklich laeuft: `@nestjs/jwt`,
+ `passport` mit `@nestjs/passport`, `argon2` fuer Passwoerter und `ldapts` fuer die
+ Verzeichnisanbindung; `@nestjs/passport` traegt heute ausserdem eine falsche Zweckangabe
+ (angeblich Schluessel-Authentifizierung zwischen Modulen) — richtig ist der Einsatz fuer die
+ Anmelde- und JWT-Strategien.
+
+ (c) Alles, was empfohlen, aber nie uebernommen wurde, verschwindet aus den Ist-Tabellen und
+ erscheint stattdessen in einem neuen, deutlich benannten Abschnitt im selben Block, direkt
+ unter den Tabellen: die Empfehlung, was stattdessen im Einsatz ist, und woher die Empfehlung
+ stammt. Betroffen sind der Identitaetsanbieter samt zugehoerigem NestJS-Paket, der
+ Cache-Dienst, die Server-State-Bibliothek, die Komponentenbibliothek, der E2E-Testlaeufer und
+ die beiden Git-Hook-Werkzeuge; ebenso die beiden Faelle, in denen zwar dieselbe Technik, aber
+ ein aelterer Hauptstand installiert ist (Next.js und Prisma) — dort mit dem klaren Vermerk,
+ dass die neuere Fassung empfohlen, aber nicht uebernommen wurde, und ohne jede Aussage
+ darueber, ob eine Aktualisierung geplant sei.
+
+ Dieser Abschnitt MUSS eine Aufzaehlung sein, keine Tabelle. Die Abnahmepruefung verwirft
+ jede Zeile, die eine dieser Techniken als erste Tabellenzelle fuehrt — das ist genau die
+ Form, die "ist eingebaut" behauptet.
+
+ (d) Die drei Anschluss-Abschnitte im selben Block angleichen, damit sie der korrigierten
+ Tabelle nicht widersprechen:
+ - `## Alternatives Considered` (Zeile 99-117): einen Einleitungssatz voranstellen, dass die
+ Tabelle die Entscheidungslage von 2026-06 festhaelt und ihre Spalte "Recommended" keine
+ Aussage ueber den heutigen Stand ist. Die Tabelle selbst bleibt inhaltlich stehen.
+ - `## Version Pinning Strategy` (Zeile 147-152): die Beispielangaben auf die installierten
+ Haupt-Fassungen bringen; die Zeile zum Identitaetsanbieter-Image ist gegenstandslos und
+ gehoert in die Aufzaehlung aus (c) statt in eine Regel.
+ - `## Sources` (Zeile 154-167): als Quellen der damaligen Empfehlung kennzeichnen, nicht als
+ Belege des Ist-Stands. Die Links bleiben.
+ - `## Multi-Tenancy Strategy` (Zeile 119-125) bleibt: Prisma Client Extensions sind
+ tatsaechlich im Einsatz (`apps/api/src/prisma/prisma-tenant.extension.ts`). Diesen Beleg
+ dort ergaenzen.
+
+ (e) Zuletzt `.planning/research/STACK.md`: unter die Ueberschrift `# v1.0 Base Stack
+ (reference — unchanged)` (Zeile 97) eine einzelne, abgesetzte Hinweiszeile setzen, dass es
+ sich um die Empfehlung vom Juni/Juli 2026 handelt, dass sie teilweise nicht uebernommen
+ wurde und dass der installierte Stand in CLAUDE.md steht. Nichts loeschen, keine Zahl in
+ diesem Dokument aendern — es ist ein datiertes Rechercheergebnis und bleibt als solches
+ lesbar.
+
+ Zum Schluss `docs/anleitung-entwicklung.md` gegenlesen (nicht aendern) und in der
+ Zusammenfassung bestaetigen, dass keine Versionsangabe der beiden Dokumente einander mehr
+ widerspricht.
+
+
+
+ node -e "const t=require('fs').readFileSync('CLAUDE.md','utf8').split('\n');const has=re=>t.some(l=>re.test(l));const bad=t.filter(l=>/^\| (Keycloak|Redis|TanStack Query|Husky|lint-staged|Playwright|nest-keycloak-connect|shadcn\/ui) \|/.test(l));const res=[['Next-Zeile nennt 15.5.19',has(/^\| Next\.js \| 15\.5\.19/)],['Prisma-Zeile nennt 6.19.3',has(/^\| Prisma \| 6\.19\.3/)],['keine nicht installierte Technik als Ist-Zeile',bad.length===0]];for(const e of res)console.log(e[1]?'OK ':'ROT ',e[0]);if(bad.length)console.log('Treffer:\n'+bad.map(l=>l.slice(0,50)).join('\n'));process.exit(res.every(e=>e[1])?0:1)"
+ git diff --exit-code -- package.json apps/api/package.json apps/web/package.json apps/desktop/package.json packages/shared/package.json packages/module-sdk/package.json pnpm-lock.yaml
+ Erste Pruefung am 2026-09-09 gegen den unveraenderten Arbeitsbaum ausgefuehrt: alle drei Punkte ROT, acht Trefferzeilen, Rueckgabewert 1; gegen eine korrigierte Kopie Rueckgabewert 0. Die zweite Pruefung ist heute gruen und bleibt es nur, solange keine Abhaengigkeit angefasst wird — sie ist die Absicherung gegen ein versehentliches Aktualisieren.
+
+
+
+ Jede Versionsangabe im Technik-Block von CLAUDE.md entspricht der in `pnpm-lock.yaml`
+ aufgeloesten Fassung beziehungsweise den Compose-/Dockerfiles; eine Node-Zeile ist ergaenzt;
+ Vitest ist mit beiden Fassungen je App gefuehrt; Docker/Compose sind als Wirtseigenschaft
+ mit Messdatum gekennzeichnet; die Authentifizierungs-Zeilen beschreiben die eingebaute
+ eigene Anmeldung statt eines Identitaetsanbieters; alle nie uebernommenen Empfehlungen
+ stehen sichtbar in einer Aufzaehlung statt in den Ist-Tabellen; Herkunftsvermerk gesetzt;
+ `Alternatives Considered`, `Version Pinning Strategy` und `Sources` widersprechen der
+ Tabelle nicht mehr; `.planning/research/STACK.md` traegt eine Hinweiszeile und ist sonst
+ unveraendert; `package.json` und `pnpm-lock.yaml` sind unveraendert.
+
+
+
+
+
+
+## Trust Boundaries
+
+| Boundary | Description |
+|----------|-------------|
+| beschreibbare Container-Schicht -> dauerhafter Speicher | Von Nutzern hochgeladene Inhalte (Profilbilder, DKV-Exporte) verlassen die fluechtige Schicht und ueberdauern den Container |
+| Host-Dateisystem <-> Container (verworfene Bind-Mount-Variante) | Ein Host-Verzeichnis waere ein zweiter Zugriffsweg auf Nutzerdaten, vorbei an der API |
+| Dokument -> Leser/Agent | CLAUDE.md praegt Annahmen darueber, welche Schutzmechanismen angeblich vorhanden sind |
+
+## STRIDE Threat Register
+
+| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
+|-----------|----------|-----------|----------|-------------|-----------------|
+| T-cx0-01 | Information Disclosure | benanntes Volume `user-files` (docker-compose.yml, docker-compose.prod.yml) | low | accept | Es entsteht keine neue Zugriffsflaeche: die Dateien werden weiterhin ausschliesslich ueber authentifizierte Endpunkte ausgeliefert (`GET /users/me/avatar` streamt aus dem in der Datenbank hinterlegten Pfad; DKV-Downloads pruefen den Dateinamen gegen ein `DKV_*.xlsx`-Muster, `apps/api/src/dkv/dkv.controller.ts:134`). Es wird kein statisches Verzeichnis veroeffentlicht. Neu ist allein die Lebensdauer |
+| T-cx0-02 | Tampering | verworfene Bind-Mount-Variante | medium | mitigate | Benanntes Volume statt Host-Pfad: kein Verzeichnis des Wirts wird in den Container gereicht, kein repo-relativer Pfad legt Nutzerinhalte in die Arbeitskopie. Zusaetzlich bleibt die Eigentuemerschaft uid 1001 aus `apps/api/Dockerfile:23-26` erhalten, statt root-eigene Rechte einzufuehren |
+| T-cx0-03 | Denial of Service | Plattenbedarf des Volumes | low | accept | Wachstum ist bereits im Code begrenzt: Profilbilder sind auf 2 MB gedeckelt (`apps/api/src/user/user.controller.ts:232`) und je Nutzer bleibt genau eine Datei (aeltere Endungen werden geloescht, `:255-261`); DKV-Exporte werden auf zehn Dateien beschnitten (`MAX_EXPORT_FILES = 10`, `apps/api/src/dkv/dkv-export.service.ts:11`). Das Volume selbst hat keine Groessengrenze — als Betriebshinweis in Kapitel 6 aufgenommen, keine Codeaenderung |
+| T-cx0-04 | Spoofing | CLAUDE.md, Abschnitt Authentifizierung | medium | mitigate | Das Dokument nennt heute einen Identitaetsanbieter, der nicht existiert. Wer das glaubt, nimmt Schutzfunktionen an (Sitzungsverwaltung, Sperren, Verzeichnisfoederation), die in Wahrheit selbst gebaut sind. Task 2 ersetzt die Angabe durch die tatsaechliche Kette aus eigenem JWT, `argon2` und `ldapts` |
+| T-cx0-05 | Repudiation | WINDOWS #17 im Ledger | low | mitigate | Der Eintrag wird NICHT geschlossen. Die Reparatur im Repository erreicht die laufende Installation nicht; ein Schliessen wuerde einen Zustand behaupten, der auf alpha nicht vorliegt. Die Zusammenfassung haelt fest, was noch aussteht und wer es tut |
+| T-cx0-SC | Tampering | npm/pnpm-Installationen | n/a | accept | Dieser Plan installiert kein Paket und aendert keine Abhaengigkeit. Eine Pruefung der Paketherkunft ist deshalb nicht erforderlich; die zweite automatisierte Pruefung in Task 2 (`git diff --exit-code` auf alle `package.json` und `pnpm-lock.yaml`) erzwingt genau das |
+
+
+
+Automatisiert (beide Aufgaben, aus der Wurzel des Arbeitsbaums):
+
+1. Das Mount-Tor aus Task 1 — drei gerenderte Konfigurationen plus die Mount-Zeile im
+ Betriebshandbuch. War vor der Arbeit vierfach rot.
+2. Das Versions-Tor aus Task 2 — zwei Stichproben auf die korrigierten Zahlen plus die
+ Ausschlusspruefung fuer nie eingebaute Technik. War vor der Arbeit dreifach rot.
+3. `git diff --exit-code` auf alle `package.json` und `pnpm-lock.yaml` — belegt, dass keine
+ Abhaengigkeit angefasst wurde.
+4. `git status --short` — nur die fuenf im Plan genannten Dateien duerfen geaendert sein.
+
+
+Nachzuholen durch den Nutzer, nicht durch den Executor — beides braucht einen Neubau der
+Container, den der Nutzer selbst ausfuehrt:
+
+(a) **Lokaler Beweis, dass die Dateien ueberleben.** Container mit den geaenderten
+Compose-Dateien neu erstellen, im Portal unter den eigenen Einstellungen ein Profilbild
+hochladen, danach `docker compose up -d --force-recreate api`, Seite neu laden: das Bild ist
+noch da. Gegenprobe frueher: genau hier ging es verloren.
+
+(b) **Uebernahme auf alpha.** Dieselben zwei Zeilen in `/opt/tessera/docker-compose.yml`
+eintragen (Datei vorher sichern), Container neu erstellen, danach `docker inspect
+tessera-api-1` pruefen — unter `Mounts` muss `/app/user-files` erscheinen. Erst wenn das
+erledigt ist, darf WINDOWS #17 geschlossen werden (`gsd-tools windows fixed 17`).
+
+(c) **Durchsicht der korrigierten Technik-Tabelle** durch den Nutzer, falls gewuenscht —
+inhaltlich pruefbar ohne Fachkenntnis: es darf nichts drinstehen, was es im Projekt nicht gibt.
+
+
+
+
+1. Beide automatisierten Tore gruen, beide waren vorher nachweislich rot.
+2. Genau zwei Commits, in dieser Reihenfolge und jeweils fuer sich lauffaehig:
+ Commit 1 (Task 1) — `fix(compose): user-files dauerhaft speichern und Betriebshandbuch nachziehen`;
+ Commit 2 (Task 2) — `docs(claude): Versionsangaben auf den installierten Stand bringen`.
+3. Keine Abhaengigkeit aktualisiert, kein Server angefasst, keine Datei ausserhalb des
+ Arbeitsbaums beruehrt.
+4. WINDOWS #17 bleibt offen; die Zusammenfassung nennt den verbliebenen Schritt des Nutzers
+ woertlich und in Alltagssprache.
+5. Die Zusammenfassung nennt die gewaehlte Speicherart samt Begruendung und alle beim
+ Nachschlagen gefundenen Abweichungen von der Versionstabelle in diesem Plan.
+
+
+