From 7b49747c72d03c3f367801d33dfe4fe66d9c0b16 Mon Sep 17 00:00:00 2001 From: Schalli Date: Wed, 9 Sep 2026 09:31:51 +0200 Subject: [PATCH] docs(quick-260909-cx0): Plan fuer Dateisicherung und Versionskorrektur Zwei unabhaengige Aufgaben: user-files als benanntes Volume in beide Repository-Compose-Dateien plus Betriebshandbuch Kapitel 6/7 (WINDOWS #17), und die Technik-Tabelle in CLAUDE.md auf den installierten Stand bringen. Beide Abnahmetore vor der Arbeit als rot gemessen. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU --- .../260909-cx0-PLAN.md | 435 ++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 .planning/quick/260909-cx0-dateisicherung-nachruesten-und-versionsa/260909-cx0-PLAN.md 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. + + + +Create `.planning/quick/260909-cx0-dateisicherung-nachruesten-und-versionsa/260909-cx0-SUMMARY.md` when done. +Festhalten: die tatsaechlich eingetragene Mount-Zeile im Wortlaut (zum Kopieren fuer den +Server), das Ergebnis beider Tore vor und nach der Arbeit, jede Abweichung zwischen der +Versionstabelle dieses Plans und dem selbst nachgeschlagenen Stand, sowie der offene Rest: +Serverdatei ergaenzen und danach den Ledger-Eintrag schliessen. +