Files
schalli 5c42c558c4
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 56s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m52s
docs(quick-260914-ku1): Kanaele und Versionsstempel abgeschlossen und verifiziert 8/8 — Zusammenfassung, Verifikation, Aktenstand
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 16:01:13 +02:00

21 KiB
Raw Permalink Blame History

phase, plan, subsystem, tags, status, requires, provides, affects, actuals, plan_head_before, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, metrics
phase plan subsystem tags status requires provides affects actuals plan_head_before tech-stack key-files key-decisions patterns-established requirements-completed coverage metrics
quick-260914-ku1 01 infra
ci
gitea-actions
docker
build-args
next-public-env
nestjs
health
versionsstempel
compose
handbuch
complete
phase provides
quick-260914-ku1 planning (1cd4212) Plan mit Erlaubnisliste, gemessenen Bezugszahlen und Threat-Register
GET /health/version liefert { name, version, channel, commit, buildTime } aus APP_* (Vorgaben dev/dev), Startzeile `Tessera API <version> (<channel>) <commit>`
VersionResponse in packages/shared; apps/web/src/lib/app-version.ts als importierbare Quelle (appVersion + loadApiVersion) fuer Abzeichen und kommenden Fehler-melden-Knopf
AppVersionBadge unten in der Seitenleiste (Desktop und mobile Schublade, nicht eingeklappt) mit Tooltip Commit/API-Version
Beide Dockerfiles nehmen APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME als Build-Args; Web bettet NEXT_PUBLIC_APP_* zur Bauzeit ein
.gitea/scripts/publish-images.sh entscheidet Kanal/Etiketten aus GITHUB_REF (main -> beta+latest, v* -> live+vX.Y.Z, sonst nichts), --print-plan ohne Docker
ci.yml loest auf main, live und Tags v* aus; publish mit fetch-depth 0 und Skriptaufruf
docker-compose.prod.yml mit ${IMAGE_TAG:-beta} fuer web und api
Betriebshandbuch Kapitel 9 (Kanaele, IMAGE_TAG je Server, Freigabe, Hotfix ohne Datenbankaenderung, Versionskontrolle, neuer Live-Server); ci-cd-setup auf gemessenen Stand
fehler-melden-knopf
erstfreigabe-v1.0.0
live-server-einrichtung
deploy
tokens tasks commits
41545 3 3
1cd4212df0
added patterns
Versionsstempel-Kette: CI-Skript -> --build-arg -> globales ARG + ARG-Wiederholung je Stufe -> ENV (runner) bzw. NEXT_PUBLIC_* vor pnpm build (web-builder)
Kanalentscheidung im POSIX-Skript statt in Workflow-if-Ausdruecken, lokal per --print-plan pruefbar
process.env.NEXT_PUBLIC_* nur mit vollem Literalnamen lesen (Bauzeit-Einbettung durch Next.js)
||-Vorgaben fuer Compose-Leerstring-Semantik (leer == ungesetzt)
created modified
apps/api/src/health/app-version.ts
apps/api/src/health/health.controller.spec.ts
apps/web/src/lib/app-version.ts
apps/web/src/lib/app-version.test.ts
apps/web/src/components/layout/app-version-badge.tsx
apps/web/src/components/layout/app-version-badge.test.tsx
.gitea/scripts/publish-images.sh
packages/shared/src/index.ts
apps/api/src/health/health.controller.ts
apps/api/src/main.ts
apps/web/src/components/layout/sidebar.tsx
apps/web/src/components/layout/sidebar.test.tsx
apps/web/src/messages/de.json
apps/web/src/messages/en.json
apps/web/Dockerfile
apps/api/Dockerfile
.gitea/workflows/ci.yml
docker-compose.prod.yml
docs/anleitung-betrieb.md
docs/ci-cd-setup.md
Kanal ist die Primaerdarstellung (IMAGE_TAG, APP_CHANNEL); `latest` nur noch Alias von `beta`, damit alpha ohne Handgriff weiterlaeuft
Zweig `live` ohne Tag wird geprueft, aber nicht veroeffentlicht — nur ein Tag darf das live-Etikett belegen (T-KU1-07)
package.json-Versionen bleiben 0.0.1; die Wahrheit der Version ist der Git-Tag (git describe)
GET /health/version bleibt @Public (T-KU1-03), per Spec-Test gepinnt
apps/web spiegelt den Antworttyp lokal (ApiVersionInfo) statt @tessera/shared zu importieren — Lockfile und Docker-deps-Stufe bleiben unangetastet
Commits direkt auf main (Projektkonvention branching_strategy: none; das Kanalmodell setzt main = Beta voraus)
ARG-Sichtbarkeit in Multi-Stage-Dockerfiles: globales ARG mit Vorgabe + ARG NAME (ohne Wert) in jeder nutzenden Stufe
Memoisiertes Modul-Promise fuer einmalige API-Abfragen je Seitenladung, still bei Fehler
QUICK-260914-KU1
id description requirement verification
D1 GET /health/version aus APP_* mit Vorgaben, Compose-Leerstring-Semantik, Startzeile, @Public gepinnt QUICK-260914-KU1
kind ref status
unit apps/api/src/health/health.controller.spec.ts (6 Tests) pass
kind ref status
integration docker run tessera-ku1-api:args node -e formatAppVersionLine() -> Tessera API v9.9.9-test (live) abc1234 pass
id description requirement verification
D2 Web-Quelle app-version.ts (appVersion, loadApiVersion memoisiert/still) und AppVersionBadge in der Seitenleiste QUICK-260914-KU1
kind ref status
unit apps/web/src/lib/app-version.test.ts (5), app-version-badge.test.tsx (4), sidebar.test.tsx#renders the version badge below the navigation pass
kind ref status
integration grep -rl v9.9.9-test /app/apps/web/.next/static | wc -l -> 1 (Bauzeit-Einbettung) pass
id description requirement verification
D3 CI-Trigger je Kanal, publish-images.sh, Build-Args in beiden Dockerfiles, IMAGE_TAG in Compose QUICK-260914-KU1
kind ref status
automated_ui js-yaml-Strukturpruefung, --print-plan in drei Lagen, docker compose config --images mit/ohne IMAGE_TAG pass
kind ref status
e2e Gitea-Actions-Lauf 297 (success) und CI-gebaute :beta-Abbilder mit APP_VERSION=ea6aa99 APP_CHANNEL=beta pass
id description requirement verification
D4 Betriebshandbuch Kapitel 9 und ci-cd-setup auf gemessenen Stand QUICK-260914-KU1
kind ref status
other grep-Gates (Kapitelueberschrift 1, IMAGE_TAG 11, Hotfix-Regel 1, veraltete Aussagen 0, Skriptname 4, Umlaute in ci-cd-setup 0) pass
duration completed
22 min (13:28Z bis 13:50Z, davon ca. 7,5 min lokale Docker-Bauten und 5,3 min CI-Lauf) 2026-09-14

