Initial commit

This commit is contained in:
marcus.hinz
2026-07-09 09:21:42 +02:00
commit c8f977d617
39 changed files with 934 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
{
"autoMemoryDirectory": "~/claude-projects/wuerth-plato/memory",
"hooks": {
"SessionStart": []
},
"permissions": {
"additionalDirectories": [
"~/projects/fahrzeugeinrichtung-plato-backend",
"~/projects/fahrzeugeinrichtung-plato-gitops"
],
"allow": [
"Edit(~/projects/fahrzeugeinrichtung-plato-backend/**)",
"Edit(~/projects/fahrzeugeinrichtung-plato-gitops/**)",
"Edit(~/claude-projects/wuerth-plato/**)"
]
}
}
+1
View File
@@ -0,0 +1 @@
.ai-control-running
+7
View File
@@ -0,0 +1,7 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
1. Dieses Modul ist Teil eines MonoRepos, das verschiedene — überwiegend Development- — Projekte bündelt.
2. Die eigentlichen Source- oder Ressource-Repos sind in der `settings.json` hinterlegt.
3. SAP-Zugriff (Basic Auth, Endpoint, Env→Secret/Overlay-Mapping): siehe `memory/sap-config.md`.
+6
View File
@@ -0,0 +1,6 @@
{
"pool": "private",
"terminal": {
"icon": "wuerth-plato.png"
}
}
+31
View File
@@ -0,0 +1,31 @@
- [User: Marcus / adesso-Berater bei Würth](user_role.md) — Tempo-getriebener Tech-Lead, will Optionen + Tradeoffs, kommuniziert auf Deutsch
- [Feedback: Priorisierung entlang Test-Linie](feedback_priorisierung.md) — "was ermöglicht Kollegen früh zu testen" schlägt techn. Priorität
- [Feedback: Push macht der User](feedback_push_macht_user.md) — committen ok, git push macht Marcus selbst
- [Feedback: Keine nativen Alerts](feedback_no_native_alerts.md) — kein window.alert/confirm; ConfirmDialog bzw. errorList nutzen
- [Feedback: Klare Benennung](feedback_klare_benennung.md) — Komponenten konkret nennen (kein „BE"); einfache, kurze Sätze
- [Projekt: Login-Loop Ursache+Fix](project_login_loop_rootcause.md) — SW fing OIDC-Callback ab; injectManifest-SW + code-Denylist + cookie secure:'auto'
- [Feedback: Keine IP-Configs bei Netzfragen](feedback_no_ip_configs.md) — VPN-DNS + Proxy statt IP-Listen/Subnetz-Routen
- [Feedback: Dedizierte Skripte statt Ad-hoc](feedback_dedizierte_skripte.md) — geprüfte Skripte per allow, keine improvisierten Mutationen; Konzept vertagt
- [Feedback: Ort zuerst bei "wo geht das?"](feedback_ort_zuerst.md) — konkrete UI-Stelle in einem Satz, keine Menü-Tour, keine Vorbedingungen vorweg
- [Feedback: Self-Service only](feedback_self_service_only.md) — keine Vorschläge, die Tickets beim Kunden erfordern
- [Feedback: Keine Zwischenstufen mit Dummy-Werten](feedback_keine_zwischenstufen.md) — direkt mit Real-Wert testen, Crash+Log ist die Diagnose
- [Feedback: Kopieren statt Teilen bei Wegwerfcode](feedback_copy_not_share.md) — Migration kopiert Logik (kein Sharing), läuft REIN BE; Offline-first nie anfassen
- [Projekt: PingFederate / WITGLOBAL IdP](project_idp_witglobal.md) — DEV-Setup verifiziert, Token-Eigenschaften, Test-Rig in scripts/oidc-test.mjs
- [Projekt: Hartverdrahtetes Demo-JWT-Secret](project_dummy_jwt_secret.md) — Fundstellen + Plan zum Ersatz durch echte IdP-Anbindung
- [Projekt: Cleanup-Timeline Mai 2026](project_cleanup_timeline.md) — Auth-BFF erledigt; nächste Themen User-DB + Postgres-Umstieg
- [Projekt: Lokale Start-Befehle](project_run_commands.md) — BE/FE-Start nach BFF-Umbau (mit OIDC_CLIENT_SECRET)
- [Projekt: Sync lokal testen](project_sync_testing.md) — Integrationssuite sync-loop (9 Szenarien, seit 2026-06-10) + Unit-Specs + sync-poke.mjs
- [Projekt: SAP-Mock-Fixtures](project_sap_mock_fixtures.md) — FERTIG 2026-06-02: 20er-Master + Fixtures + E2E grün, symmetrische Mail-Rettung, Branch test/migration-sap-fixtures
- [Projekt: Nächste Schritte](project_naechste_schritte.md) — morgen: Report (Quick Win), dann Sync-Tests+Refactoring (absolute Prio)
- [Projekt: PWA-SW Auth-Bypass](project_pwa_sw_auth_bypass.md) — SW muss /auth+/oauth+/commands durchlassen, sonst Login-Loop (Fix ab6933c)
- [Projekt: Montag offen](project_montag_offen.md) — origin als SAP-Änderungsindikator klären + heutige Auth-Fixes pushen/deployen + UI-Redesign (Design System Montag)
- [Projekt: FE-Design-System](project_fe_design_system.md) — Token-Layer (tokens.css) + Lucide-Icons + Würth-Rot; Branch feature/fe-ui-refresh
- [Projekt: Test-Infra & Coverage](project_test_infra.md) — 8 Projekte/232+ Tests; wie man Test-Targets nachrüstet (Lib vs. PWA-App)
- [Projekt: Logging-Lösung](project_logging.md) — pino-BE (JSON/stdout) + /client-logs + offline-fähiger FE-Log-Sync; Board (Phase 3) offen
- [Projekt: Tauri /health grau](project_tauri_health_grau.md) — geparkt; cross-origin via platoasset://-Scheme, Kandidaten CORS(WebKitGTK) vs Proxy; DevTools entscheidet
- [Projekt: Loader-Signing-Key gelöscht](project_loader_signing_key.md) — am 2026-06-08 entfernt; beim Wiederaufgreifen Key + PUBLIC_KEY_HEX (loader.rs:24) neu
- [Projekt: Kontakt/Adress-Spec](project_kontakt_adress_spec.md) — geplant: SAP-Schutz server-seitig, Validierung-bei-Änderung nur PlaTo, M:N-Removal; noch nicht umgesetzt
- [Projekt: Bestätigen-Bug (FSA-Loop)](project_bestaetigen_bug.md) — Edge/Windows beim Kollegen; morgen Policy + Konsolen-reason prüfen
- [Projekt: Prod-Ist-Stand](project_prod_ist_stand.md) — Prod (ocp-01) LIVE: backend 0.38.4 Running, Secrets inkl. SAP gemountet; nur PR→main mergen offen; Quota 3CPU knapp
- [SAP-Config](sap-config.md) — echter Zugriff via Basic Auth (sy08804394), Key-Predicate-URL über Proxy; Env→SealedSecret/Overlay-Mapping im ops-Repo; Adapter-Patch offen
- [Projekt: Optimistische Sperre](project_optimistic_locking.md) — VERSION_CONFLICT (409) in persistCustomer gegen Last-Write-Wins; greift nur bei version!=null (Re-Send bleibt idempotent); lastUpdateTs ist server-autoritativ
+18
View File
@@ -0,0 +1,18 @@
---
name: feedback-copy-not-share-wegwerf
description: Bei Wegwerfcode (z.B. Migration) bewusst KOPIEREN statt geteilte Logik mit dauerhaftem Code; Flows in EINER Tier halten (Migration läuft REIN BE).
metadata:
node_type: memory
type: feedback
originSessionId: da22c6d7-9f9f-4834-b8f6-bdd06d220c38
---
Zwei zusammenhängende Regeln, beide am 2026-05-28 hart eingefordert:
1. **Wegwerfcode kopiert benötigte Logik, statt sie zu teilen.** Wenn ein Teil temporär/Wegwerf ist (z.B. die SQLite/Excel-**Migration**) und einen Teil aus dem dauerhaften Code braucht (z.B. den SAP-Adapter), dann **kopieren** — keine gemeinsame Funktion/Abstraktion zwischen Wegwerf- und Bleibe-Code.
2. **Flows nicht über Tiers springen lassen.** Die Migration läuft **komplett im BE**: parsen → SAP-Abfrage → anreichern → speichern in EINEM BE-Durchlauf. Daten im Flow NICHT BE→FE→BE schaufeln.
**Why:** Beim SAP-Einbau in die Migration. Marcus wörtlich: „bitte KOPIEREN — KEINE GEMEINSAME LOGIK (das eine ist wegwerfCode, das andere brauchen wir)". Und massiver Ärger, als ich vorschlug, im Migrationsflow BE→FE→BE zu round-trippen: „Dein Schritt 1 ist ein ABSOLUTES NO GO". Davor ebenfalls Ärger, weil ich eine geteilte Offline-first-Methode (`createOrUpdate`) angefasst hatte, obwohl die Änderung NUR den Migrate-Schritt betreffen durfte.
**How to apply:** Bei Migrations-/Wegwerf-Aufgaben Logik **duplizieren** statt teilen. Den Offline-first-Pfad (`persistentStorage.createOrUpdate/createOrUpdateList`, Outbox, Reconciler, BE `CommandService`/`CustomerCoreService.createOrUpdate(List)`) **nie** für Migrationszwecke anfassen — dafür gibt es separate Migrations-Pfade (`batchImport`, `replaceLocal`). Flows in einer Schicht halten. Und: ein Fall nach dem anderen, kurze lesbare Antworten — er sagte „NEIN das ist mir zu unstrukturiert" und „ich kann's nicht lesen". Siehe [[feedback-klein-und-pragmatisch]].
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback_dedizierte_skripte
description: "Marcus will dedizierte, geprüfte Skripte statt Ad-hoc-Mutationen — freilaufend per allow-Liste; Konzept noch offen"
metadata:
node_type: memory
type: feedback
originSessionId: fc0d1c3f-3797-4d5c-9282-bf72eb45dd51
---
Marcus möchte, dass ausführende Aktionen über einen Satz **benannter, vorab geprüfter Skripte** laufen, die ich dediziert aufrufe — kein Improvisieren von Mutationen (`oc patch`, `git rm`, kubeseal etc.) im Moment. Die Skripte kommen in `settings.json` als `allow` → ich laufe damit frei, ohne ständiges „darf ich?".
**Why:** Heute (2026-06-17) gab es mehrere Unfälle/Fehlgriffe (ungefragtes Memory-Lesen, falsches settings.local statt settings, fast Klartext-PW committet). Geprüfte Skripte begrenzen die Angriffsfläche und machen Aktionen vorhersehbar.
**How to apply:** Bis das Skript-Set steht, weiter eng an Anweisungen, Mutationen ankündigen ([[feedback_plan_first]]). Konzept-Diskussion ist VERTAGT („in Ruhe"). Offene Punkte: (1) Skripte im Hub `wuerth-plato` vs. je Repo; (2) nur Mutationen via Skript oder alles; (3) erste Skripte — Kandidaten `sap-status`, `deploy-watch`, `seal-secret`. Siehe auch die offene allow-Listen-Diskussion und [[feedback_evidence_first]].
+20
View File
@@ -0,0 +1,20 @@
---
name: feedback-keine-zwischenstufen
description: "Keine Verifikations-Zwischenschritte mit synthetischen Test-Werten — direkt mit echtem Wert testen, Crash + Log ist die Diagnose."
metadata:
node_type: memory
type: feedback
originSessionId: 079df90a-81b0-43bc-aaab-fb5fce2fa508
---
Bei jedem Plan: **keine künstlichen Zwischenstufen** zum "verifizieren ob das Wiring greift", bevor wir den echten Wert/das echte Setup ausprobieren. Direkt mit Real-Wert, und wenn's crasht → Pod-Log lesen → diagnostizieren. Das spart einen kompletten Deploy-Zyklus.
**Why:** Mai 2026 — Sealed-Secrets-Cleanup. Marcus' eigenes Prinzip ("geht, geht nicht dann ist das so") habe ich ignoriert und einen Zweistufen-Test mit Dummy-`SESSION_SECRET=sealed-secret-works-2026-05-27` eingebaut. Nach dem Test war der Pod live, aber mit Dummy-Wert — er musste **noch einen** Re-Seal- und Push-Zyklus durchlaufen für den echten Wert. Marcus zurecht wütend: "VÖLLIG UNNÖTIGER EWIGER UMWEG". Ein Deploy-Zyklus = mehrere Minuten Warten + neuer Commit + neuer Push = teuer.
**How to apply:**
- Echten Wert/Real-Setup von Anfang an einsetzen, nicht als Step 2.
- Wenn ein Diagnose-Hilfsmittel wirklich nötig ist: in den **bestehenden** Plan einbauen, nicht als eigenen Vorlauf-Zyklus.
- Im Fehlerfall: Log/Crash diagnostizieren, nicht "wir haben ja den Dummy-Wert noch als Fallback".
- Trifft besonders bei lange dauernden Deploy-Loops zu (GitOps-Repo → Controller → Cluster → Rolling-Restart = 15 min pro Iteration).
Verträgt sich mit [[feedback-klein-und-pragmatisch]] (minimaler Patch) und [[feedback-plan-first]] (Plan kommunizieren — aber dann ohne Vor-Verifikations-Stufen).
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback_klare_benennung
description: Komponenten konkret benennen statt mehrdeutiger Abkürzungen; einfach formulieren
metadata:
node_type: memory
type: feedback
originSessionId: 8e135c09-283f-459e-870a-a4f4bb366969
---
Keine mehrdeutigen Abkürzungen wie „BE" verwenden — im Projekt gibt es mehrere Backend-/Deployment-Komponenten (NestJS-Pod `plato-customer-be`, gitops-Repo, Postgres-Pod), da ist „BE" wertlos. Komponente immer konkret beim Namen nennen.
**Warum:** Marcus verliert bei verschachtelten Sätzen + Abkürzungen den Faden und wird zu Recht ungehalten. „BE ist nicht hilfreich bei mehr als einer BE-Komponente."
**How to apply:** Konkrete Namen (`plato-customer-be`-Pod, OpenShift-Route, F5-LB, gitops-Repo) statt Kürzeln. Kurze, gerade Sätze; ein Gedanke pro Satz; keine eingeschobenen Nebenketten. Erklärung nur so lang wie die Frage verlangt. Verwandt: [[feedback_ort_zuerst]].
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback-no-ip-configs
description: Bei VPN-/Netzwerk-/Erreichbarkeitsfragen niemals IP-basierte Lösungen vorschlagen — DNS-via-VPN und/oder Proxy
metadata:
node_type: memory
type: feedback
originSessionId: 4657cfc7-6fae-499b-a8f3-9118720952d7
---
Bei allem rund um Erreichbarkeit von Würth-Hosts: **keine IP-Listen, keine Subnetz-Routen, keine `AllowedIPs = x.x.x.x/y`**. Lösung läuft ausschließlich über (a) DNS-Push vom VPN-Gateway und/oder (b) HTTP(S)-Proxy.
**Why:** Würth-Infrastruktur (besonders die WAF vor witglobal) versteckt wechselnde IPs hinter stabilen Hostnamen. IP-Listen werden sofort brüchig und sind Pflege-Hölle. Würth-Standard ist explizit: VPN-DNS + Proxy — Marcus hat das mit Nachdruck klargestellt ("UND KEINE IPs").
**How to apply:** Bei Reachability-Themen Full-Tunnel + Würth-DNS als Default vorschlagen, Proxy für ausgehenden App-Traffic. Code-seitig `HTTPS_PROXY` / `NO_PROXY` ENV ehren; für `jwks-rsa` ggf. `requestAgent` mit `HttpsProxyAgent` setzen. Nie zu `ip route get`, Subnetz-Allowlists oder Split-Tunneling-per-IP greifen, auch nicht "nur zum Debuggen". Siehe auch [[project-idp-witglobal]].
+18
View File
@@ -0,0 +1,18 @@
---
name: feedback_no_native_alerts
description: Niemals window.alert/confirm Dialog-Komponente bzw. errorList nutzen
metadata:
node_type: memory
type: feedback
originSessionId: bcf0637c-def5-4024-b8c6-e8be02a43c4f
---
**Grundsätzlich kein `window.alert()` / `window.confirm()`** in der App. Wirkt billig und lieblos, passt nicht zum Design-System.
Stattdessen:
- **Bestätigungen/Warnungen** (z. B. „Bearbeitung beenden, Daten gehen verloren") → `ConfirmDialog`-Komponente aus `@plato/ui-common` (Token-basiert, Teleport, v-model/confirm/cancel, `danger`-Flag). Texte über i18n.
- **Fehlermeldungen** → `errorList.push(msg, { code, severity })` → ErrorOverlay.
**Why:** Konsistente, wertige UI; native Browser-Dialoge brechen den Look.
**How to apply:** Beim Schreiben neuer UI nie zu alert/confirm greifen. Siehe [[project_fe_design_system]].
+19
View File
@@ -0,0 +1,19 @@
---
name: feedback_ort_zuerst
description: "Bei \"wo mache ich X?\"-Fragen sofort die konkrete UI-Stelle nennen, nicht durch Menüs jagen"
metadata:
node_type: memory
type: feedback
originSessionId: 03691a44-b7eb-4d9d-9e7c-5d5aae84e110
---
Marcus fragt bei UI/Tooling oft konkret "WO geht das?". Dann will er **die eine konkrete Stelle**, nicht eine Schritt-für-Schritt-Tour durch das Tool oder Vorbedingungen vorweg.
**Why:** Lange Navigationsanleitungen ("erst Actions, dann Run, dann Banner...") bei einer simplen Ortsfrage haben ihn massiv genervt ("RED DEUTSCH", "durch das komplette GitHub gejagt"). Kostet Zeit und Nerven.
**How to apply:**
- Ortsfrage → direkt der Ziel-Ort + der Knopf, in einem Satz. Z. B. "PR → Files changed → Review changes → Approve".
- Relevante Einschränkung in einem Halbsatz dazu (z. B. "eigenen PR kann man nicht approven"), aber nicht als Vorrede davorstellen.
- Keine "erst dies, dann das"-Ketten, wenn eine Stelle reicht.
**Zweiter Fehler im selben Vorgang:** Ich behauptete früh "Review Required ist fürs Deployen ignorierbar". Falsch — der witglobal `deploy-branch`-Workflow prüft `reviewDecision` und bricht mit `REVIEW_REQUIRED` ab, solange der PR nicht approved ist. Lehre: bei Aussagen über fremde Workflows erst den reusable workflow lesen, nicht aus dem Muster raten. Siehe [[feedback_evidence_first]].
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback-priorisierung
description: "Cleanup-Arbeit entlang einer Linie, die Kollegen FRÜH testen lässt — nicht nach technischer Priorität."
metadata:
node_type: memory
type: feedback
originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---
Bei Aufräum-/Refactor-Sessions: Arbeitsreihenfolge richtet sich nach "was ermöglicht den Kollegen, möglichst früh zu testen", NICHT nach meiner technischen Priorisierung (auch wenn die "teilweise richtig" sein mag).
**Why:** Marcus sagte explizit: "wir gehen gezielt entlang einer Linie, die es meinen Kollegen erlaubt FRÜH zu testen — also NICHT nach Deinen Prios, obwohl sie teilweise richtig sind." Tempo & Testbarkeit für andere schlägt technische Eleganz / theoretische Priorität.
**How to apply:** Wenn ich eine Reihenfolge vorschlage, an "wann kann jemand anders das nutzen" ausrichten. Bei alternativen Vorschlägen die "kollegen-test-ermöglicht-am-frühesten"-Option zuerst nennen. Aufwand für Härtung (Crypto, Validierung, Edge-Cases) bewusst nach hinten schieben, wenn Marcus signalisiert "Zwischenstufe, wird nachgezogen".
+14
View File
@@ -0,0 +1,14 @@
---
name: feedback_push_macht_user
description: "Commits ja, aber git push macht Marcus selbst nicht versuchen"
metadata:
node_type: memory
type: feedback
originSessionId: bcf0637c-def5-4024-b8c6-e8be02a43c4f
---
Lokal committen ist ok, aber **`git push` macht Marcus selbst**. Nicht versuchen zu pushen (Environment hat ohnehin keine GitHub-Credentials → HTTPS-Auth schlägt fehl).
**Why:** Push ist seine Hoheit; spart fehlschlagende Auth-Versuche und Credential-Prompts.
**How to apply:** Nach dem Commit melden „liegt lokal auf <branch>, Push machst du" und stoppen. Siehe [[feedback_self_service_only]], [[feedback_plan_first]].
+24
View File
@@ -0,0 +1,24 @@
---
name: feedback-self-service-only
description: "Niemals Lösungen vorschlagen, die einen Ticket-Request beim Kunden / der Würth-IT brauchen — der Prozess dauert Wochen und blockiert."
metadata:
node_type: memory
type: feedback
originSessionId: 079df90a-81b0-43bc-aaab-fb5fce2fa508
---
Bei jedem Lösungsvorschlag **zuerst filtern**: kann Marcus das **ohne externe Beteiligte** machen? Wenn nein, gehört die Option nicht in die Liste.
**Why:** Mai 2026 — Marcus arbeitet als adesso-Berater bei Würth in einem Setup, in dem alles was er nicht selbst darf (Cluster-Config, Cert-Files vom Platform-Team, Operator-Installs, Service-Accounts, Proxy-Creds) **über ein Würth-Kunden-Ticket** läuft. Ticket-Durchlauf = Wochen. Während er auf Tickets wartet, steht sein Tagesgeschäft. Mehrfach gehört: *"das dauert ewig"*, *"ich kann nicht im Team fragen, da muss der Kunde ein Ticket aufmachen"*.
**How to apply:** Vor jedem Vorschlag mentaler Check:
- Erfordert er, dass Marcus jemanden bei Würth fragt? → raus.
- Erfordert er Rechte, die er als Namespace-User nicht hat? → raus, oder mit klarem Hinweis "geht nur über Ticket — Wochen".
- Self-service-Optionen (eigene Maschine, eigene Tools, OCP-Console UI als User) **immer zuerst** anbieten.
Beispiele aus dieser Session:
- "Frag im Team nach dem Sealed-Secrets-Cert" — verboten, das ist ein Ticket.
- "OCP-Admins fragen ob Cluster-Proxy existiert" — geht nicht spontan; nur als langfristiges Cleanup ohne Erwartungshaltung erwähnen.
- "Service-Account-Creds beantragen" — Ticket, gehört in *"falls wir später Phase X machen müssen"*-Klausel, nicht in den Akutplan.
Verträgt sich mit [[feedback-plan-first]]: Plan muss self-service sein, sonst ist kein Plan, sondern Wunschzettel.
+23
View File
@@ -0,0 +1,23 @@
---
name: project_bestaetigen_bug
description: FSA-Freigabe-Loop auf Edge/Windows beim Kollegen — morgen Policy + Konsolen-reason prüfen
metadata:
node_type: memory
type: project
originSessionId: f50a3c06-a9d7-40a9-9b2e-14ddb2c03df5
---
Bug „Freigabe bestätigen"-Loop, nur beim Kollegen unter **Windows/Edge** (PWA, kein Tauri).
**Symptom:** Ordner (neu) auswählen → springt SOFORT in „Freigabe bestätigen" statt App zu öffnen → „Freigabe bestätigen" zeigt wieder nur „Zulassen", NICHT „Einmal/Bei jedem Besuch zulassen" → nach Reload erneut die Maske → Endlosschleife.
**Mechanik (Code):** Handle liegt in IndexedDB (überlebt Reload → Reconnect-Button erscheint), aber die Permission überlebt den Reload nicht. `fileIo.init` prüft `queryPermission({mode:'readwrite'})`; `!== 'granted'``NO_PERMISSION``main.js:85` mountet wieder `setupApp.vue`. Stellen: `libs/common/frontend-common/src/lib/utils/file-io.util..ts:44-46` + `reconnect` :87, `setupApp.vue:96/115/125`, `main.js:84-88`.
**Schon ausgeschlossen:** installiert-vs-Tab (bei Marcus läuft Browser-Edge ohne Install sauber). Marcus' eigene Maschine funktioniert. `edge://settings/content/fileSystem|content|fileEditing` existieren beim Kollegen nicht/leer — Permission-Liste ohne Ergebnis (kann normal sein, wenn gerade kein aktives Recht). Auto-Widerruf als Erklärung verworfen: das wäre durch Neu-Init heilbar, ist es aber nicht.
**Morgen früh mit Kollegen (Merkzettel):**
1. **Policy:** `edge://policy` → Filter `FileSystem`. Gesucht `DefaultFileSystem(Read|Write)GuardSetting` = `3` (= jedes Mal fragen, unterdrückt persistente Option) bzw. `…AskForUrls`. `2` unwahrscheinlich (Picker würde scheitern).
2. **Log:** bei ihm DevTools-Konsole → `reason` aus „Cache-Verzeichnis nicht freigegeben:" (`main.js:87`). `NO_PERMISSION` = Grant persistiert nicht; `NO_HANDLE` = anderer Bug (IndexedDB-Write/geräumt).
3. Dialog beim Klick: erscheint „Bei jedem Besuch zulassen" oder nur „Zulassen"?
Evidence-first ([[feedback_evidence_first]]): erst diese zwei Artefakte, dann Diagnose. Verwandt: [[project_pwa_sw_auth_bypass]].
+57
View File
@@ -0,0 +1,57 @@
---
name: project-cleanup-timeline
description: Cleanup-Sprint plato-customer Mai 2026 — Status nach BFF-Umbau und nächste Themen (User-DB, Postgres).
metadata:
node_type: memory
type: project
originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---
10-PT-Aufräum-Sprint auf `development`. Linie: [[feedback-priorisierung]].
**Status Ende 2026-05-22 (Fr):**
- ✅ Storage-Layout (Subdirs + `.crypt`) + AES → Base64 (`94d19a5`)
- ✅ Auto-Migration `plato5/crm.sqlite` bei Init (`ee57b86`)
- ✅ IdP-Anbindung verifiziert (siehe [[project-idp-witglobal]])
-**Auth-Umbau auf BFF abgeschlossen** (`00e2c37` "activate odic login" + weitere Commits): Dummy-JWT raus, `passport-jwt`+`jwks-rsa` raus, `openid-client`+`express-session` rein, `client_secret` server-seitig (nicht mehr im Browser), Session-Cookie-basiert. Siehe [[project-idp-witglobal]] und [[project-run-commands]].
- ✅ User `ex08802409` in `user.json` ergänzt (provisorisch, ohne echten Datensatz)
- ⏳ Test-Rig `scripts/oidc-test.mjs` uncommitted (separater Scope, war Pre-BFF-Verifikation)
**Nächste Themen (am 2026-05-22 angerissen, Implementierung steht aus):**
- **User-DB**: aktuell statisch aus `user.json` via `UserRegistry` (Konstruktor `readFileSync`). Soll eine richtige DB werden — Marcus hat das explizit als nächstes Thema benannt nachdem die User-Mapping-Lücke beim Login auffiel.
- **Kunden-Persistenz: SQLite → Postgres**: aktuell SQLite via TypeORM in `data/customer.db` (PVC-mounted im Container). Bedeutet Single-Pod, kein Rolling-Deploy. Plan: separater Postgres-Pod (StatefulSet, im Gitops-Repo zu definieren), TypeORM-DataSource auf `postgres` umstellen, Migrations neu generieren, lokal Compose o.ä. — kein Managed-Service bei Würth (außer SQL Server).
- **Infrastruktur-Repo** existiert ("haben wir") — noch nicht im Detail angeschaut.
**Plan 2026-05-26 (Di) — "alles fertig":**
- Übergabe an Kollegen zum Testen
- Test-Automatisierung
- QS-Übergabe
- **Blocker für Auth-Rollout**: Windows-Kollege braucht funktionierenden Weg zum IdP. Solange nicht geklärt, kann der BFF-Stand nicht "scharf" für ihn werden — er kommt sonst nicht in die App.
**Stand 2026-05-27 (Mi) — Deployment-Cleanup-Runde (siehe Commits `f6d7b6d`, `acafac5`, `b6856e9`):**
- ✅ OIDC_CLIENT_SECRET + SESSION_SECRET aus Image, via Sealed-Secret im GitOps-Repo (`plato-customer-secrets`), `envFrom` im Deployment-Patch `backend.yaml`
-`trust proxy` für Session-Cookies hinter OCP-Router
- ✅ Proxy-ENVs (`HTTPS_PROXY` etc.) aus Image, in Deployment-`env:`-Block; undici-`ProxyAgent` für Node-22-fetch
- ✅ Fail-Fast in `main.ts` und `oidc.service.ts` wenn Secrets fehlen (Crash statt stille Dev-Fallbacks)
- ✅ Alte GHCR-Versionen mit eingebackenem Secret gelöscht (manuell via Web-UI), aktuelle Version 0.3.0
- ⏸️ Session-Store-Umzug (MemoryStore → Redis) verschoben: replicas=1, kein akuter Bedarf, kein Redis im Namespace deploybar
**Plan 2026-05-28 (Do) — umgestellt:**
- **Vormittag (statt Postgres-Backbone):** Migration + Import-Handling. Postgres-Backbone wurde verschoben. Code-Bereich: `apps/plato-customer-be/src/migration/` (Controller `/import/sqlite` + `/import/excel`, `SqliteImportService`/`MsExcelImportService`, `CustomerRehydrator`, `DuplicateFinderUtils`, SQL-Selects, `SQliteDb`); shared `xlImport2Customer`+`excel2json`; FE `migrationUploadClient` + `legacyMigration` (`./plato5/crm.sqlite`). Import liefert nur `MigrationCheck` (Vorschau, BE persistiert nicht selbst — Persistenz FE über `persistentStorage`/Outbox bzw. `CustomerCoreService.createOrUpdateList`).
- Offene Schwachstellen im Import (28.05. analysiert): (1) `DuplicateFinderUtils.fuzzyFinds` (Kunden ohne customer_number) wird gesammelt aber nie ins Ergebnis/Report übernommen; `getByCustomerFuzzy` existiert im Core-Service, wird aber nicht aufgerufen — angefangenes Feature. (2) `SQliteDb.executeSelect` als `Promise<string>` typisiert, liefert aber Row-Array. (3) Import schreibt nichts BE-seitig.
- **Architektur-Klarstellung Marcus (28.05.):** SQLite-Migration ist ein EINMALIGER Prozess beim ersten App-Start — `main.js` Boot-Schritt 5.5 ruft `legacyMigration.migrateIfPresent()` nur `if (manifest.created)` (Cache-Verzeichnis frisch). Ab dann wird mit SAP-Daten gearbeitet. Die manuellen Header-Buttons `⇆ SQLite`/`⇆ Excel` (`CustomerHeaderActions.vue`) widersprechen dem → auf Marcus' Wunsch **disabled** (nicht gelöscht, reaktivierbar). Wie es mit dem Import-Pfad weitergeht, will Marcus noch besprechen.
- **SAP-Einzel-Flow umgebaut (28.05., erledigt, typecheck grün):** SAP-Nummer ist führend, Eingabe über `SapImportDialog` (bleibt). `CustomerStore.fetchFromSap` sucht lokal nach `customer_number`; gefunden → hart aus SAP überschreiben (8 Stammdaten-Felder, Identität bleibt), nicht gefunden → neu anlegen. Verdrängte alte Email → geretteter Kontakt (`overwriteWithSap` + `guessNameFromEmail` in `sap-adapter.ts`), Name aus Email-Local-Part geraten, Match per Email. Bestehende Kontakte/Adressen bleiben beim Überschreiben erhalten; SAP-Partner-Kontakt/-Adresse kommen nur bei Neuanlage rein.
- **SAP-Mock ins BE integriert (28.05., erledigt, typecheck grün) — Variante B, bewusst Notlösung & komplett removable:** Marcus' Vorgabe war „muss ohne großes Aufhebens vollständig rausnehmbar sein". Neues self-contained Modul `apps/plato-customer-be/src/sap-mock/` (Controller `@Get('CustomerSet')`, Service liest `crm.sqlite` via `better-sqlite3`, Query/XML 1:1 aus altem Express-Mock portiert). In `app.module.ts` **flag-gegatet** via `SAP_MOCK_ENABLED=true`. Dockerfile bekommt eine `COPY apps/sap-mock/crm.sqlite`-Zeile (30 MB). Begründung B statt eigenem Pod: SAP-Call kommt aus dem Browser (`sapClient``VITE_SAP_URL`), eigener Pod bräuchte Route+HTTPS+CORS+FE-Build-URL; B = ein Deployment, same-origin. **Rausnehmen** = Ordner löschen + 2 Zeilen in app.module.ts + COPY-Zeile + `VITE_SAP_URL` aufs echte SAP. Standalone `apps/sap-mock/` bleibt unangetastet (Quelle der crm.sqlite).
- **SAP-Mock Container-Build + Runtime VERIFIZIERT (28.05., podman):** `better-sqlite3` baut auf `node:22-alpine` ohne Build-Tools (prebuilt musl-binary), Modul wird vom nx-Webpack externalisiert, Image ~853 MB. Container bootet mit `SAP_MOCK_ENABLED=true` + Dummy-`OIDC_CLIENT_SECRET`/`SESSION_SECRET` (OIDC-Discovery-Fehler crasht NICHT). `/CustomerSet` liefert OData-XML aus `/app/apps/sap-mock/crm.sqlite`. Lokal: `docker` nicht da (kein Daemon) → `podman` nutzen.
- **SAP-Mock Query an neuen Flow angepasst (28.05.):** Lookup jetzt per `customer_number` (war interne `customer_id`) — die SAP-Nummer ist führend, und der FE-Adapter mappt `d:CustomerID``customer_number`, also gibt der Query auch `cu.customer_number as CustomerID` aus. Contacts/Addresses-Joins von `INNER` auf `LEFT OUTER` umgestellt, damit auch Kunden ohne Kontakt/Adresse auflösen (404 nur noch bei unbekannter Nummer). **OFFEN (Marcus klärt):** `CustomerToplevelNumber` = `customer_number` (→ FE `verband_nummer` = Kundennummer), weil Legacy-DB keine eigene Verband-Spalte hat — evtl. auf leer setzen.
- **Postgres-Backbone — am 2026-05-28 abend LIVE auf DEV gebracht (war "verschoben"):** Zweiter Pod `plato-postgres` im GitOps-Repo (`base/postgres.yaml`: Deployment RHEL-Image `registry.redhat.io/rhel9/postgresql-16` + ClusterIP-Service `plato-postgres:5432`, KEINE Route). Creds via Sealed-Secret `plato-postgres-secrets` (User `plato_admin`, DB `Plato`). **Bewusst OHNE PVC = ephemer** (Daten weg bei Restart). Backend-DataSource umschaltbar gemacht (`DB_TYPE=postgres``pg`, sonst sqlite-Default), `synchronize:true` für Postgres (keine PG-Migrations, da eh ephemer). Im DEV-Backend-Deployment via `envFrom plato-postgres-secrets` + `DB_TYPE=postgres` aktiviert. Verifiziert: Pod 0.6.1 Running, mit Postgres verbunden, Tabellen (customer/contact/address/contact_addresses) per synchronize angelegt, DB leer. **Stolperstein gefixt:** Entities hatten hartes `type:'datetime'` (SQLite-only) → auf Dialekt-Default umgestellt (kein Typ bei `@CreateDateColumn`/`@UpdateDateColumn`), sonst `DataTypeNotSupportedError` auf Postgres. Namespace `1401-plato-development`, RedHat-Image pullt im Cluster.
- **PVC konfiguriert (2026-05-28 abend):** StorageClass-Frage geklärt — Plattform-Team: PVC selbst als K8s-Objekt anlegen, CSI-Treiber legt PV automatisch an; **thin-csi** = Default/OLTP (für DB), trident-nas nur für File-RWX. In `base/postgres.yaml` ergänzt: PVC `plato-postgres-data` (thin-csi, RWO, **50Gi** — Marcus' Wahl quota-sparsam: 400 Nutzer × 50MB ≈ 20GB Rohdaten + DB-Overhead; nicht die beantragten 100GB), gemountet an `/var/lib/pgsql/data`, dazu `strategy: Recreate` (sonst RWO-Mount-Deadlock beim Redeploy). Kustomize-Build grün. **Noch nicht committet/deployed** (Stand Reden). `data/customer.db` bleibt uncommitted, sqlite-Daten werden NICHT nach Postgres migriert (leerer Start gewollt).
- **synchronize → PG-Migrations umgestellt (2026-05-28, Weg „B" hand-portiert):** Marcus' Entscheidung — `synchronize` raus, weil mit PVC persistent; „wenn der Pod dann deterministisch crasht, ist offensichtlich was an der Migration falsch" (Crash+Log = Diagnose, [[feedback-keine-zwischenstufen]]). Befund: die einzige bestehende Migration ist rein SQLite (`datetime`, Temp-Table-Tanz) → läuft auf PG nie. Daher neue PG-Migration `libs/customer/customer-be/src/lib/db/1780000000000-…-customer-pg-migration.js` hand-portiert aus den Entities (uuid-PK `gen_random_uuid()`, `TIMESTAMP/now()`, FKs per ALTER, Constraint-/Index-Namen 1:1 aus M000). `customer-data-source.ts`: Postgres `synchronize:false` + eigener Glob `*customer-pg-migration.js` (disjunkt vom SQLite-Glob `*customer-migration.js`). Asset-Copy (`apps/plato-customer-be/project.json`: `**/db/**/*migration.js`) erfasst beide; Prod-Build verifiziert, beide JS landen in dist, Globs trennen sauber. Deploy-Hinweis: Backend gegen ALTEN synchronize-Pod → `CREATE TABLE … already exists`; gitops-PVC-Change zuerst deployen (Pod neu = leeres PVC), dann läuft Migration clean.
- **Migrations-Features angefragt (2026-05-28, noch offen, Rückfragen unbeantwortet):** (1) Bei Import zusätzlich per `customer_number` prüfen ob Kunde existiert → wenn ja, "aus SAP updaten" und an Client zurückgeben (offen: BE-seitig SAP-Mock rufen + sap-adapter-Logik BE-portieren, oder FE macht `fetchFromSap`?). (2) Neuer FE-Dialog: aus ALLEN Kunden (`CustomerCoreService.findAll` aus Postgres) auswählen, welche in den lokalen Stand geladen werden.
**Plan 2026-05-29 (Fr):**
- **Outbox / Inflight / BadBox** Pattern + eine weitere Sync-Schnittstelle. Marcus will erst "genau anschauen" bevor implementiert wird.
**Why:** Marcus hat den Sprint explizit als "10 PT" geframet mit Test-/QS-Übergabe Di 26.05.2026.
**How to apply:** Wenn am Montag/Dienstag oder später eine neue Session startet, hier reinschauen für aktuellen Stand, statt aus git log zu rekonstruieren. Beim Aufgreifen der User-DB oder Postgres-Themen: Marcus wollte beide Sachen aufgreifen, hatte aber explizit das Postgres-Thema vor sich her geschoben (Kunden-DB zuerst). Reihenfolge mit ihm abklären.
+24
View File
@@ -0,0 +1,24 @@
---
name: project-dummy-jwt-secret
description: Hartverdrahtetes Demo-JWT-Secret PLATO_DEMO_JWT_SECRET_2025 ist als Auth zu ersetzen — Speicherorte und Risiko.
metadata:
node_type: memory
type: project
originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---
In Backend UND Frontend ist das gleiche JWT-Secret `PLATO_DEMO_JWT_SECRET_2025` hartverdrahtet. Das Frontend signiert seine eigenen Tokens (Issuer = Client), Backend akzeptiert sie mit HS256. Wer das Bundle hat, kann sich ein Token für beliebige User basteln.
**Fundstellen (Stand 2026-05-21):**
- BE: `libs/command/command-be/src/lib/auth/dummy-auth-contants.ts`
- BE (Duplikat im `dummy-for-development/`-Ordner): `libs/command/command-be/src/lib/auth/dummy-for-development/dummy-auth-contants.ts`
- BE Strategy (vorgeblich "prod"): `libs/command/command-be/src/lib/auth/jwt.strategy.ts:13` — Kommentar `// fest verdrahtet`
- BE Dummy-Strategy: `libs/command/command-be/src/lib/auth/dummy-for-development/dummy-jwt-strategy.ts`
- BE Dummy-Token-Controller: `libs/command/command-be/src/lib/auth/dummy-for-development/dummy-jwt-token.controller.ts`
- FE: `libs/common/frontend-common/src/lib/be-sync/dummy-jwt-creator.ts:2`
- FE-Aufrufer: `plato-web-client.ts:20`, `sqlite-upload-client.ts:3`
- Controller-Guard: `libs/command/command-be/src/lib/command.controller.ts:23,35` mit `@UseGuards(AuthGuard('jwt'))`
**Why:** Das ist die laufende Aufräum-Linie für DEV — soll durch echte IdP-Anbindung an [[project-idp-witglobal]] ersetzt werden. Marcus hat in dieser Session zuerst die Token-/Layout-/Crypto-Themen abgearbeitet (Commits `94d19a5`, `ee57b86` auf `development`), Auth-Hardening steht als nächster Block an.
**How to apply:** Beim Umbau alle Fundstellen rauswerfen (Konstante, beide Strategies, Dummy-Controller, FE-Token-Creator, Imports). Replace mit `passport-jwt` + JWKS-Verifikation gegen `https://login-dev.witglobal.net/pf/JWKS`, `issuer`-Check, `audience`-Check (`KONZ-PlaTo-Customer-DEV`). FE postet kein selbstsigniertes Token mehr, sondern den IdP-Access-Token.
+20
View File
@@ -0,0 +1,20 @@
---
name: project_fe_design_system
description: "FE-Design-System — CSS-Token-Layer + Lucide-Icons, Würth-Rot; eingeführt im UI-Refresh"
metadata:
node_type: memory
type: project
originSessionId: ca356122-9115-4968-8361-c6d253d00817
---
UI-Refresh der Customer-FE gestartet (Branch `feature/fe-ui-refresh`, Commit e32b996, basiert auf development).
**Konventionen ab jetzt:**
- Farben/Abstände/Typo/Radius kommen aus `apps/plato-customer-fe/src/css/tokens.css` (CSS-Variablen, kein Build-Step). `base.css` ist global und nutzt nur diese Tokens — keine neuen Magic-Werte/Hardcodes in Komponenten.
- Markenrot: `--c-brand: #cc0000` (Würth), nur als Akzent.
- Icons: `lucide-vue-next` (v1), import named (`import { Plus } from 'lucide-vue-next'`). KEINE Emoji mehr in der UI.
- HD: Inhalt auf `--content-max` (1720px) zentriert, Liste/Detail-Split 58/42.
Erster Durchgang war "sichtbarer Refresh" (Header, Liste, Detail, Karten, Icons). Noch nicht angefasst: TechFooter-/Remote-Dialog-Glyphen (bewusst gelassen), Inputs-Detailpolish, evtl. Dialoge (SapImport/MigrationReport/RemoteCustomer).
Verwandt: [[project_montag_offen]] (UI-Redesign / Design System Montag).
+18
View File
@@ -0,0 +1,18 @@
---
name: project_gitops_oidc_werte
description: Wo die OIDC-Werte im gitops-Repo liegen — DEV und PROD
metadata:
node_type: memory
type: project
originSessionId: 8d8b676f-cd4b-466f-a9d8-3340ca36a448
---
Repo `fahrzeugeinrichtung-plato-gitops`. Kustomize: `base/` + `overlays/environments/{development,production}/`. Pfade unten sind repo-relativ.
**Klartext-env (BE)** pro Overlay: `overlays/environments/<env>/backend.yaml`, unter `spec.template.spec.containers[].env`. Hier gehören CLIENT_ID/ISSUER hin.
**Secrets** (CLIENT_SECRET, SESSION_SECRET) als SealedSecret `plato-customer-secrets`, eingebunden via `envFrom → secretRef`:
- DEV: `overlays/environments/development/plato-customer-secrets.sealed.yaml`, Namespace `1401-plato-development`.
- PROD: `overlays/environments/production/customer-sealed-secret.yaml`.
Verwandt: [[project_idp_witglobal]].
+49
View File
@@ -0,0 +1,49 @@
---
name: project-idp-witglobal
description: "PingFederate (WITGLOBAL) als IdP für plato-customer-be — DEV-Konfiguration, BFF-Architektur, Token-Eigenschaften, Test-Rig."
metadata:
node_type: memory
type: project
originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---
PingFederate (WITGLOBAL) ist der Identity Provider für die Plato-Customer-Applikation.
**Architektur (seit Umbau 2026-05-22): Backend-for-Frontend (BFF)** — passt zum ursprünglichen IAM-Ticket-Inhalt.
- BE ist OIDC-Client (`openid-client` v6, confidential client mit `client_secret`)
- BE macht Code-Tausch, behält die Tokens
- FE bekommt nur HTTP-only Session-Cookie via `express-session`
- FE redirected bei 401 (axios-Interceptor) auf `/auth/login`
- Auth-Routen: `/auth/login`, `/oauth/callback`, `/auth/me`, `/auth/logout`
- Lokal: zusätzliche Middleware in `apps/plato-customer-be/src/main.ts` fängt `GET /` mit `?code=` ab (weil IAM für localhost nur Root-Redirect-URI registriert hat)
- Server: saubere `/oauth/callback`-Route, keine Root-Middleware nötig
- Lokales Cmd: siehe [[project-run-commands]]
**DEV-Endpoints und Client:**
- Issuer: `https://login-dev.witglobal.net`
- Discovery: `https://login-dev.witglobal.net/.well-known/openid-configuration`
- Client-ID: `KONZ-PlaTo-Customer-DEV`
- Client-Secret: aus ENV `OIDC_CLIENT_SECRET` (server-side, nie im Browser-Bundle!)
- Registrierte Redirect-URIs:
- Lokal: `http://localhost:3000` (Root, ohne Pfad — Exact-Match!)
- DEV-Server: `https://fahrzeugeinrichtung.apps.ocp-dev01.wgn.wuerth.com/oauth/callback`
- PROD-Server: `https://fahrzeugeinrichtung.apps.ocp-01.wgn.wuerth.com/oauth/callback`
**Token-Eigenschaften (verifiziert 2026-05-22):**
- `access_token` ist ein JWT (RS512), Audience = client_id = `KONZ-PlaTo-Customer-DEV`
- Claims im Access-Token: `scope`, `client_id`, `iat`, `jti`, `aud`, `sub`, `uid`, `iss`, `pi.sri`, `exp`
- Laufzeit: 3600s
- JWKS-URI: `https://login-dev.witglobal.net/pf/JWKS` (wird seit BFF von `openid-client` intern gehandhabt; nicht mehr direkt nötig)
**User-Mapping:** Nach erfolgreichem Code-Tausch wird `sub` (z.B. `ex08802409`) in `libs/command/command-be/src/lib/users/user.json` über `UserRegistry.getUser()` nachgeschlagen. Existiert kein Eintrag → 401. **Marcus' Eintrag `ex08802409` wurde am 2026-05-22 ergänzt** (provisorisch mit `marcus.hinz@adesso.de`). Eine richtige User-DB ist als nächstes Thema dran (siehe [[project-cleanup-timeline]]).
**Test-Rig:** `scripts/oidc-test.mjs` — Self-contained Node-Skript (keine Deps), startet HTTP-Server auf :3000, druckt Authorize-URL, fängt Callback, tauscht Code → Token, gibt alles dekodiert aus. Vor Test: Backend stoppen (Port-Konflikt). Aufruf: `CLIENT_SECRET='…' node scripts/oidc-test.mjs`. ENV überschreibbar: `CLIENT_ID`, `ISSUER`, `REDIRECT_URI`, `SCOPES`, `PORT`. Nutzt Self-Issuing-Pattern (war zum Validieren der IdP-Anbindung gedacht, vor BFF-Umbau).
**Netz-Exposure / Hardening (verifiziert 2026-05-22):** Vorgelagerter Proxy/WAF antwortet ausschließlich auf die definierten OIDC-Pfade (`/.well-known/...` → 200, `/pf/JWKS`, `/as/authorization.oauth2`, `/as/token.oauth2`). Alles andere — inkl. `GET /` und `HEAD /` — wird stillschweigend geblackholed (Connection-Timeout, kein RST, kein 403). Das ist by-design und in Ordnung. **Debugging-Falle:** Wenn die FE-PWA "trotz nicht erreichbarem IdP läuft", ist das fast immer der Service Worker + gecachte Tokens — nicht eine offene Test-Instanz. Verifikation: DevTools → Application → Clear site data → Hard-Reload. Curl aus kalter Shell ist die ehrliche Antwort auf "ist der Server wirklich offen?".
**Gotcha — `redirect_uri` Trailing-Slash bei lokalem BFF-Flow (gefixt 2026-05-22):**
PingFederate validiert `redirect_uri` beim Token-Tausch byte-exakt gegen den Authorize-Wert; bei DEV ist `http://localhost:3000` (ohne Slash) registriert und der IdP normalisiert den Authorize-Wert mit Slash intern wieder auf den registrierten String zurück. `openid-client` v6 macht beim Token-Tausch aber zwangsweise `stripParams(new URL(currentUrl))` → produziert `http://localhost:3000/` (WHATWG-URL erzwingt `pathname='/'`) → `invalid_grant: redirect_uri value must be identical to the value included in the authorization request.` **Fix:** `customFetch`-Hook in `OidcService.onModuleInit` patcht den outgoing `URLSearchParams.redirect_uri` (Body ist `URLSearchParams`, NICHT String/Uint8Array) und schneidet den Trailing-Slash ab. Server-Pfad (`/oauth/callback`) ist davon nicht betroffen.
**Offene Punkte:**
- Session-Store ist `MemoryStore` (express-session default). Für Multi-Pod-Deployment muss Redis o.ä. rein.
- `OIDC_CLIENT_SECRET` muss im Gitops-Repo als Kubernetes-Secret an den Pod gereicht werden.
+30
View File
@@ -0,0 +1,30 @@
---
name: project_kontakt_adress_spec
description: "Geplante Spec Kontakte/Adressen — SAP-Schutz, Validierung-bei-Änderung, M:N-Removal (noch NICHT implementiert)"
metadata:
node_type: memory
type: project
originSessionId: 9ea3a233-d124-48d2-bcbd-df83326a7e21
---
Soll-Funktionen für Kontakte/Adressen, am 2026-06-09 mit Marcus festgeklopft und im Wesentlichen UMGESETZT auf `development`: 282698f Relation-Removal, f423c8f Server-Guard, 5473a2b FE-Anrede-Pflicht, 140c09f Delete-Integrationstest (Kaskade+SAP-Block), 2b16384 Orphan-Delete-Fix+Test (createOrUpdate mergte Collections falsch -> Einzel-Löschen verpuffte; jetzt Skalare mergen, Kind-Arrays explizit setzen wie batchImport). OFFEN nur noch: `deleteAll` auf eigenem Branch entfernen (s.u.); MS-EXCEL-vs-Mehrgesellschaften vertagt. Commits noch ungepusht (Push macht Marcus).
**Adressen NUR auf Kundenebene.** Kontakt↔Adresse-M:N (`contact_addresses`, JoinTable auf Contact-Seite) wird ENTFERNT — toter Code, nie befüllt, auch in Alt-DB `_contacts2addresses` = 0 Einträge. Evtl. später wieder (geringer Aufwand); dann ist offen, wie eine Adresse bei Kontakt-Neuanlage zugeordnet wird. Removal braucht Entity-Edits + TypeORM-Migration; bringt die latente `addressId ON DELETE NO ACTION`-Falle weg.
**SAP-Schutz (origin === 'SAP' = read-only, „aus SAP hinterfragen wir gar nichts"):**
- UI schützt schon: `canEditCustomer` (CustomerDetails.vue:163), `canEditItem` (useCardListManageer.ts:49) — disabled Edit+Delete für Nicht-PlaTo/Excel.
- Server hat KEINEN Guard (nur `enforceOwnership` für owner_country). Zu bauen: `enforceSapProtection` in `CustomerCoreService.createOrUpdate` (NICHT in `batchImport` — das ist die Migration), Delete-Guard in `delete()` (SAP-Kunde nicht löschbar).
- origin der Kinder aus DB-Stand ableiten (by id), nicht aus Payload — sonst spooft FE `origin: 'PlaTo'`. SAP-Kind verändert/fehlend → Reject.
**Cascade:** Plato-Kunde löschen → Kontakte+Adressen mit (existiert via OneToMany `cascade`/`orphanedRowAction`, nichts zu tun). `deleteAll()`/`customer.delete.list` ist reines Test-Werkzeug → auf PROD raus/nicht erreichbar, KEIN Guard nötig.
**Contact-Felder (ALLE bleiben):** contact_type, form(=Anrede), title, firstname, lastname, fullname, position, department, phone, mobile, fax, mail, website, contact_number, comment. `addresses` RAUS.
- `fullname` wird abgeleitet = `firstname + " " + lastname`, nicht editierbar (heute freies Feld CustomerContacts.vue:39).
**Validierung:**
- Pflicht = Anrede(form)/firstname/lastname/mail. Keine DB-NOT-NULL, nur Validierungslayer.
- Greift NUR für origin PlaTo. SAP nie. MS EXCEL = vorerst PlaTo (Mehrgesellschaften später klären).
- PlaTo entsteht auch beim SAP-Import (Rettungskontakt aus abweichender Mail, `guessNameFromEmail`) → u.U. unvollständig. Daher: Pflicht NICHT bei Anlage, sondern erst bei ÄNDERUNG erzwingen — nur neue (FE) oder ggü. DB veränderte Kontakte prüfen.
- „Verändert" = gleicher Diff wie SAP-Guard, via `contactProjection`/`addressProjection` aus `content-hash` (eine Wahrheit).
Beispiel-Stores (Klartext) für Tests: `ressources/outbox.v1.json`, `ressources/inflight.v1.json`. Siehe auch [[project_naechste_schritte]].
+17
View File
@@ -0,0 +1,17 @@
---
name: project_loader_signing_key
description: Loader-Signing-Key am 2026-06-08 gelöscht — beim Wiederaufgreifen neu generieren
metadata:
node_type: memory
type: project
originSessionId: 94c6e3e8-5afc-4c31-9d3c-d5f5bcd6c872
---
`loader-signing-key.pem` (privater ed25519-Key für die Tauri-Loader-Asset-Signierung) lag untracked im Repo-Root und wurde am **2026-06-08 gelöscht** (war nie committet, nirgends referenziert). Bewusste Entscheidung (Marcus): „löschen, ersetzen wenn das Thema neu hochkommt".
Beim Wiederaufgreifen des Loader-Themas (geparkt, siehe [[project_tauri_health_grau]]):
1. Neues ed25519-Paar generieren.
2. `PUBLIC_KEY_HEX` in `apps/plato-customer-desktop/src-tauri/src/loader.rs:24` auf den neuen Public Key setzen — der alte (`c370c66a…`) gehörte zum gelöschten Private Key.
3. `tools/loader/gen-loader-keys.mjs` (im loader.rs-Kommentar referenziert) **existiert nicht** — erst erstellen oder Paar direkt per openssl/node erzeugen.
Loader-Kern (`loader.rs`): serviert Web-Assets aus `~/.plato/app` nur bei gültiger ed25519-Signatur gegen den einkompilierten Public Key (fail closed); der Private Key signiert die Bundles außerhalb (CI-Secret).
+31
View File
@@ -0,0 +1,31 @@
---
name: project_logging
description: Logging-Lösung — pino-BE + offline-fähiger FE-Log-Sync; Board (Phase 3) noch offen
metadata:
node_type: memory
type: project
originSessionId: c4e04d11-9b40-4c6a-9333-ce3bbf949ba2
---
**Implementiert 2026-06-03 (Branch development), Phase 1+2:**
- **BE:** `nestjs-pino` als App-Logger → strukturiertes JSON auf stdout (Cluster-Pipeline frisst das).
Kein pino-Transport (bricht unter webpack); lokal `… | npx pino-pretty`. Sensible Header redacted,
`/health` vom Auto-Logging ausgenommen. Bestehende `Logger.*`-Aufrufe routen automatisch durch pino.
`LOG_LEVEL`-Env steuert das Level.
- **`POST /client-logs`** (SessionAuthGuard, `ClientLogModule`): nimmt FE-Log-Batches, schreibt sie mit
`source:'fe'` + Actor aus der **Session** (nicht aus dem Body), Batch-Cap 100.
- **FE:** `clientLog` (frontend-common, Singleton): gedeckelter In-Memory-Ring (200), Batch-Sync (50) an
`/client-logs` sobald online — Heartbeat-getriggert, gleiches Muster wie Outbox. Retry bei Fehler,
kein Log-Loop, Einträge PII-frei (message/stack/code/context/path, gekürzt). In `main.js` verdrahtet:
`window.onerror` + `unhandledrejection` + Vue `errorHandler`, früh im Boot. (Vorher gingen uncaught
FE-Fehler komplett verloren.)
- Geteilter Typ `ClientLogEntry` in `@plato/models`. Commits `6f78d4b` (BE) + `fcc62f2` (FE).
**Bewusste Tradeoffs:** FE-Puffer ist in-memory (kein Storage-Dependency, übersteht keinen Reload —
Sync läuft aber sofort bei capture/online). errorList (Toasts) ist NICHT an clientLog gekoppelt — nur
uncaught/globale Fehler werden gesynct (Bridging wäre ein leichter Zusatz).
**Phase 3 (offen):** das Board. Empfehlung Grafana/Loki (oder vorhandener Cluster-Stack) — JSON-stdout ist
schon board-ready. Sentry/GlitchTip nur falls Stacktrace-Grouping gewünscht (SaaS = Egress/Ticket-Risiko →
self-service beachten). **Offene Frage an Marcus:** was bietet der Würth-Cluster schon (Grafana/Loki, ELK)?
Eine kleine `logger`-Abstraktion hält einen späteren Sentry/OTEL-Wechsel call-site-frei.
+20
View File
@@ -0,0 +1,20 @@
---
name: project_login_loop_rootcause
description: "Login-Loop lokal — Ursachen + Fix (gewaltiger Zeitfresser, nicht wiederholen)"
metadata:
node_type: memory
type: project
originSessionId: bcf0637c-def5-4024-b8c6-e8be02a43c4f
---
Der lokale Login-Loop (2026-06-02, riesiger Zeitfresser) hatte **mehrere überlagerte** Ursachen:
1. **Kern:** Der PWA-Service-Worker (`navigateFallback`/`NavigationRoute`) lieferte die gecachte App-Shell für den OIDC-Callback `/?code` aus (NavigationRoute ist VOR den runtimeCaching-Routen registriert). → BE sah den Code nie → `/auth/me` 401 → zurück zum Login → Loop. Bypass-for-network (SW aus) = sofort weg → eindeutiger SW-Beweis.
2. **Alter SW klebte:** loopte so schnell, dass das Auto-Update nicht durchkam → einmaliges Unregister/Clear nötig.
3. **Cookie:** BE-Build via `nx serve` nutzt `--node-env=production``secure: process.env.NODE_ENV==='production'` war lokal **true** → Secure-Cookie über http unzuverlässig.
**Fix (committet):** SW auf **injectManifest** umgestellt (`apps/plato-customer-fe/src/sw.ts`, lesbar) mit Callback NetworkOnly + `navigateFallbackDenylist: [...,/[?&]code=/]`; Cookie `secure:'auto'` (mit `trust proxy`); `ensureLoggedIn` offline-tolerant; `skipWaiting`/`clientsClaim`/`cleanupOutdatedCaches` → Deploys lösen den SW selbst ab.
**Lokales Re-Login nach BE-Neustart** ist erwartbar (MemoryStore); Prod nutzt Postgres-Sessions. Session-Store-Wechsel lokal: bewusst offen gelassen.
**Lehre fürs nächste Mal:** bei SW/Auth-Loop SOFORT Fakten holen — generierten `dist/sw.js` (Routen-Reihenfolge) ansehen + echtes BE-Log; nicht raten. Siehe [[feedback_evidence_first]], [[project_pwa_sw_auth_bypass]].
+33
View File
@@ -0,0 +1,33 @@
---
name: project_montag_offen
description: Offene Punkte für Montag (origin als SAP-Änderungsindikator) + heutige Auth-Fixes pushen
metadata:
node_type: memory
type: project
originSessionId: 73911b5f-10ba-449e-abaf-9db0e509132c
---
Stand Freitag 2026-05-29, Feierabend. Montag (2026-06-01) weiter:
**1. origin als SAP-Abgleich-Indikator (fachlich klären).** Marcus' Ansage: origin ist
genau das richtige Feld dafür — wenn ein Datensatz aus SAP NEU abgeglichen wird UND sich
Änderungen ergeben, soll das über origin erkennbar sein. Aktuell setzt aber sowohl das
Länder-Mapping (`country2origins: de→'SAP'`, Rehydrator) als auch der echte SAP-Treffer
(`sqlite-import.service.ts:57``target.origin='SAP'`) denselben Wert → die zwei Fälle sind
am origin NICHT unterscheidbar. Hier ist die fachliche Soll-Semantik zu definieren.
**2. Heutige Auth-Fixes pushen + deployen** (Marcus pusht selbst):
- `2bccadb` Login-Loop bei verwaister Session (Callback: destroy+clearCookie+redirect /auth/login)
- `bf3d1a6` unbekannter User → Session invalidieren + nur 404 (kein Loop)
- `04b77f4` Migrations-Report im Boot-Import als alert (wegwerf in legacy-migration.ts)
- Login-Loop-Fix wurde live getestet (session-Tabelle geleert → 1× sauberer Re-Login, stabil). ✅
**3. UI-Redesign (neu, Marcus' Wunsch).** App sieht "unterirdisch" aus, soll ans Würth-CI/
ein Design System. Zwei Schritte: (1) Design System kommt Montag von Marcus (Format offen:
Figma-Export / Brand-Guide / CSS-Vars / Referenz-URL / Screenshots). (2) nah an den Kunden
bringen — Komponenten auf Tokens mappen. Vorgehen: zentrale tokens.css/Theme-Schicht anlegen,
dann scoped-CSS pro Komponente (Header, CustomerList, CustomerDetails, Dialoge) inkrementell
nachziehen. Claude kann Screenshots direkt lesen (bester Input fürs Look&Feel) + CSS/Tokens
aus Referenz extrahieren (WebFetch holt HTML/CSS, aber visuelles Urteil aus Screenshots).
Siehe [[project_naechste_schritte]].
+30
View File
@@ -0,0 +1,30 @@
---
name: project-naechste-schritte
description: "Stand der Sync-Arbeit + offene Punkte (aktualisiert 2026-06-03)"
metadata:
node_type: memory
type: project
originSessionId: da22c6d7-9f9f-4834-b8f6-bdd06d220c38
---
**Sync ist abgearbeitet (2026-06-03, Branch development):**
- Inflight-Recovery bei init: erledigt (`outbox.recoverInflight()` in `init`).
- finalize-Status-Semantik neu: SUCCESS/WARNING -> reconcile (WARNING zusätzlich über
injizierten Notifier -> Toast severity WARNING via `outbox.setNotifier` in main.js),
FAILED -> badBox (terminal), QUEUED -> zurück in die Outbox + `sendCount++`, kein reconcile.
- Retry/Backoff im `background-sync`: transiente Sendefehler + QUEUED brechen den Loop ab und
planen verzögerten Re-Nudge (exponentiell `min(1s*2^(n-1),60s)`+Jitter aus `sendCount`),
Aufgabe nach 8 Versuchen -> badBox. Commit `42a595a`.
- Tests TDD: frontend-common 43 grün (`backoff.spec`, `outbox.spec`, `background-sync.spec`).
**Wichtige Einschränkung (latent):** Das BE emittiert weiterhin nur SUCCESS/FAILED
(`command.service.ts:51`, `domain-exception.util.ts:14`). WARNING/QUEUED kommen produktiv noch
NICHT vor — die neue Behandlung ist Vorsorge für den Tag, an dem das BE asynchron/mit Hinweis
antwortet. Wenn das gebaut wird, braucht der FE-Sync keine Änderung mehr.
**Migration ist abgeschlossen** (Commit `8e98c10` + Doku `docs/migration-regeln.md`, Commit `f269137`):
Kunden-Mobile aus Kontakt-Telefon, Keyless-Hash-Dedup für Kontakte/Adressen. Siehe [[project-sap-mock-fixtures]].
**Noch offen / mögliche nächste Themen:** origin-Semantik (SAP-Abgleich vs. Länder-Mapping,
siehe [[project_montag_offen]]), und die Folgethemen aus [[project-cleanup-timeline]] (User-DB,
Postgres-Umstieg). „Näheres zur Lift-Regel" wollte Marcus noch nachliefern.
+25
View File
@@ -0,0 +1,25 @@
---
name: project-optimistic-locking
description: "Server-seitige optimistische Sperre für Customer-Writes (VERSION_CONFLICT) — Fix gegen Last-Write-Wins bei zwei Sales am selben Kunden, eingebaut 2026-06-29."
metadata:
node_type: memory
type: project
originSessionId: 31351046-6fed-46e2-b117-bcc9d8064774
---
Vor Go-live (Donnerstag 2026-07-02) das Concurrency-Verhalten der Customer-Writes gefixt.
**Ausgangslage (charakterisiert):** `@VersionColumn` war im Schreibpfad wirkungslos — `persistCustomer` machte `repo.merge(existing, scalars)`, das die `version` aus dem Client-Payload übernahm. Folge: Last-Write-Wins, `version` lief sogar rückwärts (2→1), stiller Datenverlust bei parallelen Editoren. Jeder Sales hat prinzipiell alle Kunden des Landes → der Fall ist real.
**Fix (rein BE, kein FE-Eingriff):**
- `libs/shared/src/lib/errors/error-catalog.ts`: neuer Code `VERSION_CONFLICT` (HTTP 409).
- `libs/customer/customer-be/src/lib/customer-core.service.ts`: `enforceVersion()` in `persistCustomer` vor dem Merge — `incoming.version !== existing.version``DomainException('VERSION_CONFLICT', …, forcedState: existing)`. Danach zählt `@VersionColumn` wieder hoch.
- **Kernregel:** Sperre greift nur, wenn `incoming.version != null`. Ein nie synchronisierter Neuanlage-Stand (version undefined), der nach Crash erneut gesendet wird, ist ein idempotenter Re-Send — sonst wäre at-least-once / Crash-Recovery (S8b) kaputtgegangen. Ein bereits synchronisierter Datensatz trägt clientseitig immer eine version (reconcile schreibt den Server-Stand lokal zurück).
**Verhalten:** stale Edit → 409 → FE rollt über den vorhandenen `forcedState`-Mechanismus (wie SAP-Readonly) auf den Server-Stand zurück, Fehler sichtbar. Verliererseite verliert ihre Änderung (mit Meldung); ein Merge-Dialog wäre eine spätere fachliche Erweiterung.
**Wichtig für künftige Fixes:** `version` ist als „wer ist neuer"-Vergleichsbasis untauglich (client-steuerbar). `lastUpdateTs` ist server-autoritativ (`@BeforeUpdate``Date.now()`) — das ist die verlässliche Basis. Belegt in den Tests.
**Tests:** `customer-core.concurrency.spec.ts` (5, BE-direkt gegen In-Memory-SQLite) + `sync-loop.integration.spec.ts` S10 (End-to-End Multi-Sales). Gesamt 8 Projekte grün. Siehe [[project-sync-testing]] für den Test-Harness.
**Lokaler Testlauf:** node 22 via fnm nötig (`eval "$(/opt/homebrew/bin/fnm env)" && fnm use 22`), better-sqlite3 baut nicht gegen node 26. node_modules waren nicht installiert → `npm ci`. Repo: `~/projects/fahrzeugeinrichtung-plato-backend`.
+35
View File
@@ -0,0 +1,35 @@
---
name: project_prod_ist_stand
description: Prod (ocp-01 / 1401-plato-production) — Secrets (inkl. SAP) erledigt+aktiviert; einziger Blocker noch das to_be_replaced Image-Tag
metadata:
node_type: memory
type: project
originSessionId: 03691a44-b7eb-4d9d-9e7c-5d5aae84e110
---
Stand 2026-06-19, verifiziert direkt am Prod-Cluster (`api.ocp-01.wgn.wuerth.com`, Projekt `1401-plato-production`).
**Stand 2026-06-19 abends: Prod ist LIVE und voll erreichbar** — backend `0.38.4` 1/1 Running auf ocp-01, alle 3 Secrets (customer/postgres/sap) gemountet, ghcr-Pull funktioniert. Öffentliche URL **https://plato.witglobal.net** (Test: https://plato-test.witglobal.net auf ocp-dev01), zusätzlich zur alten fahrzeugeinrichtung-Route. OIDC-Login funktioniert.
**Custom-Routes + OIDC:** Routes als `route-public.yaml` je Overlay im gitops-Repo (commit `11ba61c`), edge/Redirect, host `plato.witglobal.net` bzw. `plato-test.witglobal.net` → backend-service:3000. Redirect-URI baut die App aus `req.headers.host` + fixem Pfad `/oauth/callback` (oidc.controller.ts `computeRedirectUri`). Beim IdP (PingFederate, strict byte-match, kein Trailing-Slash) **pro client_id** registriert: `https://plato.witglobal.net/oauth/callback` an `KONZ-PlaTo-Customer-PROD`, `https://plato-test.witglobal.net/oauth/callback` an `KONZ-PlaTo-Customer-DEV`. Kein Post-Logout-Redirect nötig.
PR `development → main` gemergt — `main` spiegelt den Prod-Stand. Go-live abgeschlossen.
Reihenfolge der Blocker, die fielen: (1) Secrets gesealt+aktiviert; (2) Dockerfile-Build-Bruch `crm.sqlite` raus → Image 0.38.4; (3) PR-Review REVIEW_REQUIRED approved; (4) **Namespace-Quota voll** (totes Frontend-Orphan-Deployment fraß 1 CPU/4Gi) → Frontend-Deployment/Service/Route gelöscht (war alter Split-Ansatz, Backend serviert FE-Assets selbst) → `oc rollout restart` → Pod startet.
**Quota-Warnung für später:** `default-resource-quotas` = 3 CPU / 12Gi hart. backend(1)+postgres(1) = 2, lässt nur 1 Slot Surge. Sobald ein zweiter Workload dazukommt (oder Rolling Update mit maxSurge), reicht es nicht → Quota anheben (Plattform) oder maxSurge=0.
**Historischer Ausgangsbefund (vor heute):**
1. **Image-Tag nie ersetzt** (offen). backend-/frontend-Pods ziehen wörtlich `…/backend:to_be_replaced` → ImagePullBackOff (Deployment 198d, 0/1). Der `.deploy to production`-Workflow (PR `development→main`, Kommentar `.deploy to production`) ist **nie gelaufen**. Das ist der gewählte Weg A, um es zu lösen — bringt echten Tag + klärt zugleich den offenen ghcr-Pull-Recht-Punkt auf Prod.
2. **Secrets — ERLEDIGT.** Alle drei SealedSecrets gegen ocp-01 gesealt, Entsiegelung auf Prod verifiziert (Synced=True), in `production/kustomization.yaml` aktiviert + in `backend.yaml` per `envFrom` referenziert:
- `plato-customer-secrets`, `plato-postgres-secrets` (commit `1ba047e`)
- `plato-sap-secrets` (commit `4601ea7`): SAP MUSS auf Prod (zentral). Werte = dieselben wie dev; dev-Klartext vom dev-Cluster gelesen, gegen ocp-01 neu gesealt. `SAP_URL` = dev-Endpoint `odatang9.wgn.wuerth.com/...` (User-Ansage „dieselben Werte"). Sealing-Rezept: `kubeseal --fetch-cert` geht auf Prod per Default-Pfad; dev-Secret per `oc --context=<dev> get secret -o json` → jq auf prod-ns umschreiben → `kubeseal --cert`.
- Beide gitops-Commits noch nicht gepusht (ahead 2; Push macht Marcus). Inhaltliche Werte-Korrektheit (richtige Prod-Creds?) erst sichtbar, wenn Backend hochkommt.
**Sauber/fertig auf Prod:** Namespace-Label `argocd.argoproj.io/managed-by=issp-gitops-developers` gesetzt, ArgoCD-App existiert und syncht. Plattformseite steht.
**Noch offen / nicht testbar:** ghcr-Pull-Berechtigung auf Prod (Pull via `default-dockercfg`, kein explizites `imagePullSecrets`) — scheitert aktuell am nicht-existenten Tag, nicht erkennbar an Auth. Zeigt sich erst mit echtem Tag. Außerdem: SAP-Secret fehlt für Prod komplett (dev hat `plato-sap-secrets.sealed.yaml`, prod nicht; `production/backend.yaml` referenziert SAP auch nicht).
Deploy-Mechanik siehe PDF `ressources/GitHub CICD to OpenShift.pdf`: Image nur auf `development` gebaut + wiederverwendet. Prod-Promotion = **Merge PR `development → main` von Hand** (das ist der manuelle Schritt); ArgoCD synct danach automatisch los (Korrektur Marcus 2026-06-23: NICHT manueller Sync). Dev-Cluster = ocp-dev01 läuft sauber (backend 0.38.1).
+16
View File
@@ -0,0 +1,16 @@
---
name: project-pwa-sw-auth-bypass
description: "PWA-Service-Worker muss server-seitige Navigationsrouten (/auth, /oauth, /commands) explizit durchlassen, sonst Login-Loop."
metadata:
node_type: memory
type: project
originSessionId: 73911b5f-10ba-449e-abaf-9db0e509132c
---
Der Service Worker (`apps/plato-customer-fe/vite.config.ts`, VitePWA/workbox) fängt per `navigateFallback: '/index.html'` + `runtimeCaching` mit `request.mode === 'navigate'` (StaleWhileRevalidate, cache `pages`) **alle** Navigationen ab. Server-seitige Routen, die NICHT zum FE gehören, müssen daher zweifach ausgenommen werden: in `navigateFallbackDenylist` UND als eigene `NetworkOnly`-Route (vor der navigate-Regel, erste passende gewinnt). Stand nach Fix `ab6933c` (2026-05-29): ausgenommen sind `/commands`, `/auth`, `/oauth`.
**Why:** Vor dem Fix war nur `/commands` ausgenommen. `/auth/login` (Navigation via `window.location.href` in FE `main.js:28`) wurde vom SW mit der gecachten index.html beantwortet statt zum IdP durchgelassen → Endlos-Loop: boot → `/auth/me` 401 → `/auth/login` → index.html → boot. Komplett client-seitig, der Server sah `/auth/login` nie. Symptom-Fingerabdruck: normales Profil loopt, Incognito beim Erststart sauber (SW kontrolliert erste Navigation noch nicht), nur "Seitendaten löschen" (= SW + Cache weg) heilt. `registerType: 'autoUpdate'` zieht den neuen SW bei Bestandsclients beim nächsten Laden nach.
**Zweiter Loop (Fix `9dd1a29`, 2026-06-02):** Die `navigate`-Runtime-Regel hat `matchOptions.ignoreSearch: true`. Lokal kehrt der IdP NICHT auf `/oauth/callback`, sondern auf die **Root** zurück (`http://localhost:3000/?code&state` — IAM hat lokal nur die Root als redirect_uri registriert, siehe `oidc.controller.ts computeRedirectUri`). Mit `ignoreSearch` matcht der Callback den gecachten `/`-Eintrag → SW liefert die alte index.html → die Root-Middleware in `main.ts` (verarbeitet `?code&state`) läuft nie → Endlos-Loop, auch nach Site-Data-Löschen. Die Denylist (`/auth`, `/oauth`) greift NICHT, weil der Pfad `/` ist. Fix: navigate-`urlPattern` um `&& !url.searchParams.has('code')` ergänzt → Callback geht ans Netz/den Server. Server-seitig war alles gesund (`/auth/login` → 302 IdP, `/auth/me` → 401) — der Bruch saß rein im SW. Diagnose-Fingerabdruck identisch (Profil loopt, Server sieht den Callback nie).
**How to apply:** Jede NEUE server-seitige Route, die der Browser per Navigation (nicht fetch) erreicht, MUSS in `navigateFallbackDenylist` UND als `NetworkOnly`-Route rein — und Callbacks, die auf der Root mit Query landen, zusätzlich aus der navigate-Regel ausnehmen (`code`-Param), sonst kehrt der Loop zurück. Verifizieren am gebauten Artefakt: `grep -o 'searchParams.has("code")' dist/apps/plato-customer-fe/sw.js`. FE muss nach Änderung neu gebaut werden (`npx nx build plato-customer-fe`), Browser einmal SW entregistrieren. Siehe [[project-run-commands]], OIDC-Flow: [[project-idp-witglobal]].
+26
View File
@@ -0,0 +1,26 @@
---
name: project-run-commands
description: Lokale Start- und Build-Befehle für plato-customer (BE + FE) nach BFF-Umbau am 2026-05-22.
metadata:
node_type: memory
type: project
originSessionId: 4657cfc7-6fae-499b-a8f3-9118720952d7
---
**BE starten (Port 3000, serviert FE-Statics aus `dist/apps/plato-customer-fe`):**
```bash
OIDC_CLIENT_SECRET='62glD9SMpaa8JAtQqb4055aYGV6KQStE5okhe9lAeDdtah6BcBIDE479mBCucxUW' SESSION_SECRET='dev-secret-local-only' npx nx serve plato-customer-be
```
Bequemer: `./scripts/dev-be.sh` (lokales, gitignoriertes Skript mit beiden Secrets + `nx serve`).
**FE neu bauen (nach jeder FE-Änderung notwendig, weil kein Vite-Dev-Server mehr läuft):**
```bash
npx nx build plato-customer-fe
```
**Why:** Nach BFF-Umbau läuft kein Vite-Dev-Server mehr (kein HMR). BE serviert die gebauten FE-Statics. Marcus hat das explizit so akzeptiert — "ich muss dann lokal jede Änderung mit build publizieren". OIDC-Client ist confidential → `OIDC_CLIENT_SECRET` ist zwingend erforderlich, sonst crasht BE beim Boot mit klarer Meldung. `SESSION_SECRET` ist seit dem Session-Store-Umbau EBENFALLS Pflicht (`main.ts:38` wirft hart, kein Default mehr) — ohne crasht der Boot noch vor dem OIDC-Check.
**How to apply:** Standard-Workflow nach jeder Code-Änderung am FE: build → Browser neu laden. BE läuft per nx-Watch und startet sich bei BE-Änderungen selbst neu (außer Port hängt — dann `lsof -ti:3000 | xargs kill`). Browser-Einstiegspunkt: `http://localhost:3000`. Siehe [[project-idp-witglobal]] für die OIDC-Architektur und [[project-cleanup-timeline]] für den Gesamtstand.
+28
View File
@@ -0,0 +1,28 @@
---
name: project-sap-mock-fixtures
description: SAP-Abgleich-Testfixtures + symmetrische Mail-Rettung — FERTIG inkl. E2E (Stand 2026-06-02).
metadata:
node_type: memory
type: project
originSessionId: ff64bed0-7915-4ef2-bc0d-c7c4af6a44ac
---
**Erledigt 2026-06-02** — Branch `test/migration-sap-fixtures` (3 Commits: a92ba63 Rehydrator-Spec-Nachzug zu 0aab04e, 7bd0727 Fixtures+Rescue, d2b9475 E2E). Alle 61 Tests in plato-customer-be grün.
**Was gebaut wurde:**
- `data/master-20.json` — 20 saubere Originale aus `data/customer.db` (echte Namen/Mails/craft_seller), erzeugt mit `scripts/build-master.mjs` (skalierbar: `node scripts/build-master.mjs 500`).
- `scripts/build-fixtures.mjs``data/fixtures/migration-source.sqlite` (20, Legacy-Schema, Fall-Marker in `opt1` ausgeschrieben) + `sap-master.sqlite` (10 SAP-Matches) + `expectations.json` (Test-Vertrag je customer_number).
- Aufteilung der 10 SAP-Seite: 2 abw. E-Mail (Fall A/B), 2 abw. Adresse, 1 beides, 2 Stammdaten-Korrektur, 3 identisch. Die anderen 10 sind reine Migrationssätze (origin=PlaTo). Match läuft NUR über `customer_number` → echte Nummer bleibt, Testfall steckt in `opt1` (nicht in der Nummer!).
**Logik-Änderung (Wegwerf-Code) `sap-adapter.ts overwriteWithSap`:** verdrängte Kunden-Mail wird als Kontakt gerettet; liegt sie schon an einem Kontakt → stattdessen die SAP-Mail retten (`origin='SAP'`). So überleben beide Mails immer als Kontakt. (Marcus' Wunsch: Trigger am Kunden, Kontakt-Check sinnvoll, dann SAP-Mail restaurieren.)
**Zwei Knackpunkte (nicht offensichtlich):**
- SAP-Query liefert `verband_nummer = customer_number` IMMER mit + baut craft_seller aus der kv neu → sonst ist jeder Match fälschlich `changed`. Quelle setzt für die 10 deshalb `verband_nummer = customer_number` und craft_seller = Original (round-trippt).
- E2E-Seam: `getFromSap` mocken, dahinter `SapMockService.buildCustomerXml` + `sapXmlToCustomer` (jetzt exportiert), `SAP_MOCK_DB`-Env auf die Fixture-DB. So läuft SAP echt durch, nur HTTP raus. Test: `apps/plato-customer-be/src/migration/sap-fixtures.e2e.spec.ts`.
**Hinweis:** Fixtures enthalten echte Kunden-/Außendienst-PII, liegen dauerhaft in der Historie (war so gewollt). Nächste Prio bleibt Sync-Tests/Refactoring, siehe [[project-naechste-schritte]].
**Update 2026-06-08:**
- `test/migration-sap-fixtures` ist Vorfahr von `development` → Fixtures + E2E + Lift liegen alle auf `development` (dort fixen, nicht auf dem alten Branch).
- **mobile-Gap gefixt** (commit `8001207` auf `development`): `liftSingleContactPhoneToMobile` (Wegwerf, util `contact-phone.util.ts`, kam in `8e98c10` NACH den Fixtures) hebt die einzige gepflegte Kontaktnummer auf `customer.mobile` — der E2E prüfte das Feld nicht. Jetzt `expectedMobile` je importiertem Satz in `expectations.json` (7 geliftet, 10 leer) + Assertion. Test grün (26 in der spec, 80 gesamt).
- **DEV-Mock = fixer Stamm (dauerhaft, NICHT im Repo):** Live gegen DEV verifiziert (Browser-Import → echte Postgres, alle Erwartungen grün: 17/10-SAP/7-PlaTo/20/20/3). Dafür das geteilte `backend`-Deployment (ns `1401-plato-development`) so umgebaut und **bewusst stehen gelassen** (Marcus: „fixer bekannter Stamm"): ConfigMap `sap-mock-master` (= `sap-master.sqlite`, 40 KB) als Volume gemountet unter `/app/apps/sap-mock/sap-master.sqlite` + env `SAP_MOCK_DB` darauf. `SAP_MOCK_ENABLED=true` ohnehin gesetzt. Die Image-`crm.sqlite` (30 MB generisch) bleibt daneben ungenutzt. Image war `backend:0.28.2`.
+27
View File
@@ -0,0 +1,27 @@
---
name: project-sync-testing
description: Sync testen — Integrationssuite (Logikschleife, seit 2026-06-10) + Unit-Specs + manuelles node-Rig gegen den echten BE.
metadata:
node_type: memory
type: project
originSessionId: ff64bed0-7915-4ef2-bc0d-c7c4af6a44ac
---
Zum Testen von Sync-Änderungen (Outbox/Inflight/badBox) gibt es zwei eingespielte Wege:
**Weg 1 — Logik isoliert (Arbeitspferd, kein Auth/Build):**
```bash
npx nx test frontend-common --watch # Unit-Specs + sync-loop.integration.spec.ts
npx nx test command-be # BE-seitiges handle/sync
```
Seit 2026-06-10 gehört dazu die **Integrationssuite** `libs/common/frontend-common/src/lib/integration/sync-loop.integration.spec.ts`: echte FE-Singletons (Outbox/BackendSync/PersistentStorage/storage-core) gegen echten CommandService+CustomerCoreService auf In-Memory-better-sqlite3; HTTP-Grenze als Mock-Brücke mit doppeltem JSON-Roundtrip (handle() mutiert payload). 9 Szenarien: Happy-Path, SAP-FAILED-Rollback, Delete-Kaskade, SAP-Delete-Block, Offline-Flush, Transient-Retry, Batch-Partial-Failure, Crash-Recovery (2 Varianten). GEFIXT am 2026-06-10: Fehler-Taxonomie (TRANSIENT_CODES + 5xx in background-sync, Netz/5xx wirft der Web-Client roh weiter, deterministische Fehler → neues `outbox.fail()` = sofort badBox, Queue läuft weiter) und Partial-Batch (createOrUpdateList transaktional via gemeinsamem persistCustomer(repo,…)). Verbleibende `// OFFENE FRAGE:`-Pins: QUEUED-Aufgabe ohne Rollback, kein Versionsvergleich beim Reconcile (Last-Arrival-Wins), Doppel-Finalize wirft statt idempotent. frontend-common/vite.config.ts hat dafür explizite @plato/*-Aliase bekommen. Suite gesamt: 8 Projekte / 313 Tests.
**Weg 2 — echte BE/SAP-Stichprobe von Hand** über `scripts/sync-poke.mjs` (gitignored, pure Node, läuft auch unter Windows via `node scripts/sync-poke.mjs ...`):
```bash
node scripts/sync-poke.mjs customer.update '{"id":"cust-123","name":"Test"}' --cookie 'connect.sid=s%3A...'
node scripts/sync-poke.mjs --sync-all --cookie 'connect.sid=s%3A...'
```
**Why:** Die Routen `/commands`, `/commands/sync`, `/commands/sync/all` (`command.controller.ts`) hängen alle an `SessionAuthGuard`, der hart `req.session.user` verlangt — KEIN Dev-Bypass. Deshalb braucht jeder Hand-Test gegen den BE eine echte express-session: einmal im Browser einloggen, dann `connect.sid` aus den DevTools (Application → Cookies → http://localhost:3000) holen. `oidc-test.mjs` taugt NICHT als Cookie-Quelle — es redet direkt mit dem IdP, erzeugt nur Tokens (keine BE-Session) und belegt selbst Port 3000.
**How to apply:** Weg 1 als TDD-Schleife für die dokumentierten badBox/Inflight-Lücken (siehe [[project-naechste-schritte]]). Weg 2 als Stichprobe, wenn du den realen Reconcile + SAP-Enrichment sehen willst — taugt auch um `WARNING`/`QUEUED`-Antworten gegen den echten BE zu provozieren. Cookie geht per `--cookie` (plattformneutral) oder `PLATO_SESSION`-Env (PowerShell: `$env:PLATO_SESSION='...'`). Bei `401` ist das Cookie abgelaufen → neu einloggen. Detail für `--sync-all`: Body muss `{}` sein, nicht leer (`express.json()` lehnt `"null"` mit 400 ab, vermerkt in `plato-web-client.ts`). BE-Start siehe [[project-run-commands]].
+22
View File
@@ -0,0 +1,22 @@
---
name: project_tauri_health_grau
description: "Offener Befund — Online-Indikator in der Tauri-App bleibt grau gegen DEV (eigenes Thema, geparkt)"
metadata:
node_type: memory
type: project
originSessionId: 94c6e3e8-5afc-4c31-9d3c-d5f5bcd6c872
---
Tauri-Desktop: Online-Indikator bleibt grau gegen DEV, obwohl Browser (same-origin) gegen denselben BE funktioniert. **Eigenes Thema, derzeit bewusst KEIN Fix** (Stand 2026-06-08) — Fokus liegt auf Produktivnahme.
Mechanik: Indikator = `GET {BE_URL}/health` erreichbar (nicht VPN/`navigator.onLine`), Heartbeat 3s Timeout, offline alle 4s. Quelle `libs/common/frontend-common/src/lib/be-sync/connectivity.ts`.
Warum Tauri anders als Browser: Webview lädt von eigenem URI-Scheme `platoasset://localhost` (tauri.conf.json:14, src-tauri/src/loader.rs:26/168) → `fetch` auf `https://…ocp-dev01…/health` ist **cross-origin**. Browser ist same-origin.
Falsch verworfen: CSP ist NICHT die Ursache — `"csp": null` heißt in Tauri 2 *keine* CSP. BE-CORS ist offen (`origin: true`, main.ts:35).
Zwei offene Kandidaten (per DevTools-Konsole zu entscheiden, Debug-Build hat sie offen, loader.rs:162-165):
1. custom Scheme `platoasset` nicht als CORS-/secure-fähig registriert → WebKitGTK (Fedora) blockt cross-origin-fetch von der Origin. Konsole zeigt dann `CORS … blocked`.
2. Webview erreicht DEV-Host netzseitig nicht (Proxy/VPN-DNS). Konsole zeigt `Failed to fetch`/DNS.
Verwandt: [[project_run_commands]]
+45
View File
@@ -0,0 +1,45 @@
---
name: project_tauri_loader
description: "Tauri-Desktop-Loader (Win/Mac/Linux) — Architektur, Stand, offene Punkte (2026-06-03)"
metadata:
node_type: memory
type: project
originSessionId: c4e04d11-9b40-4c6a-9333-ce3bbf949ba2
---
**Branch `feature/tauri-loader`** (NICHT auf development; lokal committet, **Push macht Marcus**
Sandbox hat keine GitHub-Creds). Ziel: Desktop-App als fixe, einmal signierte Exe + lokal
austauschbare ("swapbare") Web-Assets, offline-fähig, Win/Mac/Linux.
**Architektur:**
- `apps/plato-customer-desktop/src-tauri` (Tauri v2): Loader serviert Assets via `platoasset://`
aus **`~/.plato/app`**, prüft Manifest-Signatur (ed25519, Public Key in `loader.rs`) + je Datei
den Hash → fail closed. Asset-Bundle (FE-Build + `manifest.json`+`manifest.sig`) signiert mit
`tools/loader/sign-assets.mjs` (Key `loader-signing-key.pem`, gitignored).
- **Daten-Root `~/.plato`** (Schwester von `app/`): `plato5/`, `sync/`, `settings/`, `customer/`.
FE-Storage im Tauri-Zweig über `store_fs.rs` (statt File-System-Access-API, die im WebView fehlt).
- **Auth (cross-origin, kein Cookie):** OIDC bleibt serverseitig; BE stellt nach Login ein
HMAC-Token (`TokenService`) aus, Guard akzeptiert `Authorization: Bearer`. Desktop-Login öffnet
`<BE>/auth/login` im Tauri-Fenster (`desktop_login.rs`), fängt nach Callback die saubere
`/`-Landung ab → `/auth/desktop` → Token. `authToken` (localStorage) + axios-Interceptor.
- **Endpoint-Picker** beim Start (DEV ocp-dev01 / localhost:3000); Token wird bei Wechsel verworfen.
- **Migration** im Desktop: `migrateIfPresent` liest `plato5/crm.sqlite` via `tauri-plugin-fs`
(Binär), lädt token-auth zum BE.
**Stand:** Login-Flow gegen localhost verifiziert (inkl. 2FA → Token → UI mit Daten). BE-Build/
FE-Build/Rust-Build grün. Migration gebaut, **GUI noch nicht getestet**.
**Lokales Setup:** Rust via rustup installiert; WebKitGTK-Deps via `scripts/setup-tauri-linux-deps.sh`
(Fedora, sudo). Build: `cargo build` in src-tauri; FE→app: `npx nx build plato-customer-fe &&
node tools/loader/sign-assets.mjs dist/apps/plato-customer-fe loader-signing-key.pem &&
cp -r dist/apps/plato-customer-fe/. ~/.plato/app/`.
**Offen:** (1) Branch pushen (Marcus). (2) Migration-GUI testen. (3) BE auf DEV deployen
(branch-deploy) — Auth-Änderung vorher reviewen, `AUTH_TOKEN_SECRET` auf DEV setzen, CORS ist
`origin:true`. (4) Exe-Verteilung via `tauri-release`-CI + Code-Signing (intern oder Azure Trusted
Signing). (5) `crm.sqlite` ggf. in .gitignore.
⚠️ **Wichtig (während der Desktop-Arbeit gelöscht):** Ein `rm -rf ~/.plato/*` hat lokale Daten
(plato5/sync/settings/customer) gelöscht — daher liegt die crm.sqlite neu in `~/.plato/plato5`.
Beim Asset-Tausch NIE den ganzen Ordner leeren, nur `app/`. Siehe [[project_test_infra]] (deleteAll
ist bewusst global) und [[project_login_loop_rootcause]].
+51
View File
@@ -0,0 +1,51 @@
---
name: project_test_infra
description: Test-Coverage-Stand + wie man Test-Targets für bisher test-lose Projekte aufsetzt
metadata:
node_type: memory
type: project
originSessionId: c4e04d11-9b40-4c6a-9333-ce3bbf949ba2
---
**Stand 2026-06-03 (Branch development): 8 Test-Projekte, 232 Tests grün.**
`nx run-many -t test`: plato-customer-be (74), frontend-common (71), customer-fe (30),
plato-customer-fe (25), shared (13), command-be (8), customer-be (6), ui-common (5).
**Bug-Jagd (3 parallele Review-Agenten, von mir am Code gegengeprüft) → 7 echte Bugs gefixt:**
- `b8e3355` xlImport2Customer: `customer_number='undefined'` (`String(x)??''` greift nie).
- `f0c4e48` outbox.peek: Command-Verlust, wenn Crash zwischen Outbox-Remove und Inflight-Write
(Reihenfolge gedreht: erst Inflight, recoverInflight dedupt per id).
- `7dd3dc0` dedupeByCustomerNumber: doppelte Importnummer lautlos verloren (jetzt erster gewinnt + warn).
- `fc9cc22` setSort (Vergleich nach Zuweisung), NumberInput (v-model.number), setEditing (Karten-Wechsel).
- `042b9b3` connectivity: Heartbeat re-armte trotz stop() (Race), Generation-Token.
- `012a243` reconcile gehärtet: Form per `Array.isArray` statt `.length` (leeres Array galt als
Einzelobjekt; null/undefined Payload/forcedState crashte nicht mehr), delete.list rollt ganze
Liste zurück, reconcile-Fehler wird über den Notifier gemeldet statt still geloggt.
Verworfen als kein-Live-Bug: QUEUED→badBox (by design), selectedId-Highlight (visuell).
**NICHT als Bug melden:** `CustomerCoreService.deleteAll` löscht bewusst die GLOBALE customer-Tabelle
ohne Länderfilter — reine Test-/Reset-Funktion, fliegt vor Prod raus (Marcus, 2026-06-03).
DEV-SAP-Mock (`.get()` auf Kreuzprodukt-Join wählt willkürlichen Kontakt/Adresse): **bewusst nicht
gefixt** — SAP-OData-Schema trägt eh nur EINEN Partner/Adresse, und eine Query-Änderung würde die
26 gepinnten E2E-Fixtures riskieren.
**Diese Session per TDD abgedeckt** (vorher Lücken): Sync-Kern (outbox, background-sync,
backoff, **reconcile**=Offline-first-Konvergenz inkl. Rollback, **connectivity**=Heartbeat),
customer-fe Store + FE-`sap-adapter` (live SAP-Parsing), und die SW-Routing-Policy.
**Muster: Test-Target für ein Projekt ohne Tests nachrüsten** (war bei customer-fe und der
App plato-customer-fe nötig — `nx run-many` ließ sie sonst aus):
- **Lib** (z. B. customer-fe): `test`-Block in die vorhandene `vite.config.ts`
(`{ watch:false, globals:true, environment:'node', include:['src/**/*.{test,spec}.{ts,tsx}'], passWithNoTests:true }`)
+ `"test": { "executor": "@nx/vite:test" }` ins `project.json`.
- **App mit VitePWA/vue** (plato-customer-fe): NICHT die Haupt-`vite.config.ts` nehmen (PWA/vue-
Plugins stören den Testlauf). Eigene `vitest.config.ts` ohne diese Plugins (nur `nxViteTsPaths()`),
und im Test-Target `"options": { "configFile": "apps/plato-customer-fe/vitest.config.ts" }`.
- jsdom je Datei via `// @vitest-environment jsdom` (z. B. DOMParser im sap-adapter, window/fetch
in connectivity). Cross-Lib-Wertimporte (`@plato/...`) im Vitest-Modus mocken, sonst lädt das Modul nicht.
**SW-Login-Loop festgenagelt:** Routing-Regeln aus `sw.ts` in pures `sw-routing.ts` ausgelagert
(`isNetworkOnly`/`isOidcCallback`/`NAV_DENYLIST`) und getestet. Siehe [[project_pwa_sw_auth_bypass]].
**Test-lose Projekte:** alle relevanten haben jetzt ein Test-Target. `libs/shared` per eigener
`vitest.config.ts` + `configFile`-Option (gleiches Muster wie die App). Keine bewusst offene
Coverage-Lücke mehr in den verzweigten/kritischen Pfaden.
+56
View File
@@ -0,0 +1,56 @@
---
name: sap-config
description: "Echter SAP-Zugriff — Basic Auth (NICHT Kerberos), Endpoint/Key-Predicate-URL, Proxy-Pflicht, Env→Secret/Overlay-Mapping"
metadata:
node_type: memory
type: project
originSessionId: fc0d1c3f-3797-4d5c-9282-bf72eb45dd51
---
Echte SAP-Anbindung (Mock-Ablösung). Auth ist **Basic Auth**, nicht Kerberos/SPNEGO — die alte Annahme war falsch (Korrektur 2026-06-17). Verifiziert mit erfolgreichem Call:
```
curl -sv -u 'sy08804394:<pw>' \
"https://odatang9.wgn.wuerth.com/sap/opu/odata/WUE/sd_plato5_srv/CustomerSet(CustomerID='1205619',Salesorg='0001')" \
-H "Accept: application/xml"
```
**Endpoint / Parameter:**
- Base: `https://odatang9.wgn.wuerth.com/sap/opu/odata/WUE/sd_plato5_srv`
- Entity: `CustomerSet(CustomerID='<id>',Salesorg='<org>')` — OData **Key-Predicate**-Syntax (Hochkommata, ggf. URL-encodet `%27`). NICHT `?CustomerId=…&Salesorg=…` (das ist die alte Mock-Query).
- Salesorg im DEV-Test: `0001`.
- Header: `Accept: application/xml`. Antwort = XML mit `d:`-Tags (Parser `sapXmlToCustomer`).
- **Netz: nur über Proxy** `http://proxy.wgs.wuerth.com:3128`. Aus dem Cluster ist `odatang9:443` direkt = Timeout.
**Technischer User:** `sy08804394` (+ Passwort). Beides gehört in ein Secret, nicht in Env-Literale.
**Env → Secret/Overlay-Mapping (ops-Repo `fahrzeugeinrichtung-plato-gitops`):**
- Deployment `backend` (ns `1401-plato-development`) zieht Secrets per `envFrom` aus `plato-customer-secrets` (+ `plato-postgres-secrets`). Aktuelle Keys: nur `OIDC_CLIENT_SECRET`, `SESSION_SECRET`.
- Nicht-geheime Env stehen im Overlay-Patch `overlays/environments/development/backend.yaml` (Proxy, `SAP_MOCK_ENABLED`, OIDC_*). Dort sind `SAP_URL`/`BE_URL` bereits **auskommentiert** vorgesehen.
- Secrets sind **SealedSecrets**: `plato-customer-secrets.sealed.yaml`. Neuer Wert → Klartext-Secret nach Vorlage `secret.example.template.yaml`, dann `kubeseal -o yaml -f <klartext>.yaml > <sealed>.yaml` (im richtigen Cluster eingeloggt!), in `kustomization.yaml` als resource referenziert.
**Aktivierung umgesetzt 2026-06-17 (committed, NICHT gepusht):**
- Adapter `apps/plato-customer-be/src/migration/util/sap-adapter.ts` gepatcht (backend `development`, commit `e33cc32`): bei gesetztem `SAP_URL` → Key-Predicate-URL `CustomerSet(CustomerID='…',Salesorg='0001')` + `Authorization: Basic` aus `SAP_USER`/`SAP_PASSWORD`, raus über den globalen undici-ProxyAgent (kein eigener Dispatcher). Ohne `SAP_URL` unverändert Mock-Pfad (alte Query, `directDispatcher`, kein Auth). Mock-E2E grün.
- Eigenes Secret `plato-sap-secrets` (Keys `SAP_USER`/`SAP_PASSWORD`) als SealedSecret, ns `1401-plato-development`. Template `secret.sap.template.yaml` hat NUR Platzhalter — echtes PW steht ausschließlich verschlüsselt in `plato-sap-secrets.sealed.yaml` (nie im Klartext in Git).
- gitops `main`, commit `4c0b8cd`: Sealed Secret als resource in `kustomization.yaml`, `backend.yaml`-Overlay um `envFrom plato-sap-secrets` + `SAP_URL=…/sd_plato5_srv` erweitert, `SAP_MOCK_ENABLED=false`.
**Push & Deploy gelaufen:** backend `development` gepusht → CI baute `backend:0.38.1`, ArgoCD ausgerollt; DEV-Pod oben mit `SAP_URL` + `mock=false`, Secret `plato-sap-secrets` aktiv. Boot sauber (SAP wird erst beim Import gerufen).
**SAP-Rechte fehlen (2026-06-17):** Der technische User `sy08804394` hat KEINE Rechte auf dem Endpoint → echter Import läuft nicht. Technisch fährt aber alles hoch.
**Update 2026-06-23 — Rechte jetzt da, Call grün:** Aus dem DEV-Pod (`backend-...`, ns `1401-plato-development`) liefert derselbe User `sy08804394` **HTTP 200** mit gültigem OData-XML (`CustomerID='1205619',Salesorg='0001'`). Damit ist der Parser-Erwartungswert bestätigt: Antwort ist XML mit `d:`-Tags (`d:Salesorg`, `d:CustomerID`, `d:SalesrepNumber` …) → `sapXmlToCustomer` passt. Test-Rig im Pod: kein curl vorhanden → über `node` + undici `request` (NICHT globalen fetch — der kollidiert mit der node_modules-undici-Version: „invalid onRequestStart method") mit `ProxyAgent(HTTPS_PROXY)` und Basic-Auth-Header.
**PROD ebenfalls grün (2026-06-23):** Derselbe Test aus dem PROD-Pod (ns `1401-plato-production`) liefert auch HTTP 200 + identisches XML. SAP-URL + Creds sind in DEV- und PROD-Overlay identisch → beide Umgebungen erreichen SAP gleichermaßen.
**Mock vollständig aus Runtime/Prod entfernt (2026-06-17):**
- backend `development` commit `30591b8`: `apps/sap-mock/` (standalone), `sap-mock.controller.ts`, `sap-mock.module.ts` und das `app.module.ts`-Wiring gelöscht. Build grün.
- **Für Tests bewusst behalten:** `sap-mock.service.ts` + `sap-reverse-query.ts` (nutzt der E2E via `buildCustomerXml`), `sap-fixtures.e2e.spec.ts`, `data/fixtures/`, `scripts/build-*.mjs`. Werden vom App-Code nicht mehr importiert → fliegen aus dem Prod-Bundle.
- gitops `main` commit `f12f6d9`: `SAP_MOCK_ENABLED` aus dev+prod-Overlay.
- DEV-Cluster direkt bereinigt (war manuell, nicht in Git): ConfigMap `sap-mock-master`, Volume/Mount, `SAP_MOCK_DB`-Env entfernt.
- Beide Commits noch NICHT gepusht.
Parser `sapXmlToCustomer` (erwartet `d:`-Tags) bleibt bis zum ersten echten Body unbestätigt — wegen fehlender Rechte noch nicht verifizierbar.
**OFFEN (morgen, Marcus aktiv ansprechen):** Der für Tests behaltene Mock-Rest (`sap-mock.service.ts`, `sap-reverse-query.ts`, Fixtures-Generatoren) soll RAUS — Mock im Repo war nur Notlösung, Behalten verstößt gegen die Etikette. Entscheidung treffen: **A** alles inkl. SAP-Migrations-E2E löschen (kein SAP-Test mehr); **B** Mock-Code raus, `sapXmlToCustomer` gegen ein paar statische, aufgezeichnete SAP-XML-Fixtures testen (schlanker Parser-Test bleibt). Marcus tendiert klar zu „raus".
**Hinweis Live-State:** Der DEV-Pod hat zusätzlich `SAP_MOCK_DB` + ConfigMap-Mount `sap-mock-master` — das wurde manuell gepatcht und steht NICHT im Overlay-`backend.yaml`. Siehe [[project-sap-mock-fixtures]] (Mock/Fixtures bleiben gültig).
+20
View File
@@ -0,0 +1,20 @@
---
name: user-role
description: "Marcus Hinz — adesso-Consultant beim Würth-Konzern auf dem plato-customer-Projekt (Backend/Frontend), Tech-Lead-Tempo, direkter Kommunikationsstil."
metadata:
node_type: memory
type: user
originSessionId: 9259f85f-e34d-4327-89e2-534bbfbc48c2
---
Marcus arbeitet als adesso-Berater im Würth-Umfeld am Projekt "plato-customer" (NestJS-Backend + Vue/Vite-Frontend, Nx-Monorepo). Login-Identitäten:
- Würth/WITGLOBAL: `ex08802409` (Externer-Präfix), Mail `marcus.hinz@adesso.de`
- privat (Claude-Account): `marcus.fj.hinz@gmail.com`
**Arbeitsweise / Erwartungshaltung:**
- Pragmatisch und tempogetrieben — Priorität ist "Kollegen können FRÜH testen", nicht technische Vollständigkeit (siehe [[feedback-priorisierung]]).
- Erwartet direkte Antworten ohne Floskeln, kurzes Ack reicht.
- Hat Erfahrung mit Enterprise-Setups (PingFederate, Würth-IdP, Gitflow CI/CD) und kennt die Tradeoffs (z.B. bewusst Base64 statt AES als Zwischenschritt, weil "Laien-Schutz" reicht und echte Crypto später kommt).
- Macht selbst Architektur-Entscheidungen, will von mir Optionen + Tradeoffs, nicht fertige Pläne.
**Sprache:** Kommunikation auf Deutsch. Code-Kommentare gemischt DE/EN (siehe Repo). Commit-Messages mal DE, mal EN — kein strenges Schema.