commit c8f977d6170aef4aeb77429733594f894c033b1c Author: marcus.hinz Date: Thu Jul 9 09:21:42 2026 +0200 Initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..d0dd81a Binary files /dev/null and b/.DS_Store differ diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..dc744e9 --- /dev/null +++ b/.claude/settings.json @@ -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/**)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c82ae92 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.ai-control-running diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f23051a --- /dev/null +++ b/CLAUDE.md @@ -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`. diff --git a/ai-control.json b/ai-control.json new file mode 100644 index 0000000..739e692 --- /dev/null +++ b/ai-control.json @@ -0,0 +1,6 @@ +{ + "pool": "private", + "terminal": { + "icon": "wuerth-plato.png" + } +} diff --git a/memory/MEMORY.md b/memory/MEMORY.md new file mode 100644 index 0000000..3d1cb77 --- /dev/null +++ b/memory/MEMORY.md @@ -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 diff --git a/memory/feedback_copy_not_share.md b/memory/feedback_copy_not_share.md new file mode 100644 index 0000000..5a01602 --- /dev/null +++ b/memory/feedback_copy_not_share.md @@ -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]]. diff --git a/memory/feedback_dedizierte_skripte.md b/memory/feedback_dedizierte_skripte.md new file mode 100644 index 0000000..d7f1a3d --- /dev/null +++ b/memory/feedback_dedizierte_skripte.md @@ -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]]. diff --git a/memory/feedback_keine_zwischenstufen.md b/memory/feedback_keine_zwischenstufen.md new file mode 100644 index 0000000..723517e --- /dev/null +++ b/memory/feedback_keine_zwischenstufen.md @@ -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 = 1–5 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). diff --git a/memory/feedback_klare_benennung.md b/memory/feedback_klare_benennung.md new file mode 100644 index 0000000..f14d6bc --- /dev/null +++ b/memory/feedback_klare_benennung.md @@ -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]]. diff --git a/memory/feedback_no_ip_configs.md b/memory/feedback_no_ip_configs.md new file mode 100644 index 0000000..c6e57b8 --- /dev/null +++ b/memory/feedback_no_ip_configs.md @@ -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]]. diff --git a/memory/feedback_no_native_alerts.md b/memory/feedback_no_native_alerts.md new file mode 100644 index 0000000..31404c4 --- /dev/null +++ b/memory/feedback_no_native_alerts.md @@ -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]]. diff --git a/memory/feedback_ort_zuerst.md b/memory/feedback_ort_zuerst.md new file mode 100644 index 0000000..57e108a --- /dev/null +++ b/memory/feedback_ort_zuerst.md @@ -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]]. diff --git a/memory/feedback_priorisierung.md b/memory/feedback_priorisierung.md new file mode 100644 index 0000000..cee0e81 --- /dev/null +++ b/memory/feedback_priorisierung.md @@ -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". diff --git a/memory/feedback_push_macht_user.md b/memory/feedback_push_macht_user.md new file mode 100644 index 0000000..d17ca70 --- /dev/null +++ b/memory/feedback_push_macht_user.md @@ -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 , Push machst du" und stoppen. Siehe [[feedback_self_service_only]], [[feedback_plan_first]]. diff --git a/memory/feedback_self_service_only.md b/memory/feedback_self_service_only.md new file mode 100644 index 0000000..72bf7cc --- /dev/null +++ b/memory/feedback_self_service_only.md @@ -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. diff --git a/memory/project_bestaetigen_bug.md b/memory/project_bestaetigen_bug.md new file mode 100644 index 0000000..623ae13 --- /dev/null +++ b/memory/project_bestaetigen_bug.md @@ -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]]. diff --git a/memory/project_cleanup_timeline.md b/memory/project_cleanup_timeline.md new file mode 100644 index 0000000..85fef41 --- /dev/null +++ b/memory/project_cleanup_timeline.md @@ -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` 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. diff --git a/memory/project_dummy_jwt_secret.md b/memory/project_dummy_jwt_secret.md new file mode 100644 index 0000000..9831b54 --- /dev/null +++ b/memory/project_dummy_jwt_secret.md @@ -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. diff --git a/memory/project_fe_design_system.md b/memory/project_fe_design_system.md new file mode 100644 index 0000000..e0ecae0 --- /dev/null +++ b/memory/project_fe_design_system.md @@ -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). diff --git a/memory/project_gitops_oidc_werte.md b/memory/project_gitops_oidc_werte.md new file mode 100644 index 0000000..755423c --- /dev/null +++ b/memory/project_gitops_oidc_werte.md @@ -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//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]]. diff --git a/memory/project_idp_witglobal.md b/memory/project_idp_witglobal.md new file mode 100644 index 0000000..f2b94cd --- /dev/null +++ b/memory/project_idp_witglobal.md @@ -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. diff --git a/memory/project_kontakt_adress_spec.md b/memory/project_kontakt_adress_spec.md new file mode 100644 index 0000000..d2e82d8 --- /dev/null +++ b/memory/project_kontakt_adress_spec.md @@ -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]]. diff --git a/memory/project_loader_signing_key.md b/memory/project_loader_signing_key.md new file mode 100644 index 0000000..de800b4 --- /dev/null +++ b/memory/project_loader_signing_key.md @@ -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). diff --git a/memory/project_logging.md b/memory/project_logging.md new file mode 100644 index 0000000..14ebae1 --- /dev/null +++ b/memory/project_logging.md @@ -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. diff --git a/memory/project_login_loop_rootcause.md b/memory/project_login_loop_rootcause.md new file mode 100644 index 0000000..dac57f6 --- /dev/null +++ b/memory/project_login_loop_rootcause.md @@ -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]]. diff --git a/memory/project_montag_offen.md b/memory/project_montag_offen.md new file mode 100644 index 0000000..4d0f076 --- /dev/null +++ b/memory/project_montag_offen.md @@ -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]]. diff --git a/memory/project_naechste_schritte.md b/memory/project_naechste_schritte.md new file mode 100644 index 0000000..114e4ac --- /dev/null +++ b/memory/project_naechste_schritte.md @@ -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. diff --git a/memory/project_optimistic_locking.md b/memory/project_optimistic_locking.md new file mode 100644 index 0000000..e13c9ad --- /dev/null +++ b/memory/project_optimistic_locking.md @@ -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`. diff --git a/memory/project_prod_ist_stand.md b/memory/project_prod_ist_stand.md new file mode 100644 index 0000000..9837bc9 --- /dev/null +++ b/memory/project_prod_ist_stand.md @@ -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= 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). diff --git a/memory/project_pwa_sw_auth_bypass.md b/memory/project_pwa_sw_auth_bypass.md new file mode 100644 index 0000000..99470db --- /dev/null +++ b/memory/project_pwa_sw_auth_bypass.md @@ -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]]. diff --git a/memory/project_run_commands.md b/memory/project_run_commands.md new file mode 100644 index 0000000..45c5cad --- /dev/null +++ b/memory/project_run_commands.md @@ -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. diff --git a/memory/project_sap_mock_fixtures.md b/memory/project_sap_mock_fixtures.md new file mode 100644 index 0000000..39261cb --- /dev/null +++ b/memory/project_sap_mock_fixtures.md @@ -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`. diff --git a/memory/project_sync_testing.md b/memory/project_sync_testing.md new file mode 100644 index 0000000..dc326ef --- /dev/null +++ b/memory/project_sync_testing.md @@ -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]]. diff --git a/memory/project_tauri_health_grau.md b/memory/project_tauri_health_grau.md new file mode 100644 index 0000000..531bb91 --- /dev/null +++ b/memory/project_tauri_health_grau.md @@ -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]] diff --git a/memory/project_tauri_loader.md b/memory/project_tauri_loader.md new file mode 100644 index 0000000..1fa72b3 --- /dev/null +++ b/memory/project_tauri_loader.md @@ -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 + `/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]]. diff --git a/memory/project_test_infra.md b/memory/project_test_infra.md new file mode 100644 index 0000000..0c8d0ef --- /dev/null +++ b/memory/project_test_infra.md @@ -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. diff --git a/memory/sap-config.md b/memory/sap-config.md new file mode 100644 index 0000000..eb4b7b3 --- /dev/null +++ b/memory/sap-config.md @@ -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:' \ + "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='',Salesorg='')` — 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 .yaml > .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). diff --git a/memory/user_role.md b/memory/user_role.md new file mode 100644 index 0000000..09f6680 --- /dev/null +++ b/memory/user_role.md @@ -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.