Quick 260914-ku1 Plan 01: Zwei Auslieferungskanaele (Beta auf main, Live per Tag) mit Versionsstempel durch alle Schichten — Summary

Versionsstempel APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME fliesst vom CI-Skript ueber Build-Args in beide Abbilder, aus GET /health/version und dem Startlog der API und als v<Version> · <Kanal> unten in der Seitenleiste; main veroeffentlicht beta+latest, ein Tag v* veroeffentlicht live+vX.Y.Z; docker-compose.prod.yml waehlt den Kanal ueber ${IMAGE_TAG:-beta}; das Betriebshandbuch erklaert Kanaele, Freigabe, Hotfix (ohne Datenbankaenderung) und den neuen Live-Server. Der echte CI-Weg ist einmal durchlaufen: Lauf 297 hat :beta-Abbilder mit ea6aa99 beta gebaut.

Ausgangslage und Bezugspunkt

Alle Gates gegen 6c19451 (Code unangetastet seit Planung; HEAD bei Start 1cd4212, Arbeitsbaum sauber, main == origin/main).

Baseline vor jeder Aenderung (erneut gemessen, identisch mit der Planung):

Suite Befehl Ergebnis
API pnpm -C apps/api exec vitest run Test Files 64 passed (64) / Tests 1054 passed (1054), Exit 0
Web pnpm -C apps/web exec vitest run Test Files 38 passed (38) / Tests 233 passed (233), Exit 0

Task 1 — Versionsstempel durch alle Schichten (TDD)

