7b49747c72
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
436 lines
32 KiB
Markdown
436 lines
32 KiB
Markdown
---
|
|
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."
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
|
@~/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.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.
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: user-files dauerhaft speichern und das Betriebshandbuch nachziehen (WINDOWS #17)</name>
|
|
<files>docker-compose.yml, docker-compose.prod.yml, docs/anleitung-betrieb.md</files>
|
|
|
|
<precondition>Die Docker-CLI ist lokal aufrufbar (`docker compose version` antwortet). Die Pruefung rendert ausschliesslich Konfiguration und startet, baut und stoppt nichts.</precondition>
|
|
|
|
<reversibility rating="costly">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.</reversibility>
|
|
|
|
<action>
|
|
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 (`<projekt>_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.
|
|
</action>
|
|
|
|
<verify>
|
|
<automated>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)"</automated>
|
|
<note>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.</note>
|
|
</verify>
|
|
|
|
<done>
|
|
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.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Versionsangaben in CLAUDE.md auf den installierten Stand bringen</name>
|
|
<files>CLAUDE.md, .planning/research/STACK.md</files>
|
|
|
|
<reversibility rating="reversible">Reine Dokumentationsaenderung, jederzeit zuruecknehmbar.</reversibility>
|
|
|
|
<action>
|
|
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 `<!-- GSD:stack-start ... -->` und `<!-- GSD:stack-end -->`
|
|
(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.
|
|
</action>
|
|
|
|
<verify>
|
|
<automated>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)"</automated>
|
|
<automated>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</automated>
|
|
<note>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.</note>
|
|
</verify>
|
|
|
|
<done>
|
|
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.
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## 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 |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
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.
|
|
|
|
<human-check>
|
|
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.
|
|
</human-check>
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
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.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
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.
|
|
</output>
|