diff --git a/.planning/STATE.md b/.planning/STATE.md index a0a331b..1ec6d0e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,11 +4,11 @@ milestone: v1.2 current_phase: 17 current_phase_name: eigene-ausschreibungs-quellen-je-nutzer status: verified -stopped_at: "2026-09-14: Etappe 3c und WINDOWS #29 abgeschlossen und verifiziert; Mandantenfaehigkeit ruht auf Entscheidung des Users (3a/Etappe 4 nicht weiterverfolgen, Thema nicht ansprechen); Schalter AUS; Live-Gehen alpha 2026-09-15" -last_updated: "2026-09-14T11:51:23.000Z" +stopped_at: "2026-09-14: Quick 260914-ku1 abgeschlossen (3 Commits cdb571c/9731501/ea6aa99 gepusht, CI-Lauf 297 success, :beta-Abbilder mit ea6aa99 beta); offen: Fehler-melden-Knopf, danach Erstfreigabe v1.0.0 (Zweig live + Tag), Server-Handgriffe durch den User" +last_updated: "2026-09-14T13:53:23.837Z" last_activity: 2026-09-14 last_activity_desc: Quick 260910-jab — drei zu kurz greifende RLS-Regeln geschlossen (GroupMembership beide Seiten, ModuleGrant beide Ziele, TenderRssFeedSource Lese-/Schreibsplit), listForUser gebunden, Aktenstand kohaerent -state_head: 939c8121a182fb8ad93b3b7f5b9fdcecc17a3ffd +state_head: ea6aa995b27de1dfa9b557c06df9fa3bbcdd8ea3 progress: total_phases: 17 completed_phases: 15 @@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17) Phase: 17 (eigene-ausschreibungs-quellen-je-nutzer) — VERIFIED / passed Plan: 3 of 3 Status: Phase abgeschlossen und im Browser gegengeprueft — bereit fuer /gsd-ship -Last activity: 2026-09-14 - Etappe 3c Systemkontext abgeschlossen (260914-eym, verifiziert 9/9) und WINDOWS #29 (260914-ebg, 6/6); offen: Etappe 3a (Produktfrage Subdomain vs. Login-Wahl), Etappe 4 nur nach Rueckfrage, Ship von Phase 17 +Last activity: 2026-09-14 - Zwei Auslieferungskanaele + Versionsstempel (260914-ku1, verifiziert 8/8); davor Etappe 3c (260914-eym) und WINDOWS #29 (260914-ebg). Naechster Schritt: Fehler-melden-Knopf, danach Zweig live + Tag v1.0.0 Progress: [██████████] 100% @@ -402,6 +402,7 @@ None yet. | 260910-jab | Die drei zu kurz greifenden Datenbankregeln geschlossen — T-JTS-02, T-JTS-03, WINDOWS #19 (bewusste Reihenfolge-Abweichung, vorgezogen auf Nutzerwunsch, statt wie geplant nach Etappe 2). Neue, handgeschriebene, lokal angewandte Migration `20260910120000_rls_widen_membership_grant_and_platform_read`: `GroupMembership` prueft jetzt beide Seiten der Beziehung (Gruppe UND Benutzer), `ModuleGrant` prueft zusaetzlich beide moeglichen Ziele mit Leer-Zulassung (D-04), `TenderRssFeedSource` bekommt vier nach Befehl getrennte Regeln (Lesen schliesst plattformweite Zeilen ein, Schreiben verlangt weiterhin einen Mandanten — die Trennung ist noetig, weil ein einzelner USING-Ausdruck sonst auch UPDATE/DELETE mitregelt). `SearchProvider` bewusst NICHT angefasst: die WINDOWS-#19-Praemisse ist fuer dieses Modell widerlegt (kein Codeweg erzeugt eine mandantenlose Zeile). Drei loch-behauptende Pruefungen im Wegwerf-Werkzeug UMGEKEHRT statt geloescht (66→74 Pruefungen), mit Verweis auf die alten Pruefungsnamen und Befundkennungen im Meldetext. Genau EIN Anwendungspfad musste mitgebunden werden (`TenderRssFeedSourceService.listForUser`) — sonst haette die Reparatur ihn still von 'liefert nach dem Scharfschalten nichts' auf 'liefert nur die plattformweiten Zeilen, taeuscht Vollstaendigkeit vor' verschlechtert; Falsifizierungsnachweis gefuehrt (Bindung zurueckgenommen, genau ein Test rot, zurueckgesetzt). WINDOWS #19 geschlossen mit Beleg, WINDOWS #24 neu angelegt (Verwaltungsweg fuer plattformweite Zeilen unter der Anwendungsrolle fehlt weiterhin — verschwindet nicht mit #19). Aktenstand kohaerent: Klassifikation, Kritikschrift (neuer Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19" mit Signaltabelle beider Fehlerrichtungen je Regel), Betriebsanleitung, WINDOWS.md — fuenf ueberholte Bestandsstellen mit Nachtraegen versehen, alte Messprotokolle bleiben woertlich stehen. Selbst gemessen statt uebernommen: Baseline 833/56 Tests, 66/66 Live-Pruefungen; Endstand 839/56, 74/74; keine zweite Sitzungsvariable fuer den Benutzer gefunden (nur `app.current_tenant`). Rule-1-Fix: implizites `any` in `tenders.controller.ts` nach der Bindung behoben. `npx prisma` versuchte ungefragt Prisma 8 herunterzuladen — abgebrochen, lokale gepinnte 6.19.3 verwendet | 2026-09-10 | f4f3115,6b23735,03fb3bf | [260910-jab-mandantentrennung-die-drei-zu-kurz-greif](./quick/260910-jab-mandantentrennung-die-drei-zu-kurz-greif/) | | 260914-ebg | **WINDOWS #29 geschlossen — Zielrollen-Riegel in `UserController.update()`/`remove()`.** Ein ADMIN kann den SUPER_ADMIN seines Mandanten nicht mehr aendern (Kennwort, isActive, Rolle, Anmeldename, E-Mail) oder loeschen; Riegel nach der Mandantengrenze, vor der Rollenzuweisungs-Pruefung (Vorlage `AuthService.adminResetPassword`, T-FH9-04). Acht neue Spec-Tests (8 -> 16), Baseline 1020 -> 1028 Tests / 62 Dateien, Falsifizierung durch Rueckbau `Tests 2 failed | 14 passed (16)` (Test 9/13), unabhaengig vom Verifizierer wiederholt. Kopfkommentar `adminResetPassword` nachgezogen (T-FH9-05 nicht mehr offen). Ledger 16 offen / 1 zurueckgestellt / 19 geschlossen / 36 gesamt: #29 fixed, NEU #35 (Biome-Konfiguration im Bestand nicht lauffaehig, `pnpm lint` Leerlauf) und #36 (Admin-Frontend verschluckt 403 still). Verifiziert 6/6, gepusht. | 2026-09-14 | 759ea3b,63f9df0,70d007b | [260914-ebg-windows-29-schliessen-rechteausweitung-a](./quick/260914-ebg-windows-29-schliessen-rechteausweitung-a/) | | 260914-eym | **Etappe 3c — Systemkontext fuer die Hintergrunddienste.** Migration `20260914120000_rls_system_context_read`: `is_system_context()`, fuenf permissive `system_read_policy ... FOR SELECT` (DkvModuleConfig, LdapConfig, LdapFieldMapping, TenderMatch, TenderSavedSearch); `forSystem(prisma)` in Array-Form mit ausdruecklichem Zuruecksetzen von Mandant/Benutzer, `forTenant()` setzt `app.system_context` zurueck (kein Erben, gemessen). Sechs Faelle: DKV-Planer einmal-abfragen-viele-bedienen (Auftrag je Mandant, WINDOWS #21 fixed); Mail-Transport je Versand aus der SmtpConfig des Empfaenger-Mandanten mit unveraenderter Umgebungs-Rueckfallkette, Startpfad und Mailer-Fabrik entfallen (WINDOWS #30 fixed, SmtpConfig ohne Systemregel); ldap `getAllActiveConfigs()` und Boot-Nachverschluesselung lesen ueber Systemkontext, schreiben je Mandant gebunden; tender-digest Kandidaten und tender-matching Suchprofile ueber Systemkontext, Schleifen gebunden; admin-seed nur dokumentiert (Tenant ohne Regel). Detektor mit fuenfter Erkennungsform `forSystem(` und exakter Erlaubnisliste (falsifiziert: Fremddatei 1 rot, Zweitaufruf 2 rot). Werkzeug 203 -> 253 (`runSystemContextChecks`: ungebunden 0 / System beide Mandanten / Schreiben abgewiesen 42501 bzw. count 0 / kein Erben / pg_policies 34, 5x SELECT). Rueckbau (a) 5 rot mit gelungenem Insert, (b) 1 rot, (c1) 253 gruen + (c2) 5 rot, (d) 2/3 rot. Tests 1028 -> 1054 / 62 -> 64 Dateien, tsc 0, 29 Dateien gegen 5e0e408, Schalter AUS (Compose/.env/Schema/Lockfile unveraendert). Klassifikation 61/179/5, 72 Paare, sechs Zeilen `system-gebunden`; Kritikschrift (y1)-(y5); Auftrag 3c Erledigt. Ledger 15 offen / 1 zurueckgestellt / 21 geschlossen / 37 gesamt; NEU #37 (prozessweiter Single-Flight-Riegel `processInbox`). Verifiziert 9/9, gepusht. | 2026-09-14 | 3d64567,6e2a641,939c812 | [260914-eym-mandantentrennung-etappe-3c-systemkontex](./quick/260914-eym-mandantentrennung-etappe-3c-systemkontex/) | +| 260914-ku1 | **Zwei Auslieferungskanaele und Versionsstempel.** `main` = Beta (Etiketten `beta` + `latest`), Tag `vX.Y.Z` = Live (Etiketten `live` + `vX.Y.Z`), Zweig `live` ohne Tag nur geprueft — Entscheidung in `.gitea/scripts/publish-images.sh` (`--print-plan`), CI-Trigger `branches: [main, live]` + `tags: [v*]`, `fetch-depth: 0`. Versionsstempel `APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME` als Build-Args in beide Dockerfiles (web zur Bauzeit als `NEXT_PUBLIC_APP_*`, api als Laufzeit-ENV; Vorgabe `dev`). `GET /health/version` liefert name/version/channel/commit/buildTime, Startlog `Tessera API vX (channel) commit`. Web: `app-version.ts`, `AppVersionBadge` in `sidebar.tsx` (sidebar-footer.tsx ist seit ba02b25 toter Code). `docker-compose.prod.yml`: `image: ...:${IMAGE_TAG:-beta}`. Betriebshandbuch Kapitel 9 (Zwei Kanaele, Freigabe, Hotfix ohne Datenbankaenderung, neuer Live-Server), ci-cd-setup.md auf gemessenen Stand. Falsifiziert: Build mit `v9.9.9-test live` -> Stempel in dist und Web-Bundle, ohne Args `dev`. Echter CI-Lauf 297 gruen (5:18 min), Abbilder `beta`/`latest` tragen `ea6aa99 beta`. Tests API 1054 -> 1060 / 64 -> 65 Dateien, Web 233 -> 243 / 38 -> 40, tsc 0, 20 Dateien gegen 6c19451. Offen: Zweig `live` + Tag `v1.0.0` nach dem Fehler-melden-Knopf anlegen; Handgriffe fuer den User (IMAGE_TAG je Server) im SUMMARY. Verifiziert 8/8, gepusht. | 2026-09-14 | cdb571c,9731501,ea6aa99 | [260914-ku1-zwei-auslieferungskanaele-beta-auf-main-](./quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/) | ## Deferred Items @@ -443,8 +444,8 @@ sind. Kein Anlass, sie vorher erneut vorzulegen. ## Session Continuity -Last session: 2026-09-14T11:51:23.000Z +Last session: 2026-09-14T13:53:23.147Z Resumed: 2026-09-14 — Sitzung ueber /gsd-resume-work fortgesetzt; #29 und 3c als /gsd-quick --validate mit voller Kette durchgefuehrt. -Stopped at: **ETAPPE 3c ABGESCHLOSSEN (260914-eym, 9/9), WINDOWS #29 GESCHLOSSEN (260914-ebg, 6/6) — 2026-09-14.** Endstand 1054 Tests / 64 Dateien, Werkzeug 253/253, Ledger 15 offen / 21 geschlossen / 37 gesamt, alles gepusht, Arbeitsbaum sauber, DER SCHALTER IST AUS. **USER-ENTSCHEIDUNG 2026-09-14: Mandantenfaehigkeit RUHT — Etappe 3a und Etappe 4 werden NICHT weiterverfolgt, das Thema wird nicht mehr angesprochen** (siehe Deferred Items). Tessera laeuft als Ein-Firmen-System vollstaendig; Live-Gehen alpha am 2026-09-15 mit dem Stand auf main (Deploy macht der User; neue Migration 20260914120000_rls_system_context_read laeuft dabei mit, unter BYPASSRLS wirkungslos). Offen ohne Bezug zur Mandantenfaehigkeit: Ship von Phase 17 (blockiert durch windows_enforce bei open_count 15), Ledger #35 (Biome-Konfiguration im Bestand nicht lauffaehig), #36 (Admin-Frontend verschluckt 403 still). Naechster Einstieg: `/gsd-resume-work`, dann das, was der User nennt. +Stopped at: 2026-09-14: Quick 260914-ku1 abgeschlossen (3 Commits cdb571c/9731501/ea6aa99 gepusht, CI-Lauf 297 success, :beta-Abbilder mit ea6aa99 beta); offen: Fehler-melden-Knopf, danach Erstfreigabe v1.0.0 (Zweig live + Tag), Server-Handgriffe durch den User Resume file: None -Last activity: 2026-09-14 - Completed quick task 260914-eym: Etappe 3c Systemkontext fuer die Hintergrunddienste, WINDOWS #21/#30 geschlossen +Last activity: 2026-09-14 - Completed quick task 260914-ku1: Zwei Auslieferungskanaele (beta/live), Versionsstempel, Versionsanzeige, Betriebshandbuch Kapitel 9 diff --git a/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-SUMMARY.md b/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-SUMMARY.md new file mode 100644 index 0000000..63282d4 --- /dev/null +++ b/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-SUMMARY.md @@ -0,0 +1,361 @@ +--- +phase: quick-260914-ku1 +plan: 01 +subsystem: infra +tags: [ci, gitea-actions, docker, build-args, next-public-env, nestjs, health, versionsstempel, compose, handbuch] +status: complete + +requires: + - phase: quick-260914-ku1 planning (1cd4212) + provides: Plan mit Erlaubnisliste, gemessenen Bezugszahlen und Threat-Register +provides: + - "GET /health/version liefert { name, version, channel, commit, buildTime } aus APP_* (Vorgaben dev/dev), Startzeile `Tessera API () `" + - "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" +affects: [fehler-melden-knopf, erstfreigabe-v1.0.0, live-server-einrichtung, deploy] + +actuals: + tokens: 41545 + tasks: 3 + commits: 3 +plan_head_before: 1cd4212df08cb0910e6c5c02abf6926534951935 + +tech-stack: + 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)" + +key-files: + created: + - 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 + modified: + - 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 + +key-decisions: + - "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)" + +patterns-established: + - "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" + +requirements-completed: [QUICK-260914-KU1] + +coverage: + - id: D1 + description: "GET /health/version aus APP_* mit Vorgaben, Compose-Leerstring-Semantik, Startzeile, @Public gepinnt" + requirement: QUICK-260914-KU1 + verification: + - kind: unit + ref: "apps/api/src/health/health.controller.spec.ts (6 Tests)" + status: pass + - kind: integration + ref: "docker run tessera-ku1-api:args node -e formatAppVersionLine() -> Tessera API v9.9.9-test (live) abc1234" + status: pass + - id: D2 + description: "Web-Quelle app-version.ts (appVersion, loadApiVersion memoisiert/still) und AppVersionBadge in der Seitenleiste" + requirement: QUICK-260914-KU1 + verification: + - kind: unit + ref: "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" + status: pass + - kind: integration + ref: "grep -rl v9.9.9-test /app/apps/web/.next/static | wc -l -> 1 (Bauzeit-Einbettung)" + status: pass + - id: D3 + description: "CI-Trigger je Kanal, publish-images.sh, Build-Args in beiden Dockerfiles, IMAGE_TAG in Compose" + requirement: QUICK-260914-KU1 + verification: + - kind: automated_ui + ref: "js-yaml-Strukturpruefung, --print-plan in drei Lagen, docker compose config --images mit/ohne IMAGE_TAG" + status: pass + - kind: e2e + ref: "Gitea-Actions-Lauf 297 (success) und CI-gebaute :beta-Abbilder mit APP_VERSION=ea6aa99 APP_CHANNEL=beta" + status: pass + - id: D4 + description: "Betriebshandbuch Kapitel 9 und ci-cd-setup auf gemessenen Stand" + requirement: QUICK-260914-KU1 + verification: + - kind: other + ref: "grep-Gates (Kapitelueberschrift 1, IMAGE_TAG 11, Hotfix-Regel 1, veraltete Aussagen 0, Skriptname 4, Umlaute in ci-cd-setup 0)" + status: pass + +metrics: + duration: "22 min (13:28Z bis 13:50Z, davon ca. 7,5 min lokale Docker-Bauten und 5,3 min CI-Lauf)" + completed: "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 · ` 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 "" 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 · 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: + ```bash + 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): + ```yaml + 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: + ```bash + 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 ` · 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: + +```bash +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`. diff --git a/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-VERIFICATION.md b/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-VERIFICATION.md new file mode 100644 index 0000000..5a17ab8 --- /dev/null +++ b/.planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-VERIFICATION.md @@ -0,0 +1,198 @@ +--- +phase: quick-260914-ku1 +verified: 2026-09-14T13:59:19Z +status: passed +score: 8/8 must-haves verified +covered_files: + - ".gitea/scripts/publish-images.sh" + - ".gitea/workflows/ci.yml" + - ".planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-PLAN.md" + - ".planning/quick/260914-ku1-zwei-auslieferungskanaele-beta-auf-main-/260914-ku1-SUMMARY.md" + - "apps/api/Dockerfile" + - "apps/api/src/health/app-version.ts" + - "apps/api/src/health/health.controller.spec.ts" + - "apps/api/src/health/health.controller.ts" + - "apps/api/src/main.ts" + - "apps/web/Dockerfile" + - "apps/web/src/components/layout/app-version-badge.test.tsx" + - "apps/web/src/components/layout/app-version-badge.tsx" + - "apps/web/src/components/layout/sidebar.test.tsx" + - "apps/web/src/components/layout/sidebar.tsx" + - "apps/web/src/lib/app-version.test.ts" + - "apps/web/src/lib/app-version.ts" + - "apps/web/src/messages/de.json" + - "apps/web/src/messages/en.json" + - "docker-compose.prod.yml" + - "docs/anleitung-betrieb.md" + - "docs/ci-cd-setup.md" + - "packages/shared/src/index.ts" +covered_digest: "v1:sha256:3cc012949ab9484c9c9821c7dea15392439adc9224bd2f9e3025991c0eba6062" +behavior_unverified: 0 +overrides_applied: 0 +--- + +# Quick-Task 260914-ku1: Zwei Auslieferungskanaele (Beta/Live) — Verifikationsbericht + +**Ziel:** Zwei Auslieferungskanaele (main -> beta+latest; Tag v* -> live+vX.Y.Z; Zweig live ohne Tag nur geprueft), Versionsstempel per Build-Args in beide Container-Abbilder, `GET /health/version`, Versionsabzeichen in der Seitenleiste, `IMAGE_TAG` in docker-compose.prod.yml, Publish-Skript mit `--print-plan`, Betriebshandbuch Kapitel „Zwei Kanaele" inkl. Hotfix-Rezept und Live-Server-Einrichtung, ci-cd-setup.md aktualisiert, gepusht, echter CI-Lauf gruen. + +**Verifiziert:** 2026-09-14T13:59:19Z +**Status:** passed +**Re-Verifikation:** Nein — Erstverifikation + +Alle Pruefungen wurden selbst erneut ausgefuehrt (nicht aus dem SUMMARY uebernommen). Wo die eigene Messung von der SUMMARY-Behauptung abweicht, ist das unten vermerkt — es gab keine Abweichung. + +## Pruefung 1 — Commits, Diff-Umfang, unangetastete Dateien + +| Befehl | Erwartet | Gemessen | +|---|---|---| +| `git log --oneline 1cd4212..HEAD` | 3 Commits | `ea6aa99`, `9731501`, `cdb571c` — 3 Commits | +| `git diff --stat 6c19451 -- . ':!.planning'` (letzte Zeile) | `20 files changed` | `20 files changed, 851 insertions(+), 49 deletions(-)` | +| `git diff --name-only 6c19451 -- '.env*' apps/api/prisma/schema.prisma apps/api/prisma/migrations package.json pnpm-lock.yaml biome.json apps/web/src/components/layout/sidebar-footer.tsx` | leer | leer (keine Ausgabe) | +| `git status --porcelain` (vor jeder Aenderung durch die Verifikation) | nur die zwei erwarteten offenen Dateien | `M .planning/STATE.md`, `?? .../260914-ku1-SUMMARY.md` — beides die dem Orchestrator gehoerenden, unberuehrt gelassenen Dateien | +| `git fetch -q && git status -sb \| head -1` | kein `[ahead` | `## main...origin/main` | + +Status: ✓ VERIFIED + +## Pruefung 2 — Testsuiten und Typpruefung + +| Befehl | Erwartet | Gemessen | +|---|---|---| +| `cd apps/api && npx vitest run` | 65 Dateien / 1060 Tests | `Test Files 65 passed (65)` / `Tests 1060 passed (1060)` | +| `cd apps/web && npx vitest run` | 40 Dateien / 243 Tests | `Test Files 40 passed (40)` / `Tests 243 passed (243)` | +| `pnpm -C packages/shared exec tsc --noEmit` | Exit 0 | Exit 0 | +| `pnpm -C apps/api exec tsc --noEmit` | Exit 0 | Exit 0 | +| `pnpm -C apps/web exec tsc --noEmit` | Exit 0 | Exit 0 | + +Status: ✓ VERIFIED + +## Pruefung 3 — Versionsstempel im Code (API und Web) + +Quelldateien gelesen (nicht nur gegrept): + +- `apps/api/src/health/health.controller.ts`: `getVersion()` delegiert an `getAppVersion()` aus `./app-version`, `@Public()` und `@Get('version')` gesetzt, kein Zugriff mehr auf `npm_package_version`. Kommentar begruendet T-KU1-03. +- `apps/api/src/health/app-version.ts`: `getAppVersion()` liest `process.env.APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME` mit `||`-Vorgaben `dev`/`dev`/``/``, `name: 'tessera'`. `formatAppVersionLine()` baut `Tessera API () `, ohne Commit ohne Leerzeichen am Ende. +- `apps/api/src/health/health.controller.spec.ts`: 6 Tests wie im Plan beschrieben (`check()`, Vorgaben, Durchreichen, Compose-Leerstring-Semantik, `formatAppVersionLine`, `@Public()`-Metadatenpruefung per `Reflect.getMetadata`). Alle 6 gruen (siehe Pruefung 2). +- `apps/api/src/main.ts`: `console.log(formatAppVersionLine())` direkt nach der bestehenden Port-Zeile (Zeile 35, nach Zeile 34). +- `apps/web/src/lib/app-version.ts`: `appVersion` liest `process.env.NEXT_PUBLIC_APP_VERSION/_CHANNEL/_COMMIT` je mit vollem Literalnamen (keine Destrukturierung, kein `process.env[name]`); `normalizeChannel` faellt bei unbekanntem Kanal auf `'dev'` zurueck; `loadApiVersion()` memoisiert ein Modul-Promise gegen `${API_URL}/health/version` mit `credentials: 'include'`, still bei Fehler (`.catch(() => null)`). +- `apps/web/src/components/layout/app-version-badge.tsx`: `data-testid="app-version"`, Text `${appVersion.version} · ${t('channel.'+channel)}`, `title` aus `Commit ` und `API ()`, `undefined` wenn beides fehlt. +- `apps/web/src/components/layout/sidebar.tsx`: Badge-Block liegt NACH dem Einklapp-Block (der `hidden md:block` traegt) und OHNE dieses Attribut — dadurch auch in der mobilen Schublade sichtbar; `!isCollapsed` blendet ihn eingeklappt aus (Zeilen ~201-206). +- `apps/web/src/messages/de.json` / `en.json`: `sidebar.channel = { beta: "Beta", live: "Live", dev: "Entwicklung"/"Development" }` — in beiden Dateien vorhanden, per Node geprueft. +- `sidebar.test.tsx`: Modul-Mock fuer `AppVersionBadge` und ein sechster Test `renders the version badge below the navigation`, der `screen.getByTestId('app-version-badge')` prueft. + +Status: ✓ VERIFIED + +## Pruefung 4 — Dockerfile-Falsifizierung (unabhaengig wiederholt) + +Beide Dockerfiles gelesen: globales `ARG APP_VERSION=dev` (und die drei weiteren) vor dem ersten `FROM`; im Web-`builder` `ARG`-Wiederholung + `ENV NEXT_PUBLIC_APP_*` unmittelbar VOR `RUN pnpm --filter=@tessera/web build`; im `runner` beider Images `ARG`-Wiederholung + `ENV APP_VERSION=...` nach `ENV NODE_ENV=production`. + +Eigener Bau (nicht aus dem SUMMARY uebernommen): + +``` +docker build -f apps/api/Dockerfile --build-arg APP_VERSION=v7.7.7-verify --build-arg APP_CHANNEL=live -t tessera-api-verify:tmp . +docker run --rm --entrypoint node tessera-api-verify:tmp -e 'console.log(process.env.APP_VERSION, process.env.APP_CHANNEL)' +-> v7.7.7-verify live +``` + +Erwartung erfuellt. Danach `docker rmi tessera-api-verify:tmp` ausgefuehrt — kein Rueckstand (`docker images | grep -c tessera-api-verify` -> 0). + +Status: ✓ VERIFIED + +## Pruefung 5 — CI-Workflow-Struktur und Publish-Skript + +`.gitea/workflows/ci.yml` per js-yaml geparst: + +``` +branches=["main","live"] tags=["v*"] jobs=quality,test,publish fetchDepth=0 script=true needs=test +``` + +`.gitea/scripts/publish-images.sh --print-plan` in drei Lagen (eigene Ausfuehrung): + +| GITHUB_REF | Ergebnis | +|---|---| +| `refs/heads/main` | Kanal `beta`, Push-Zeilen fuer `web:beta`, `web:latest`, `api:beta`, `api:latest` (main-Fall enthaelt BEIDE, `beta` und `latest`) | +| `refs/heads/live` | „Kein Veroeffentlichungs-Anlass ... nichts zu tun." — keine `push`-Zeile | +| `refs/tags/v1.0.0` | Kanal `live`, Push-Zeilen fuer `web:live`, `web:v1.0.0`, `api:live`, `api:v1.0.0` | + +Status: ✓ VERIFIED + +## Pruefung 6 — CI-gebaute Abbilder und echter Gitea-Lauf + +`docker images | grep tessera-ctl` zeigt `localhost:3002/schalli/tessera-ctl/{api,web}:{beta,latest}` (zusaetzlich `git.vicolab.de/...` als Fetch-Alias und lokale `tessera-ctl-{api,web}:latest` aus fruehreren lokalen Bauten — nicht Teil dieser Pruefung). + +``` +docker run --rm --entrypoint node localhost:3002/schalli/tessera-ctl/api:beta -e 'console.log(process.env.APP_VERSION, process.env.APP_CHANNEL, process.env.APP_COMMIT)' +-> ea6aa99 beta ea6aa99 + +docker run --rm --entrypoint sh localhost:3002/schalli/tessera-ctl/web:beta -c 'grep -rl "ea6aa99" apps/web/.next/static | wc -l' +-> 1 +``` + +`ea6aa99` ist der zuletzt gepushte Commit (siehe Pruefung 1) — Uebereinstimmung. + +Gitea-API (`GET /api/v1/repos/schalli/tessera-ctl/actions/runs?limit=5`, Token aus `git remote get-url --push origin`, nicht ausgegeben): + +``` +297 ea6aa995b27de1dfa9b557c06df9fa3bbcdd8ea3 completed success push +296 6c19451be9a25feb227af075076a839a968c5366 completed success push +... +``` + +Lauf 297 entspricht `head_sha = ea6aa99...`, `status: completed`, `conclusion: success` — deckt sich mit der SUMMARY-Angabe. + +Status: ✓ VERIFIED + +## Pruefung 7 — docker-compose.prod.yml / IMAGE_TAG + +``` +docker compose -f docker-compose.prod.yml config --images +-> git.vicolab.de/schalli/tessera-ctl/web:beta, .../api:beta, postgres:16-alpine + +IMAGE_TAG=live docker compose -f docker-compose.prod.yml config --images +-> .../api:live, postgres:16-alpine, .../web:live +``` + +Ohne Variable Vorgabe `beta`, mit `IMAGE_TAG=live` `live` fuer beide Images. Kein `:latest` mehr in der Datei (per grep unabhaengig bestaetigt: 0 Treffer). + +Status: ✓ VERIFIED + +## Pruefung 8 — Betriebshandbuch und ci-cd-setup.md + +`docs/anleitung-betrieb.md`, Abschnitt `## 9. Zwei Kanäle: Live und Beta` (Zeile 355) gelesen (nicht nur gegreppt): erklaert den Kanalbegriff, die eine `.env`-Zeile je Server (`IMAGE_TAG=beta`/`IMAGE_TAG=live`), die Server-Compose-Anpassung, `pull` + `up -d --force-recreate`, das Freigabe-Rezept, den vollstaendigen Hotfix-Ablauf mit der fett gesetzten Regel „Keine Datenbankänderung als Hotfix" samt Alltagssprache-Begruendung (Migrations-Zeitstempel-Reihenfolge), die drei Erkennungswege (Oberflaeche/`curl`/Log) und die Einrichtung des neuen Live-Servers (eigene Secrets, eigene leere Datenbank, `APP_URL`, Reihenfolge Erstfreigabe vor erstem Pull). Echte Umlaute durchgaengig (`Zwei Kanäle`, `änderungen`, `möglicherweise`, `größeren` etc.) — Ton der Datei gehalten, keine ASCII-Umschrift in diesem Kapitel. + +`docs/ci-cd-setup.md`: Abschnitt „Gitea Secrets" beschreibt `REGISTRY_TOKEN` und den Login (kein „keine Secrets" mehr); Abschnitt „Pipeline-Ueberblick" beschreibt Trigger (main/live/Tags v*), Etiketten-Tabelle, Build-Args-Tabelle, laengere Web-Bauzeit und markiert D-13 als ueberholt. `grep -c "Kein Registry-Push\|build-deploy"` -> 0. + +| Grep | Erwartet | Gemessen | +|---|---|---| +| `^## 9\. Zwei Kanäle: Live und Beta` in anleitung-betrieb.md | 1 | 1 | +| `IMAGE_TAG` in anleitung-betrieb.md | >= 6 | 11 | +| `Keine Datenbankänderung als Hotfix` | 1 | 1 | +| `Kein Registry-Push\|build-deploy` in ci-cd-setup.md | 0 | 0 | +| `[äöüÄÖÜß]` in ci-cd-setup.md (ASCII-Konvention) | 0 | 0 | + +Status: ✓ VERIFIED + +## Anti-Pattern-Scan + +Alle 13 durch die Fingerprint-Liste erfassten Code-/Config-Dateien auf `TBD`, `FIXME`, `XXX`, `TODO`, `HACK`, `PLACEHOLDER`, „not yet implemented" u. ae. geprueft — keine Treffer in einer der geaenderten Dateien. + +## Requirements-Abdeckung + +`QUICK-260914-KU1` ist eine Quick-Task-Anforderung ohne eigenen Eintrag in `.planning/REQUIREMENTS.md` (projektueblich fuer `/gsd-quick`-Auftraege, kein Roadmap-Phasenbezug) — kein verwaistes Requirement, da REQUIREMENTS.md keinen Phasen-Bezug fuer diesen Quick-Task erwartet. + +## Beobachtete Nebenpunkte (keine Gaps) + +- Zusaetzliche lokale Docker-Images (`git.vicolab.de/...:latest`, `tessera-ctl-api:latest`, `tessera-ctl-web:latest`) liegen auf dem Host aus fruehreren Bauten/Pulls — nicht Teil dieses Plans und nicht durch ihn verursacht. +- `sidebar-footer.tsx` bleibt wie geplant unangetastet (toter Code, im SUMMARY als Nebenbefund vermerkt). +- `.env.prod.example` bewusst ohne `IMAGE_TAG`-Zeile (Regel: keine `.env*`-Aenderungen) — im Handbuch als offener Punkt vermerkt. + +## Angenommene Risiken + +- **T-KU1-03** (`GET /health/version` bleibt `@Public()`, gibt Version/Kanal/Commit/Bauzeit ohne Anmeldung preis): als "accept" im Threat-Register des Plans geflaggt, durch Spec-Test 6 gepinnt. Bei externem Verkauf von Tessera muesste das revidiert werden (im Plan bereits als Folgeaenderung genannt). Die Verifikation bestaetigt nur, dass die Entscheidung wie dokumentiert umgesetzt und getestet ist — keine eigene Bewertung des Risikos selbst. +- **T-KU1-04** (Tag-Push `v*` als alleiniger Freigabe-Hebel, kein Tag-/Branch-Schutz in Gitea): als "accept" geflaggt, weil aktuell nur ein Konto Schreibrecht hat. Diese Verifikation hat KEINE Gitea-Repository-Einstellungen aendern koennen/muessen (ausserhalb der Erlaubnisliste) und bestaetigt nur, dass die Empfehlung im Handbuch/ci-cd-setup steht. +- **`live`-Zweig und Tag `v1.0.0` sind bewusst nicht angelegt** — laut Plan Folgearbeit nach dem noch ausstehenden „Fehler-melden-Knopf"-Quick-Task. Das bedeutet: der Live-Kanal ist bislang nur durch die drei `--print-plan`-Simulationen und den lokalen Docker-Falsifizierungslauf bewiesen, NICHT durch einen echten CI-Lauf mit `refs/tags/v*` (der echte CI-Lauf in Pruefung 6 deckt nur den Beta-Pfad ab). Das ist im Rahmen des Plans ausdruecklich so vorgesehen (Output-Abschnitt: "Zweig live und Tag v1.0.0 werden NICHT in diesem Plan angelegt") und daher kein Gap, aber ein offener Punkt fuer die naechste Freigabe. +- Zusaetzliche, vom Runner gebaute lokale Images auf dem Host (`git.vicolab.de/...:latest` etc.) wurden nicht aufgeraeumt — sie stammen nicht aus dieser Verifikation und wurden nicht entfernt, um den Host-Zustand nicht ueber das Mandat hinaus zu veraendern. + +--- + +_Verifiziert: 2026-09-14T13:59:19Z_ +_Verifier: Claude (gsd-verifier)_