RED-Lauf (Schritt A, vor jedem Produktionscode)

pnpm -C apps/api exec vitest run src/health/health.controller.spec.ts (Exit 1):

Error: Cannot find module './app-version' imported from '.../apps/api/src/health/health.controller.spec.ts'
 Test Files  1 failed (1)
      Tests  no tests

pnpm -C apps/web exec vitest run src/lib/app-version.test.ts src/components/layout/app-version-badge.test.tsx src/components/layout/sidebar.test.tsx (Exit 1):

 ❯ src/components/layout/app-version-badge.test.tsx (0 test)   Failed to resolve import "./app-version-badge"
 ❯ src/lib/app-version.test.ts (0 test)                       Failed to resolve import "./app-version"
 ❯ src/components/layout/sidebar.test.tsx (6 tests | 1 failed)
     × renders the version badge below the navigation          Unable to find an element by: [data-testid="app-version-badge"]
 Test Files  3 failed (3)
      Tests  1 failed | 5 passed (6)

GREEN und Gate (Schritt B/C)

Verify-Block von Task 1, gemessen:

Pruefung Erwartet Gemessen
API-Spec health.controller.spec.ts Tests 6 passed (6) Tests 6 passed (6)
Web drei Specs Test Files 3 passed (3) / Tests 15 passed (15) Test Files 3 passed (3) / Tests 15 passed (15)
Plan-Checker-Zusatz: obige drei + umlaut-guard.spec.ts + tenderRadar-parity.spec.ts in EINEM Aufruf gruen Test Files 5 passed (5) / Tests 21 passed (21)
grep -c npm_package_version health.controller.ts 0 0
grep -c "formatAppVersionLine()" main.ts 1 1
grep -c process.env.NEXT_PUBLIC_APP_VERSION app-version.ts 1 1
grep -c "<AppVersionBadge />" sidebar.tsx 1 1
Node-Zeile Uebersetzungen Beta Live Entwicklung | Development Beta Live Entwicklung | Development
tsc --noEmit shared / api / web 0 / 0 / 0 0 / 0 / 0
API-Suite voll 65 / 1060 Test Files 65 passed (65) / Tests 1060 passed (1060)
Web-Suite voll 40 / 243 Test Files 40 passed (40) / Tests 243 passed (243)

Commit cdb571c — git show --stat HEAD zeigt 13 Dateien (471+/8-).

Task 2 — Build-Args, Veroeffentlichungs-Skript, CI-Trigger, IMAGE_TAG

Gates ohne Docker (Schritt F), gemessen

branches=["main","live"] tags=["v*"] jobs=quality,test,publish fetchDepth=0 script=true needs=test
main_plan=4   tag_plan=4   live_plan=0   stamp=1
compose_beta=2   compose_live=2   latest-in-compose=0
ARG APP_VERSION=dev: api 1 / web 1;  NEXT_PUBLIC_APP_VERSION=$APP_VERSION: 1;  ENV APP_VERSION=$APP_VERSION: web 1 / api 1;  EXEC=0
git describe --tags --always -> cdb571c (kein Tag vorhanden, Bau vor dem ersten Tag scheitert nicht)

--print-plan in den drei Lagen (woertlich):

GITHUB_REF=refs/heads/main:
Tessera cdb571c (beta) cdb571c 2026-09-14T13:33:40Z -> Etiketten: beta latest
push localhost:3002/schalli/tessera-ctl/web:beta
push localhost:3002/schalli/tessera-ctl/web:latest
push localhost:3002/schalli/tessera-ctl/api:beta
push localhost:3002/schalli/tessera-ctl/api:latest

GITHUB_REF=refs/tags/v1.2.3:
Tessera cdb571c (live) cdb571c 2026-09-14T13:33:40Z -> Etiketten: live v1.2.3
push localhost:3002/schalli/tessera-ctl/web:live
push localhost:3002/schalli/tessera-ctl/web:v1.2.3
push localhost:3002/schalli/tessera-ctl/api:live
push localhost:3002/schalli/tessera-ctl/api:v1.2.3

GITHUB_REF=refs/heads/live:
Kein Veroeffentlichungs-Anlass fuer 'refs/heads/live' (nur main und Tags v*): nichts zu tun.

docker compose -f docker-compose.prod.yml config --images ohne Variable: web:beta, api:beta, postgres:16-alpine; mit IMAGE_TAG=live: api:live, postgres:16-alpine, web:live.

Falsifizierung Bauzeit-Einbettung (Schritt E)

Baudauer (warmer deps-Cache): api:args 111 s, web:args 129 s, api:noargs 113 s, web:noargs 99 s — zusammen 7 min 32 s, alle Exit 0.

# Befehl (Kurzform) Erwartet Gemessen
1 api:args node APP_VERSION APP_CHANNEL APP_COMMIT APP_BUILD_TIME v9.9.9-test live abc1234 2026-09-14T00:00:00Z v9.9.9-test live abc1234 2026-09-14T00:00:00Z
2 api:args node require("/app/apps/api/dist/health/app-version").formatAppVersionLine() Tessera API v9.9.9-test (live) abc1234 Tessera API v9.9.9-test (live) abc1234
3 web:args sh grep -rl v9.9.9-test /app/apps/web/.next/static | wc -l >= 1 1
4 web:args node APP_VERSION v9.9.9-test v9.9.9-test
5 api:noargs node APP_VERSION APP_CHANNEL / web:noargs node APP_VERSION dev dev / dev dev dev / dev
6 web:noargs Bundle-Grep auf v9.9.9-test 0 0
6b api:noargs dist-Zeile (Zusatz) Tessera API dev (dev) Tessera API dev (dev)

Aufgeraeumt: docker rmi der vier tessera-ku1-*-Etiketten und tessera-web-plancheck:baseline (alle „Untagged", docker images zeigt keine mehr).

Commit 9731501 — 5 Dateien (115+/13-).

Task 3 — Handbuch, ci-cd-setup, Push, CI-Beobachtung

Precondition: curl localhost:3002/api/v1/version -> {"version":"1.26.2"}; docker ps | grep -c ^gitea-runner$ -> 1.

Gates, gemessen:

Pruefung Erwartet Gemessen
grep -c "^## 9. Zwei Kanäle: Live und Beta" 1 1
grep -c IMAGE_TAG anleitung-betrieb.md >= 6 11
grep -c "Keine Datenbankänderung als Hotfix" 1 1
grep -c "Kein Registry-Push|build-deploy" ci-cd-setup.md 0 0
grep -c publish-images.sh ci-cd-setup.md >= 2 4
grep -c '[äöüÄÖÜß]' ci-cd-setup.md 0 0
git diff --stat 6c19451 -- . ':!.planning' GIT_EXIT=0, 20 files changed GIT_EXIT=0, 20 files changed, 851 insertions(+), 49 deletions(-)
Unangetastet-Stichprobe (prisma, biome.json, .env*, package.json, lockfile) U_EXIT=0, U_EMPTY=0 U_EXIT=0, U_EMPTY=0
git status -sb nach Push kein [ahead ## main...origin/main

Commit ea6aa99 — 2 Dateien (265+/28-). git push -> 6c19451..ea6aa99 main -> main.

CI-Lauf nach dem Push

Feld Wert
Lauf-ID 297 (event push, ref main, head_sha = ea6aa99…)
status / conclusion completed / success
started_at / completed_at 2026-09-14T15:44:23+02:00 / 2026-09-14T15:49:41+02:00 — 5 min 18 s (Planung: 4-6 min mit Build-Args)
api:beta node APP_VERSION APP_CHANNEL APP_COMMIT APP_BUILD_TIME ea6aa99 beta ea6aa99 2026-09-14T13:46:12Z (git rev-parse --short ea6aa99 = ea6aa99)
web:beta node APP_VERSION APP_CHANNEL ea6aa99 beta
web:beta Bundle-Grep auf ea6aa99 1 (Bauzeit-Einbettung ueber den echten CI-Weg)
docker image inspect Created web:beta / web:latest beide 2026-09-14T15:47:54.520283239+02:00 (ein Bau, zwei Etiketten, nach dem Push)
docker image inspect Created api:beta / api:latest beide 2026-09-14T15:48:40.191081238+02:00
Registry (Gitea-API /packages/schalli?type=container) tessera-ctl/web beta 15:47:59, latest 15:48:00; tessera-ctl/api beta und latest 15:49:34

Kein Fehlversuch, ein einziger Push, ein einziger Lauf.

Abschluss-Verifikation (nach Task 3)

  • API Test Files 65 passed (65) / Tests 1060 passed (1060), Exit 0
  • Web Test Files 40 passed (40) / Tests 243 passed (243), Exit 0
  • tsc --noEmit: TSC_packages/shared=0, TSC_apps/api=0, TSC_apps/web=0
  • git fetch -q && git status -sb | head -1 -> ## main...origin/main
  • git ls-remote --heads origin live | wc -l -> 0, git tag | wc -l -> 0 (Zweig live und Tag v1.0.0 wie geplant NICHT angelegt)

git status --porcelain (vor dem Schreiben dieses SUMMARY): leer.

git log --oneline 1cd4212..HEAD:

ea6aa99 docs(quick-260914-ku1): Betriebshandbuch — Zwei Kanäle Live und Beta, Freigabe, Hotfix ohne Datenbankänderung, neuer Live-Server; ci-cd-setup auf gemessenen Stand
9731501 ci(quick-260914-ku1): zwei Kanaele — main -> beta+latest, Tag v* -> live+vX.Y.Z, Versionsstempel als Build-Args in beide Dockerfiles, IMAGE_TAG in docker-compose.prod.yml
cdb571c feat(quick-260914-ku1): Versionsstempel — GET /health/version aus APP_*, VersionResponse, app-version.ts und Abzeichen v<Version> · <Kanal> in der Seitenleiste

commits: 3 gemessen aus git rev-list --count 1cd4212..HEAD; actuals.tokens = 166183 Zeichen ueber die 20 geaenderten Dateien / 4 = 41545 (der reine Diff waere 49515 Zeichen = 12378).

Deviations from Plan

Auto-fixed Issues

None — plan executed exactly as written. Alle Zahlen des Plans (65/1060, 40/243, 20 Dateien, 13/5/2 Dateien je Commit, Grep-Werte) wurden exakt getroffen; keine Erwartung musste angepasst werden.

Prozess-Abweichung (dokumentiert, keine Code-Abweichung)

Commits auf main. Die Executor-Vorschrift verlangt eigentlich einen Nicht-Standard-Zweig. Dieses Projekt arbeitet per branching_strategy: none seit jeher direkt auf main, der Auftrag verlangt ausdruecklich git push auf main, und das Kanalmodell dieses Plans definiert main = Beta — ein Seitenzweig haette den Plan nicht erfuellen koennen (die Pipeline haette nichts gebaut). Kein git update-ref, kein Force-Push, kein Eingriff in .planning/config.json.

Nebenbefunde

  • apps/web/src/components/layout/sidebar-footer.tsx ist seit ba02b25 (2026-06-26) toter Code: wird nirgends gerendert, einziger Treffer ausserhalb der Datei ist der Mock in sidebar.test.tsx. Unangetastet gelassen (nicht in der Erlaubnisliste); Kandidat fuer einen Aufraeum-Quick-Task.
  • .env.prod.example bewusst nicht angefasst (Regel: keine .env*-Dateien). Die Zeile IMAGE_TAG steht nur im Handbuch (Kapitel 3 Tabelle, Kapitel 9 mit ausdruecklichem Hinweis, dass die Vorlage sie noch nicht enthaelt).
  • Der Web-Bau laeuft in der CI jetzt bei jedem Lauf durch die builder-Stufe (NEXT_PUBLIC_APP_COMMIT aendert sich je Commit) — gemessen 5 min 18 s statt unter 2 min zuvor; im ci-cd-setup vermerkt.
  • Handbuch Kapitel 6/7 nennen weiterhin /opt/tessera/docker-compose.yml fuer die Volume-Reparatur; Kapitel 9 nennt die tatsaechlich benutzte Datei /opt/tessera/docker-compose.prod.yml (.env setzt COMPOSE_FILE). Die aelteren Stellen wurden nicht umgeschrieben (nicht Teil des Plans).

Threat Flags

Keine neue Angriffsflaeche ausserhalb des Threat-Registers des Plans: GET /health/version war bereits @Public (T-KU1-03, akzeptiert und per Spec-Test 6 gepinnt); das Skript kennt kein Secret; das Push-Token wurde in Task 3 nur in einer Shell-Variablen benutzt (Ausgabe der Push-URL im Log mit *** maskiert).

Known Stubs

Keine. Alle neuen Werte sind an echte Quellen gebunden (Umgebung, /health/version); ohne Build-Args greifen die bewussten Vorgaben dev.

Was bewusst offen bleibt

  • Zweig live und Tag v1.0.0 sind NICHT angelegt — Rezept steht im Handbuch Kapitel 9 („Erstfreigabe v1.0.0"); erfolgt nach dem Fehler-melden-Knopf (eigener Quick-Task, der apps/web/src/lib/app-version.ts importiert).
  • Server nicht angefasst: alpha (/opt/tessera/.env und docker-compose.prod.yml) und der neue Live-Server werden vom User eingerichtet, siehe „Handgriffe" unten. Bis dahin zieht alpha weiter :latest = dasselbe Beta-Abbild.
  • .env.prod.example ohne IMAGE_TAG-Zeile (Regel .env*); ein spaeterer Quick-Task darf sie ergaenzen.
  • latest bleibt als Alias von beta, bis alpha auf IMAGE_TAG=beta umgestellt ist; danach kann das Etikett aus dem Skript entfallen.
  • Tag-Schutz v* und Branch-Schutz live in Gitea (T-KU1-04) — heute nicht noetig (nur schalli hat Schreibrecht), im ci-cd-setup als Empfehlung fuer den Fall weiterer Konten.
  • Human-Check (end-of-phase, nicht blockierend): lokal docker compose up -d --build web api und unten in der Seitenleiste dev · Entwicklung sehen, Tooltip API dev (dev); eingeklappt verschwindet die Zeile.

Handgriffe fuer den User

Aus dem Handbuch Kapitel 9 („Die eine Zeile je Server" und „Den neuen Live-Server einrichten"):

Auf alpha (Beta), einmalig:

  1. In /opt/tessera/.env die Zeile IMAGE_TAG=beta eintragen.
  2. Sicherung der Compose-Datei:
    cd /opt/tessera
    cp docker-compose.prod.yml docker-compose.prod.yml.bak.$(date +%Y%m%d)
    
  3. In /opt/tessera/docker-compose.prod.yml die zwei image:-Zeilen aendern (nur das Ende der Zeile):
        image: git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG:-beta}
        image: git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG:-beta}
    
  4. Dann wie in Kapitel 4:
    docker compose -f docker-compose.prod.yml pull
    docker compose -f docker-compose.prod.yml up -d --force-recreate api web
    
  5. Kontrolle: unten in der Seitenleiste steht <Kurzkennung> · Beta; curl -s http://localhost:3001/health/version zeigt "channel":"beta".

Auf dem neuen Live-Server (tessera.ctl.de): Kapitel 2 des Handbuchs vollstaendig, mit diesen Abweichungen:

  • IMAGE_TAG=live in der .env (Pflicht — ohne die Zeile zieht der Server die Beta).
  • Eigene, neu erzeugte Geheimnisse (JWT_SECRET, TESSERA_ENCRYPTION_KEY, DB_PASSWORD, Admin-Passwort); nichts von alpha uebernehmen.
  • Eigene, leere Datenbank; die alpha-Datenbank wird NICHT kopiert (falls doch gewuenscht: Kapitel 6 UND derselbe TESSERA_ENCRYPTION_KEY).
  • APP_URL=https://tessera.ctl.de.
  • Der erste pull holt :live — dieses Etikett gibt es erst nach der Erstfreigabe v1.0.0. Also erst freigeben (Claude: git checkout -b live main, Tag v1.0.0, Push), dann installieren; oder fuer einen Probelauf voruebergehend IMAGE_TAG=beta, danach auf live umstellen und pull + up -d --force-recreate api web wiederholen.

Bei jeder Freigabe danach (User sagt „Version X freigeben", Claude pusht Zweig und Tag), auf dem Live-Server:

docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --force-recreate api web

Self-Check: PASSED

  • Dateien vorhanden: alle 7 neu erstellten Dateien (app-version.ts api/web, beide Specs/Tests, Badge + Test, publish-images.sh) — FOUND.
  • Commits vorhanden: cdb571c, 9731501, ea6aa99 — FOUND (git log --oneline 1cd4212..HEAD), alle auf origin/main.