TODO — Kora Platform¶
Technische Schulden, Deferred-Items aus Code-Reviews, und Integration-Learnings. Diese Liste ist Teil der Platform-Dokumentation unter docs.kora.luki-net.org.
Konventionen¶
- Severity: High (≥ 80) / Medium (50–79) / Low (< 50). Scores folgen dem Review-Report-Template.
- Quelle: Aus welchem Review/Block stammt das Item.
- Status: Open / In Progress / Done. Abgeschlossene Items wandern in den Archiv-Abschnitt am Ende dieses Dokuments mit Commit-Hash + Datum (Traceability). Alte Archiv-Einträge werden nach ~3 Monaten oder einem Major-Release zusammengefasst.
Offene Technische Schulden¶
Block-2-/Block-3-Todos (B3-03, B3-04, B2-01, B2-03, B2-05, B2-07) sind in
Cleanup-02 und TODO-B2-03 (platform/b2-03-service-account) abgeschlossen
worden — siehe Archiv am Ende.
Die Einträge aus Prometheus-Scrape / Dev-Infrastruktur (TODO-Cleanup03-01, -02) sind inzwischen abgeschlossen — siehe Archiv.
Aus Block 19 Phase 4 (2026-05-03, Branch feature/block-19-bge-m3-embedder)¶
TODO-Block-19-Phase-4 — Production-Cutover ✅ Erledigt 2026-05-03¶
- Severity: High (Score 80)
- Status: ✅ Erledigt 2026-05-03 mit Cutover-Commit (siehe Changelog
[Unreleased]→ Phase 4) - Snapshot-Recovery-Point:
backups/qdrant/avs_handbuecher_pre_block19_cutover_20260503-1150.snapshot(4.2 MB, 30+ Tage Aufbewahrung) - Aktivierte Defaults:
use_hybrid_retrieval=True,qdrant_collection=avs_handbuecher_bge_m3insrc/avs_chatbot/config.py..envsynchronisiert (idempotenter Switch). - Cutover-Verifikation:
make smoke8/8 grün,make smoke-hybridgrün, frische Live-Cache-bypass-Query (cached=false, 5 sources), 4 weitere Live-Queries fehlerfrei.
TODO-Block-19-Observation-Window — 7-Tage-Beobachtung ✅ Erledigt 2026-05-10¶
- Severity: Medium (Score 60) — Production-Confidence-Building
- Trigger: Phase-4-Cutover ist live. 7-Tage-Window-Start: 2026-05-03.
- Status: ✅ Tag 7 grün abgeschlossen 2026-05-10. R@5 0.96 (+0.04 vs Block 18), MRR 0.74 (−0.0267 dokumentierter Reranker-Bias q006/q025), avg_latency 140ms (−15ms). Live-Stack: API healthy 7d, smoke-hybrid grün, 0 Query-Errors. Snapshot bleibt 30d in
backups/qdrant/(defense in depth bis 2026-06-02)..env.block19_backupanalog behalten bis Cleanup-Welle ~2026-06-02. - Cycle:
- Tag 0 (2026-05-03): Cutover, Initial-Smoke, Live-Verify ✅
- Tag 1 (2026-05-04): 24h-Snapshot der Metriken — p50/p95-Latenz, Error-Rate, No-Match-Rate gegen Tag-0-Baseline
- Tag 3 (2026-05-06): Mid-Window-Review — bei Drift Diskussion ob weitermachen oder rollback
- Tag 7 (2026-05-10): End-Window-Review ✅ grün:
- Phase 5 (Edge-Case-Queries q026–q035) als nächste Roadmap-Karte freigegeben
- Alte Collection
avs_handbuecherCleanup als separates Followup-Item - Snapshot in
backups/qdrant/für 30 Tage behalten (defense in depth)
- Grafana-Panel-Tasks (innerhalb Window aufzubauen):
-
avs_query_duration_secondsp50/p95/p99 — bestehender Panel -
avs_no_retrieval_match_total-Rate — bestehender Panel - NEU
avs_rag_stage_duration_seconds{stage="embedding"}— BGE-M3 vs e5-large-Baseline (~20 ms) - NEU
avs_rag_stage_duration_seconds{stage="retrieval"}— Hybrid (Dense + Sparse parallel) - Alarm: p95-Latenz > 500 ms (Block-18 ~155 ms, Block-19 ~140 ms — Headroom für Spikes)
- Alarm:
no_retrieval_match_total-Rate > 5%/h - Wöchentlicher manueller
make eval-hybrid(Makefile-Target ist Block-19.5/Block-20-Vorbereitung) - Rollback-Pfad (bei Drift):
cp .env.block19_backup .env && make redeploy. Snapshot-Restore bei tieferem Issue.config.py-Defaults auf Hybrid bleiben — bei Rollback override.envdie Defaults. - Aufwand: ~30 min Setup + asynchrone Beobachtung
- Quelle: Block 19 Phase 4 Cutover, 2026-05-03
- Status: Open
TODO-Block-19-Cleanup — Alte Collection avs_handbuecher löschen¶
- Severity: Low (Score 30) — Storage-Hygiene
- Trigger: Nach Tag-7 grüner Observation (~2026-05-10). Snapshot in
backups/qdrant/behält Recovery-Möglichkeit. - Vorschlag:
- Aufwand: ~10 min
- Voraussetzung: Observation-Window grün abgeschlossen
- Quelle: Block 19 Phase 4 Cutover, Plan §15a.4
- Status: Blocked-bis-Tag-7
TODO-Block-19-5-Reranker-Upgrade — bge-reranker-v2-m3¶
- Severity: Medium (Score 60) — Quality-Tuning + konsistenter BGE-Stack
- Trigger: Phase-3.5-Diagnose-Outcome B' (Cross-Encoder-Reranker-Bias). q006 zeigt: TransformersSimilarityRanker (
cross-encoder/mmarco-mMiniLMv2-L12-H384-v1) bevorzugt Kurverwaltung-Chunks vor Beherbergungsbetrieb-Chunks bei duplizierten Inhalten. RRF-Weights haben dabei keine Wirkung (Sweep validated). - Vorschlag:
- SentenceTransformersSimilarityRanker mit
BAAI/bge-reranker-v2-m3(multilingual cross-encoder, konsistent zu BGE-M3-Embedder). - Phase-3-Style-Eval-Pattern wiederverwenden:
make smoke-hybrid-Skeleton + In-Process-Eval-Skript für 25 Test-Queries. - q006-Tiefen-Inspektion (analog Phase 3.5 Sub-Step 1) bestätigen: kommt MRR auf >= 0.77 zurück?
- A/B gegen Phase-3-Block-19-Eval (
eval_retrieval_block19_phase3_20260503-0545.json). - Voraussetzung: Tag-7 Observation grün, Phase-4-Cutover stabil.
- Aufwand: ~4-6h (Reranker-Wechsel + Eval-Run + Bericht)
- Quelle: Block 19 Phase 3.5 Diagnose, H4-Hypothese
- Status: Open
TODO-Block-19-Tooling — In-Process-Quality-Run-Runner formalisieren¶
- Severity: Low (Score 40) — Tooling-Schuld, nicht Production-relevant
- Problem: Phase-3-/3.5-Runs liefen mit Inline-Skripten via
docker cp+docker compose exec. Existierendestests/evaluation/evaluate_retrieval.pyist HTTP-basiert (testet Live-API), nicht in-process — undtests/-Verzeichnis ist nicht in den API-Container gemountet. - Vorschlag:
- Neuer Runner
tests/evaluation/evaluate_retrieval_inprocess.py(oder--mode in-process-Flag am bestehenden Runner) der die Pipeline direkt aufruft analogmake smoke-hybrid. - Mount-Strategie: optional
tests/als Bind-Mount indocker-compose.ymlfür In-Process-Workflows (read-only). - Neue Makefile-Targets
make eval-hybrid/make eval-legacyanalogsmoke-hybrid(Phase-4-Observation-Window-Tool). - Reusable für Block 19.5 (Reranker-Upgrade) und Block 20 (AST-Chunking-Flag).
- Aufwand: ~2h
- Quelle: Block 19 Phase 3 + Phase 3.5 Drift-Finding (Eval-Runner-CLI)
- Status: Open
TODO-Block-19-Phase-5 — Edge-Case-Queries q026–q035¶
- Severity: Low (Score 30) — Test-Set-Erweiterung
- Trigger: Phase-3-Eval validiert nur 25 bestehende Queries. Edge-Case-Queries (Paragraphen, Codes, Akronyme, deutsche Compounds wie "Kurtaxe") sind separater Phase-5-Check.
- Vorschlag: 10 neue Test-Queries (q026–q035) im Test-Set ergänzen mit Fokus auf:
- Paragraphen-Querverweise (z.B. "§ 12 Meldegesetz")
- Fehler-Codes (z.B. "Was bedeutet Fehlercode E-2031?")
- Akronyme (z.B. "Was ist die ELMA-Schnittstelle?")
- Deutsche Compounds-Edge-Cases (BGE-M3-Tokenization-Stress-Test)
- Voraussetzung: Tag-7 Observation grün.
- Aufwand: ~2-3h (Query-Curation + Eval-Run)
- Quelle: Phase-3-Akzeptanzkriterien, q026–q035 ausserhalb Phase-3-Scope
- Status: Open
TODO-Block-19-Reconciliation — Konzept-Update §6a.2 + §15¶
- Severity: Low (Score 30) — Konzept-Doku-Hygiene
- Vorschlag: Updates in
docs-kora/docs/konzepte/multitenancy-fundament.md: - §6a.2 — Reranker-Rolle dokumentieren (war in Phase-3.5-Diagnose H4 die Wurzel des MRR-Drifts, nicht RRF). Pre-Reranker-Ranking ≠ Post-Reranker-Top-K.
- §15 — Per-Subprocess-In-Process-Pattern (Demo bleibt im Legacy-Modus während Hybrid-Run via
docker compose exec -e ...+ Inline-Skript). Brücke zu §15a Parallel-Entwicklung. - §6a.2 — q006-Lesson: Mehrdeutige Gold-Labels in Test-Set bei duplizierten Inhalten über mehrere Handbücher. Empfehlung: file-level Match bleibt (Phase 3 hat das nicht broken), aber chunk-/section-level Match als optionale Erweiterung in Phase 5+.
- Aufwand: ~1h
- Quelle: Block 19 Phase 3 + 3.5 Methodik + Diagnose
- Status: Open
TODO-Block-19-Tooling — In-Process-Quality-Run-Runner formalisieren¶
- Severity: Low (Score 40) — Tooling-Schuld, nicht Production-relevant
- Problem: Phase-3-Run lief mit Inline-Skript
/tmp/phase3_eval.pyviadocker cp+docker compose exec. Existierendestests/evaluation/evaluate_retrieval.pyist HTTP-basiert (testet Live-API), nicht in-process — undtests/-Verzeichnis ist nicht in den API-Container gemountet. - Vorschlag:
- Neuer Runner
tests/evaluation/evaluate_retrieval_inprocess.py(oder--mode in-process-Flag am bestehenden Runner) der die Pipeline direkt aufruft analogmake smoke-hybrid. - Mount-Strategie: optional
tests/als Bind-Mount indocker-compose.ymlfür In-Process-Workflows (read-only). - Neue Makefile-Targets analog
smoke-hybridfür die zwei Modi. - Reusable für Block 19.5 (Reranker-Upgrade) und Block 20 (AST-Chunking-Flag).
- Aufwand: ~2h
- Quelle: Block 19 Phase 3 Drift-Finding (Eval-Runner-CLI)
- Status: Open
TODO-Block-19-Lesson — Per-Subprocess-Methodik dokumentieren¶
- Severity: Low (Score 30) — Konzept-Reconciliation
- Vorschlag: §6a.2 oder §15 in
multitenancy-fundament.mdum den Per-Subprocess-In-Process-Pattern erweitern (Demo bleibt im Legacy-Modus während Hybrid-Run, Workflow für zukünftige Block-Vergleiche 19.5 / 20). Brücke zu §15a Parallel-Entwicklung. - Aufwand: ~30 min
- Quelle: Block 19 Phase 3 Methodik
- Status: Open
Aus Block 18 (2026-05-02, Branch platform/block-18-docling-ingestion)¶
TODO-Block-18-Followup-01 — EnhancedPDFConverter-Removal (Cleanup-Welle)¶
- Severity: Low (Score 30)
- Datei(en):
src/avs_chatbot/pipelines/components/pdf_converter.py(193 LoC),tests/unit/test_pdf_converter.py(166 LoC) - Problem: Block 18 hat
DoclingPDFConverterals Default-PDF-Converter imFileTypeRouteretabliert.EnhancedPDFConverterwird nicht mehr referenziert, bleibt aber im Code als Cleanup-Welle-Restanteil. Pure-Helper-Functions (_strip_footer,_mark_hint_boxes,_rows_to_markdown) sind isoliert genug für Removal. - Vorschlag: Nach Block-18-Merge: File
pdf_converter.pylöschen, Import inindexing.py(bereits durch Block 18 entfernt) verifizieren,tests/unit/test_pdf_converter.pylöschen,pdfplumber>=0.11-Pin in pyproject.toml prüfen (wird ggf. nicht mehr gebraucht —doclingzieht eigene PDF-Backend-Deps). - Aufwand: ~2h (Code-Removal, Dependency-Cleanup, Smoke-Tests)
- Trigger: Cleanup-Welle (z.B. v1.5.0 oder vor Block 14). Nicht Block-18-Scope per Lutz-Entscheidung "Block 18 ist groß genug".
- Disziplin-Datapoint: Pre-Block-18-EnhancedPDFConverter macht silent-fallback auf PyPDFToDocument bei jedem Fehler. Block 18 etabliert Fail-Loud. Removal entfernt damit auch das letzte Stück silent-fallback-Pattern aus dem Code.
- Quelle: Block-18-Implementation (Phase 7), Discovery-Datapoint
- Status: Open
TODO-Block-18-Followup-02 — Page-Number-Fidelity im Widget ✅ Erledigt¶
- Severity: Medium (Score 60) — User-facing
- Status: ✅ Erledigt 2026-05-02 mit Block-18-Merge
814f981 - Lösung: Phase-2.5-Page-Tracking via
page_break_placeholderaus docling-core 2.30+ + numbered Sentinels (\x02AVS_PAGE_N\x02). Pro Chunk werdenpage_number/page_start/page_endaus Sentinel-Position abgeleitet, statt aus 1-Doc-per-Page-Index. Real-Eval-Verifikation: Smoke-Queries zeigen echte Page-Numbers (p.7, p.13, p.54, ...) statt alle p.1. - Resolution-Pattern: TODO im selben Block aufgelöst statt als Followup ausgelagert (Lutz-Entscheidung beim Phase-2.5-Scope-Add).
TODO-Block-18-Followup-03 — q004 Gold-Label-Re-Annotation¶
- Severity: Low (Score 30)
- Problem: Eval-Frage q004 ("Was passiert wenn ein Benutzer gesperrt
wird?") hat aktuell single-doc Gold-Label
(
Meldeschein_Handbuch_AdministrationBeherbergungsbetrieb.pdf). Block 18 retrievedRichtlinie zum sicheren Umgang mit Informationenals rank-1, Beherbergung außerhalb Top-5. Beide Sources semantisch valide — der Sicherheits-Richtlinie behandelt Benutzer-Sperrung im Sicherheitskontext, Beherbergung im operativen Meldeschein-Workflow. - Effekt: R@5 q001-q020 fällt von 95% auf 90% allein wegen q004 → Stop-Trigger 4 verfehlt. Dokumentierte Block-18-Merge-Ausnahme.
- Vorschlag: Multi-Doc-Annotation (Liste von relevant_documents pro Frage) ODER Eval-Set-Refactor mit präziseren Frage-Formulierungen, die eindeutig auf 1 Source zielen.
- Aufwand: ~1-2h (Eval-Schema-Erweiterung + Re-Annotation aller 25 Q + evaluator-Logik für any-match-Hit)
- Trigger: Nächste Eval-Set-Maintenance-Welle (vor v1.0.0-Release oder bei Block 19 BGE-M3 Re-Eval)
- Quelle: Block-18-Real-Eval Hand-off
- Status: Open
TODO-Block-18-Backup-Cleanup — Production-Backup-Snapshot¶
- Severity: Low (Score 20)
- Datei:
backups/qdrant/avs_handbuecher_pre_block18.snapshot(8.2 MB) - Problem: Backup der vor-Block-18-Production-Collection liegt nach Production-Switch noch auf disk. Rollback-Pfad falls Quality-Problem in ersten 1-2 Wochen.
- Vorschlag: Nach 1-2 Wochen ohne Rollback-Notwendigkeit löschen:
rm /opt/avs-chatbot/backups/qdrant/avs_handbuecher_pre_block18.snapshot - Aufwand: ~5min
- Trigger: 2026-05-16 (zwei Wochen nach Production-Switch)
- Status: Open
TODO-Konzept-02 — Pattern-Reife-Quote-Trendlinie in §17.2 einarbeiten¶
- Severity: Low (~30–45 min, Mini-Run nach v1.3.0-Tag)
- Datei:
docs-kora/docs/konzepte/multitenancy-fundament.md§17.2 - Problem: §17.2 führt einen Pauschal-Pattern-Reife-Wert (60 %). v1.3.0-Welle liefert drei differenzierende Datapoints (D2 23 %, D1 31 %, E ~50 %) plus existing Block 8 (50–80 %) und Block 11 (40 %). Diese erlauben eine kalibrierte Quote-Erwartung pro Block-Typ (Cleanup-Welle Backend, Cleanup-Welle Frontend, Foundation-Reuse hoch, Foundation-Reuse + neuer Code, Foundation-erweiternd) und damit eine belastbarere Block-13-Schätzung.
- Fix: §17.2-Tabelle um Block-Typ-Spalte plus Quote-Erwartung ergänzen. Datapoints-Liste in §17.2a (analog Reconciliation-Quelle Nr. 6 aus TODO-Konzept-01-Auflösung). Konzept-Header-Patch v5.3.3 → v5.3.4. v1.0.0-Budget bleibt unverändert (~427h) — die Kalibrierung ändert keine Aufwand-Zahlen, nur die Erwartungsfunktion.
- Pattern: TODO-Konzept-01 als Vorlage (eigener Mini-Run nach v1.2.0-Tag, ~30 min, dokumentativer Patch ohne Zahlen-Korrektur).
- Status: ✅ Erledigt 2026-05-02 — Mini-Run dieser Eintrag selbst. §17.2a Reconciliation Nr. 7 (Pattern-Reife-Quote-Trendlinie pro Block-Typ) ergänzt; §17.5 (Cleanup-Wellen außerhalb 427h-Hauptachse) als neue Sektion eingeführt; Konzept-Header v5.3.3 → v5.3.4. Merge folgt unten.
Aus Block 5 Phase E Code-Review (2026-04-22)¶
Nummerierungs-Hinweis: Die TODO-IDs
TODO-Block-5b..-5fstammen aus dem Code-Review des Plattform-Module-Blocks, der historisch als "Block 5" umgesetzt wurde. Nach der Roadmap- Umnummerierung vom 2026-04-23 (siehe Struktur-Sync-Commit23a9b10) ist dieser Block in der Roadmap als Block 6 — Plattform-Module & einstufige Freischaltung geführt. Die TODO-IDs selbst bleiben als stabile Identifier unverändert.
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein Merge-Blocker nach CLAUDE.md-Regel („≥ 80 vor Merge fixen").
TODO-Block-5b, -5c, -5d in Cleanup-Branch
platform/block-5-cleanup-audit-delta (2026-04-24) geschlossen — siehe
Archiv am Ende.
TODO-Block-5e, -5f, -5g im v1.3.0-D2-Lauf
platform/v1.3.0-d2-backend-polish (2026-05-01) abgeschlossen — siehe
Archiv am Ende.
TODO-Block-5e — Audit-Rollback-Pfad ungestestet¶
- Severity: Low (Score 35)
- Datei: Tests
- Problem:
write_module_audit-503-Pfad (DB-Failure) hat keinen Test. Bei Audit-Fail soll die Mutation rollbacken — aktuell nur im Smoke latent durch Audit-Row-Existenz-Check abgedeckt. - Fix: Integration-Test mit gemockter/sabotierter Audit-Tabelle (z.B.
kurzfristig Permission revoken) und Assert, dass
tenant_modules-Row nach 503 nicht persistiert wurde. - Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —test_audit_failure_rolls_back_mutationsimuliertOperationalErrorimwrite_platform_auditund prüft Rollback der umgebendencreate_tenant-Mutation.
TODO-Block-5f — DELETE-Route ohne Defense-in-Depth-Scope-Check¶
- Severity: Low (Score 30)
- Datei:
src/kora_platform/api/routes/tenant_modules.py:165-203 - Problem: DELETE läuft auf BYPASSRLS-Engine und verlässt sich allein
auf
_require_operator_or_vendor. Kein zusätzlicher Tenant-Scope- Check wie inlist_tenant_modules. - Heute: Routing erlaubt nur Operator/Vendor; kein Bug.
- Fix (wenn Block 7 Operator-UI Tenant-Admin-Revoke erlaubt, nach Roadmap-Umnummerierung vom 2026-04-23): Explicit tenant-id-Match vor dem DELETE, plus RLS-Mode via app-Engine.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —DELETE /api/v1/tenants/{id}/modules/{module_id}prüft jetzt zuerst Tenant-Existenz vor dem DELETE auftenant_modules. Vorher stilles No-Op bei typo'd Tenant-ID, jetzt 404.
Aus Block 7.3 Code-Review (2026-04-25)¶
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein
Merge-Blocker. Review-Datei:
reviews/block-7-3-modules-and-packages.md.
TODO-Block-7-3-02, -7-3-03 (Backend-Endpoints) im v1.3.0-D2-Lauf
platform/v1.3.0-d2-backend-polish (2026-05-01) abgeschlossen.
Frontend-Anteile (-04, -05, -06, -07) im v1.3.0-D1-Lauf
platform/v1.3.0-d1-frontend-polish (2026-05-01) abgeschlossen,
zusammen mit dem -02/-03 Frontend-Last-Mile (useModule + useTenantModules
auf neue Endpoints migriert). Siehe Archiv am Ende. -01
(tenant_packages) bleibt offen für Block 12 (Provisioning).
TODO-Block-7-3-01 — tenant_packages Tier/Limits-Backend + UI (Pre-Flight 0b)¶
- Severity: Medium (Score 65)
- Datei(en):
tenant_packages-Tabelle (alembic/platform/versions/0001_initial_schema.py:53-67), FrontendTenantsDetailPage.vueTab "Pakete & Module" - Problem: Konzept §8.3 sieht Pro-Tenant-Pakete mit Tier
(
Starter/Pro/Enterprise) + Limits (max_chatbots,max_docs_per_chatbot,max_storage_mb,max_sources_per_chatbot,max_monthly_queries,avs_shared_enabled) vor. Tabelle existiert seit Block 1, hat aber keine API/Service/Pydantic-Schemas und 0 Rows live. Im Code gibt es kein Limits-Enforcement — die Felder sind heute Doku-only. Block 7.3 baut deshalb nur den Modul-Toggle. - Fix-Akzeptanz:
- Backend:
tenant_package_service.pymitget/upsert-Methoden, RoutenGET/PATCH /api/v1/platform/tenants/{id}/package - Service muss Limits-Enforcement aktivieren (z. B. Chatbot-
Lifecycle prüft
max_chatbots, Document-Uploader prüftmax_docs_per_chatbot) - Frontend: Tier-Dropdown + Number-Inputs für Limits in TenantsDetailPage Tab
- Audit-Action
tenant_package.updated - Rationale Deferred: Limits-UI ohne Enforcement ist Cosmetic- Doku-Work — würde User irreführen. Limits gehören sinnvoll in Block 12 (Tenant-Provisioning), wo Onboarding den Tier setzt und Lifecycle-Services die Enforcement implementieren.
- Status: Open (Trigger Block 12)
TODO-Block-7-3-02 — useModule lädt komplette Liste statt Detail-Endpoint (Review M1)¶
- Severity: Medium (Score 60)
- Datei:
frontend/operator-ui/src/composables/useModule.ts:18-35 - Problem: Detail-Lookup ruft
GET /platform/modules(volle Liste) und filtert clientseitig — Backend hat keinen/platform/modules/{id}-Endpoint. Heute akzeptabel (2 Module live), bei wachsendem Module-Inventar verschwendet das Bandbreite und CPU. - Fix: Backend-Detail-Endpoint
GET /api/v1/platform/modules/{id}ergänzen, Composable umstellen auf Single-Call. - Rationale Deferred: Aktuell 2 Module, < 50 erwartet bis Block 13 (Connector-Subsystem bringt mehr). Trigger: > 50 Module ODER Backend bekommt Detail-Endpoint mit zusätzlichen Feldern, die nicht in der Liste sind.
- Status: ✅ Erledigt in v1.3.0-D2 + D1 (Merges
bb0abbd+18c4884) — Backend-EndpointGET /api/v1/platform/modules/{module_id}in D2 ergänzt;useModulein D1-Last-Mile auf Single-Call umgestellt, ModulesDetailPage-Spec deckt 404-Pfad ab.
TODO-Block-7-3-03 — Clientseitiger Set-Diff in TenantModulesSection (Review M2)¶
- Severity: Medium (Score 55)
- Datei:
frontend/operator-ui/src/components/TenantModulesSection.vue:36-53 - Problem: Component lädt
platform_modulesundtenant_modulesparallel und mergt clientseitig zuModul × ist-aktiv-für-Tenant. Bei N Tenants und M Modulen ist das N+1-fähig — ein Aggregate-EndpointGET /api/v1/platform/tenant-modules?include_unassigned=truewürde es in einem Roundtrip lösen. - Fix-Optionen:
- Pinia/Singleton-Cache für Plattform-Module über Tenants hinweg (Frontend-only, nutzt useModules-Cache zwischen Routes)
- Backend-Aggregate-Endpoint mit Cross-Tenant-Listing
- Rationale Deferred: Bei < 5 Tenants live ist der Doppel-Call
kein Performance-Problem. Trigger: erstes Tenant-Onboarding mit
50 Tenants ODER Block 7.4 Audit-Log-Viewer braucht ähnliches Aggregate-Pattern.
- Status: ✅ Erledigt in v1.3.0-D2 + D1 (Merges
bb0abbd+18c4884) — Backend-Aggregate-EndpointGET /api/v1/platform/tenant-modules?tenant_id=…&include_unassigned=truein D2 (Cross-Tenant-Fan-out ohnetenant_idist 400, Block-12-Trigger).useTenantModulesin D1-Last-Mile migriert; TenantModulesSection feuert nur noch einen Roundtrip.
TODO-Block-7-3-04 — Kein Optimistic-Update beim Toggle (Review M3)¶
- Severity: Medium (Score 50)
- Datei:
frontend/operator-ui/src/components/TenantModulesSection.vue:107-119 - Problem: Nach
assign()/revoke()macht der Component einen vollentenant.load()-Refresh. Beim N-fachen Toggle innerhalb weniger Sekunden flickert die UI. Optimistic-Update würde das glätten. - Fix: Local-State-Update vor Backend-Call; Rollback bei Error.
- Rationale Deferred: Bei aktuellem Volumen (< 5 Module pro Tenant) kaum sichtbar. Trigger: erste UX-Beschwerde oder Block 7.4 Bulk-Operations (das wäre Multiple-Toggle-im-Sturm).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — Optimistic-Update viapatchRowvor POST/DELETE, Rollback perrestoreRowbei Fehler. Testsflips badge optimistically on activate before the assign POST resolvesundrolls back optimistic activate when the POST rejectsdecken beide Pfade.
TODO-Block-7-3-05 — Mutation-Errors auf Composable-Ebene ohne Detail (Review L1)¶
- Severity: Low (Score 45)
- Datei:
frontend/operator-ui/src/composables/useTenantModules.ts:34-43 - Problem:
assign()undrevoke()werfen roheApiError- Objekte; der Caller (TenantModulesSection) zeigt einen generischen Toast"Aktivieren fehlgeschlagen: <message>". Derbody.detail- Field aus 422-Responses wird nicht extrahiert. - Fix:
useApiumflattenError(err): {status, message, detail}erweitern; alle Caller adaptieren. - Rationale Deferred: Generic Toast ist heute ausreichend. Trigger: erste 422-Validation auf der Modules-Toggle-Route (heute unwahrscheinlich, weil Backend nur 400/404 wirft).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —flattenError(err)incomposables/useApi.tsreduziert roheApiError-Objekte auf{status, message, detail}. Pydantic-422-Listen werden zu human-lesbaren Detail-Zeilen gejoint. TenantsListPage Bulk-Soft-Delete und TenantModulesSection Bulk-/Single-Toggle nutzen flattenError für Toast-Detail-Zeilen.
TODO-Block-7-3-06 — enabled_by-UUID-Truncation ohne Tooltip (Review L3)¶
- Severity: Low (Score 35)
- Datei:
frontend/operator-ui/src/components/TenantModulesSection.vue:198 - Problem:
enabled_by-Anzeige{{ row.tenant_state.enabled_by.slice(0, 8) }}…zeigt 8 UUID-Chars. Operator kann nicht erkennen, welcher User das war — fehlender Tooltip mit voller UUID oder Username-Lookup. - Fix:
<span :title="row.tenant_state.enabled_by">für Hover- Tooltip; mittelfristig Username-Lookup-Cache (Backend liefert nur UUIDs). - Rationale Deferred: Operator kennt heute alle Operator-User intern. Trigger: erste externe Operator-Personas (Block 17).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — TenantModulesSection zeigt die volleenabled_by-UUID als HTML-title-Tooltip auf dem trunkierten 8-Char-Display. Testrenders enabled_by full UUID as title attributeasserted die UUID im title-Attribut.
TODO-Block-7-3-07 — BaseLayout-Footer veraltet (Review L4)¶
- Severity: Low (Score 30)
- Datei:
frontend/operator-ui/src/layouts/BaseLayout.vue:63 - Problem: Footer-Text steht hartkodiert auf
"Kora Platform · Operator-UI (Block 7.1b)"— durch 7.2 + 7.3 überholt. Sollte entweder generisch sein oder den aktuellen Block-Stand reflektieren. - Fix: Auf neutrales
"Kora Platform · Operator-UI"ohne Block-Referenz reduzieren (so wie Tenant-UI das macht). Oder Vite- Env-Var einsetzen, die der CI/CD beim Build setzt. - Rationale Deferred: Pure Cosmetic. Trigger: Phase B Abschluss (vor v1.0.0-GA).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — Cosmetic-Cleanup in TenantsCreatePage-Description und TenantsDetailPage-Audit-Card (vorher „Block 7.3 / Block 7.4"). Footer war bereits durch den Layout-Refactor 2026-04-28 entfernt; D1 räumt die letzten veralteten Block-Strings im UI auf.
Aus Block 7.4 Code-Review (2026-04-25)¶
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein
Merge-Blocker. Review-Datei:
reviews/block-7-4-audit-bulk-connectors.md.
TODO-Block-7-4-01, -02, -03, -06, -07, -08 im v1.3.0-D2-Lauf
platform/v1.3.0-d2-backend-polish (2026-05-01) abgeschlossen — siehe
Archiv am Ende. -04 (KNOWN_ACTIONS-Drift, Block-13/14-Trigger) und -05
(Playwright workers, >50-Specs-Trigger) bleiben offen.
Review-Finding M1 (Score 75) — Filter-Shortcut aus TenantsDetailPage
liest ?entity_id=… nicht wurde vor dem Merge im Commit
a0ab7c9 (useAuditEntries.ts:readUrlFilters()) gefixt und ist daher
nicht hier gelistet.
TODO-Block-7-4-01 — Bulk-Loop ohne Max-Cap und ohne Batch-Hydration (Review M2)¶
- Severity: Medium (Score 70)
- Datei(en):
src/kora_platform/services/module_service.py:213-268,src/kora_platform/services/tenant_service.py:213-223 - Problem:
bulk_assign_to_tenantundbulk_soft_delete_tenantsiterieren pro Item:session.get(...)+INSERT ... ON CONFLICT DO UPDATE RETURNING(Modules) bzw.session.get(...)+ Soft-Delete- Update (Tenants). Bei 100 Items sind das ~200 Round-Trips, bei 1000 Items wird die UI-Latenz sichtbar. Es gibt zudem keinen Max-Cap im Backend — ein Operator könnte 10000 IDs submitten. - Fix-Akzeptanz:
- Pre-Validate per
WHERE id IN (...)-Bulk-Select - Single
INSERT ... VALUES (...), (...) ON CONFLICT DO UPDATE(Modules) bzw.UPDATE tenants SET deleted_at = now() WHERE id IN (...) AND deleted_at IS NULL RETURNING id(Tenants) - Soft-Cap
max_items=500per Pydantic-Body (zentrale Konstante, konsistent für alle Bulk-Routen) - Rationale Deferred: Heute < 50 Tenants und < 5 Module/Tenant
live; aktuelle 1×
session.get+ UPDATE liegt unter 500 ms gegen lokales Postgres. Trigger: > 200 Tenants ODER erste UI-Beschwerde über Bulk-Latenz. - Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —bulk_soft_delete_tenantsundbulk_assign_to_tenantnutzenWHERE id IN (...)-Pre-Validate plus single Multi-Row-Statement statt N Per-Item-Roundtrips. Pydanticmin_length=1, max_length=MAX_BULK_ITEMS=500in beiden Bulk-Routen.
TODO-Block-7-4-02 — CSV-Export mappt forensische Felder nicht (Review M3)¶
- Severity: Medium (Score 65)
- Datei:
src/kora_platform/services/audit_service.py:96-168 - Problem:
actor_keycloak_id,ip_addressundsession_idsind in der DB-Tabelle, im Pydantic-Read-Schema und im Frontend-TS-Type vorhanden — aberexport_csvschreibt sie nicht in den CSV-Header. Forensisch ärgerlich, weil ein Audit-CSV ohne Quell-IP / Session- ID nicht mit den vCenter-/Keycloak-Logs korrelierbar ist. - Fix: Drei Spalten an den Header anhängen, gleiche Reihenfolge wie im Pydantic-Schema. Excel verkraftet zusätzliche Spalten.
- Rationale Deferred: Erste Forensik-Anfrage gibt es noch nicht;
bisherige Audits wurden mit
include_details=trueund JSON-Suche abgedeckt. Trigger: erster Compliance-Lauf mit Quell-IP-Anforderung. - Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — CSV-Export (GET /api/v1/platform/audit/export.csv) mappt jetztactor_keycloak_id,ip_address,session_idin den Header. Testtest_csv_export_includes_forensic_columnsergänzt.
TODO-Block-7-4-03 — Bulk-Audit-Auffindbarkeit per entity_id (Review M4)¶
- Severity: Medium (Score 60)
- Datei(en):
src/kora_platform/api/routes/operator_tenants.py:329,src/kora_platform/api/routes/tenant_modules.py:312 - Problem: Lutz-Konvention „eine Audit-Zeile pro Bulk-Aktion"
schreibt
entity_id="bulk:<n>"und packt die UUID-Liste indetails.after. Ein Operator, der perentity_id=<tenant.id>im Audit-Filter sucht, findet das Bulk-Event nicht, sondern nur die N Einzel-Events (die es bei Bulk gar nicht gibt). - Fix-Optionen:
- Konvention dokumentieren in
docs-kora/docs/konzepte/audit.md(Hinweis: „Bulk-Events suchst du überaction=*.bulk_*oder JSON-Search auf der UUID") - UI-Hint im AuditLogPage-Filter-Toolbar
- Rationale Deferred: Single-Entry-Pattern ist die richtige Compliance-Lesbarkeit (ein Reviewer sieht „Operator hat 17 Tenants in einer Aktion soft-gelöscht"). Tradeoff ist die Auffindbarkeit; ein Konventions-Doc löst das ohne Code-Änderung. Trigger: erste Operator-Frage „warum finde ich Tenant X nicht im Audit-Log".
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — Neue Doku-Pageoperations/audit-conventions.mddokumentiert „eine Audit-Zeile pro Bulk-Aktion"-Konvention plus Anonymous-Actor-Pfad und CSV-Forensik-Felder.
TODO-Block-7-4-04 — KNOWN_ACTIONS-Drift gegen Block 13/14 (Review M5)¶
- Severity: Medium (Score 55)
- Datei:
frontend/operator-ui/src/types/audit.ts:42-52 - Problem:
KNOWN_ACTIONSist eine kuratierte Liste für das Action-Filter-Dropdown (heute: tenant.*, template.*, module.*, tenant_module.*). Block 13 (Connectors) und Block 14 (Lizenz- Audit) werden eigene Action-Codes mitbringen — wenn die Liste nicht mitwächst, sind die neuen Actions im Dropdown unsichtbar (User muss „Free-Text"-Filter nutzen, was Composable nicht unterstützt). - Fix: Bei Block 13/14 die jeweiligen Actions in
KNOWN_ACTIONSergänzen. Mittelfristig: Backend-EndpointGET /api/v1/platform/audit/known-actions(DISTINCT-Query aufaction-Spalte), Composable lädt einmal pro Session. - Rationale Deferred: Reine Erweiterungs-Aufgabe, keine Korrektur. Trigger: Block 13 oder 14 startet.
- Block-11-Update (2026-05-01): Block 11 hat
chatbot_feedback.created(anonymous-actor Widget-Schreibung) in die Liste eingetragen. Restliche Drift bleibt offen — Block 13/14 und potenzielle Cleanup-Welle vor Block 13. - Status: Open (Trigger Block 13/14; Block-11-Anteil eingetragen)
TODO-Block-7-4-05 — Playwright workers: 1 als Pragma (Review L1)¶
- Severity: Low (Score 40)
- Datei:
frontend/operator-ui/playwright.config.ts:19 - Problem: Playwright läuft sequenziell, weil parallele Worker sich denselben Backend-State (Tenants-Liste, Audit-Log) teilen und sich Rows „klauen" (Test A erstellt Tenant X, Test B sucht per Slug und filtert, Test A soft-deleted X mid-flight, Test B scheitert). Aktuell akzeptabel — 8 Specs unter ~30 s — aber bei wachsendem Test-Volumen zu langsam.
- Fix: Per-Worker-DB-Schemas (jede Worker-Instanz bekommt eigene Postgres-Schema mit isolierter Tenants-Tabelle, Audit-Log). Playwright global-setup richtet die Schemas ein, env-Var an die Tests durchschleift. Komplex, lohnt erst bei > 50 Specs.
- Rationale Deferred: Pragma korrekt für Stand v1.0.0. Trigger: Test-Volumen > 50 Specs ODER Wall-Time > 2 Min.
- Status: Open
TODO-Block-7-4-06 — datetime.utcnow() (Py3.12 deprecated) (Review L2)¶
- Severity: Low (Score 35)
- Datei:
src/kora_platform/api/routes/operator_audit.py:131 - Problem:
datetime.utcnow()wird in Python 3.12 mitDeprecationWarningversehen, in 3.13/3.14 vermutlich entfernt. Aktuell nur eine Stelle (CSV-Export-Filename). - Fix:
datetime.now(UTC)(mitfrom datetime import UTC). - Rationale Deferred: Heute kein Warning sichtbar im Lint, weil Backend-Stack noch Py3.11. Trigger: Upgrade auf Py3.12 ODER irgendein anderer Bereich des Backends löst das Warning aus.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —datetime.utcnow()→datetime.now(UTC)im CSV-Filename-Builder.
TODO-Block-7-4-07 — Date-Range-Validation date_from <= date_to fehlt (Review L3)¶
- Severity: Low (Score 30)
- Datei:
src/kora_platform/api/routes/operator_audit.py(Filter-Pfad) - Problem: Wenn ein Operator versehentlich
date_from>date_tosetzt, gibt es kein 422-Error — die Liste ist einfach leer, was nach „kein Audit gefunden" aussieht. - Fix: Pydantic-Validator in
AuditListParams(@model_validatormitmode="after") der prüftdate_from <= date_to, oder Route- levelassertmit explizitem 422. - Rationale Deferred: Kosmetisch; UI-Filter-Toolbar setzt Date-Range monoton (User wählt zuerst „from", dann „to"). Trigger: erste Support-Anfrage „Audit-Suche zeigt nichts".
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —_validate_date_range-Helper lehntdate_from > date_tomit422 date_from_after_date_toab. Unit-Teststest_validate_date_range_accepts_ordered_rangeund_rejects_inverted_rangeergänzt.
TODO-Block-7-4-08 — Bulk-UI ohne Progress-Indicator (Review L4)¶
- Severity: Low (Score 25)
- Datei(en):
frontend/operator-ui/src/pages/TenantsListPage.vue(Bulk-Bar),src/components/TenantModulesSection.vue(Bulk-Bar) - Problem: Toast nach Bulk-Soft-Delete bzw. Bulk-Aktivieren ist heute der einzige Latenz-Hinweis. Bei N+1-Bulk-Loop (siehe TODO-Block-7-4-01) wird die Latenz bei großen Bulks spürbar.
- Fix:
<FormButton :loading>während des Requests, optional ein einfacher „2/17 verarbeitet"-Counter. Echter Progress wird mit Cap-Hardening aus M2 redundant — also gemeinsam refactor'n. - Rationale Deferred: Heute keine spürbare Latenz (lokales Postgres, < 100 Items üblich). Trigger: gemeinsam mit M2 (Cap + Batch-Hydration).
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — Mit dem Cap-Hardening aus -7-4-01 obsolet geworden: Bulk-CapMAX_BULK_ITEMS=500plus single Multi-Row-Statement macht Per-Item-Progress redundant.
Aus Routing-Discovery (Merge 9c0c51d, 2026-04-25)¶
Beim Schreiben von operations/routing-and-endpoints.md
sichtbar geworden. Beide vorher nur in CLAUDE.md/userMemories oder
implizit im Init-Script-Pattern, keine TODO-Spur.
TODO-Platform-04 (iptables-Setup-Skript für Remote-vLLM-Node) wurde
im Lauf platform/todo-platform-04-iptables-remote-node (2026-04-29)
abgeschlossen — siehe Archiv am Ende.
TODO-Platform-10 (Mkdocs-Auto-Deploy bei Doc-Merge) wurde im Lauf
platform/todo-platform-10-mkdocs-static-hosting (2026-04-29) als
Option 3 (Build-static-Pattern mit nginx:alpine) abgeschlossen —
siehe Archiv am Ende.
TODO-Auth-NEU — JWT-sub-Claim-Mapper-Fix (Drift #8)¶
- Severity: Medium (Score 40, M2)
- Entdeckt: Color-System-Implementation (Merge
7261426, 2026-04-28). 8. Drift-Datenpunkt im Auth-Stack-Drift-Aufräum-Vektor (Datenpunkte #1–#7 archiviert via TODO-Platform-05/-06/-07/-08/-09). - Datei(en): Realm-JSONs
infra/keycloak/realms/kora-platform-realm.jsonundinfra/keycloak/realms/kora-tenants-realm.json(Soll-Zustand); Init-Skript-Reconcile-Pfad analog zucreate-operator-ui-client.sh; Live-Sync viakc-config-clioderkcadm. - Befund: JWTs aus beiden Realms (
kora-platform,kora-tenants) enthalten keinsub-Claim. Verfügbar im Token sind nurpreferred_username,email,realm_access.roles,sid,iss,aud,exp/iat. Derkora-scope-Custom-Scope hat keinen Subject-Mapper, und Keycloak emittiertsubin der aktuellen Realm-Konfiguration nicht. Standard-OIDC weicht damit ab —subist normalerweise immer im Access-Token. - Bestehende Workarounds (nicht zu fixen, nur zu kennen):
- NULL-Fallback in
platform_audit_log.actor_keycloak_id UUIDNULLABLE. Bei fehlendemsub(aktuell: jeder Token) wird NULL eingetragen; Audit-Trail referenziert User stattdessen überactor_user=preferred_username. - Composite-PK in
user_preferences (realm, username)statt natürlicherkeycloak_id UUID-PK (Color-System-Implementation, Migration 0008). Pragmatisch, funktional, aber nicht der Soll-Architektur-Pfad. - Risiko: Jede zukünftige Tabelle mit User-Bezug muss einen der beiden Workarounds nutzen. Mit jeder neuen Code-Stelle wächst die Sanierungs-Schuld linear (Settings-Erweiterungen, User-Activity- Tracking, Notifications-Preferences).
- Fix-Pfad:
kora-scope-Client-Scope in beiden Realm-JSONs um einensub-Mapper erweitern (oder via Standard-OIDC-Scope-Inclusion mitopenidals Default-Scope sicherstellen)kc-config-cli/kcadm-Sync gegen Live-Keycloak — idempotenter Init-Skript-Pfad analog zucreate-operator-ui-client.sh- Re-Login-Test in beiden Realms — JWT enthält
sub scripts/verify-auth-stack.shCheck 58 wechselt von FAIL → PASS- Optional (separater Folge-Block):
user_preferences-Migration zu UUID-PK + Daten-Migration der bestehenden Composite-Keys (Lookup über Username-zu-sub-Mapping pro Realm) - Optional (separater Folge-Block): Audit-Log-Backfill für
historische
actor_keycloak_id IS NULL-Zeilen (Lookup analog; nur wenn Daten-Wert hoch genug) - Aufwand-Schätzung: ~1.5h für Schritte 1–4, weitere ~2h für 5+6 falls erwünscht.
- Verify-Mechanismus:
scripts/verify-auth-stack.shCheck 58 testet diesub-Claim-Anwesenheit in einem frisch gemintten Token aus beiden Realms. Aktuell: FAIL (kein Regressionsfeind, das ist die explizite Drift-Frühwarnung). - Blockt: keine kritischen Features. Aber jede weitere User-bezogene Tabelle sollte den Fix erst abwarten, statt einen dritten Workaround zu erfinden.
- Status: Open
TODO-Platform-07 (Tenant-UI Auth-URL-Drift) und TODO-Platform-09
(Systematische Auth-Stack-Verifikation) wurden im Lauf
platform/todo-09-auth-stack-verification (2026-04-27) gemeinsam
abgeschlossen — siehe Archiv am Ende.
Aus Browser-Walkthrough (2026-04-26)¶
Live-Browser-Walkthrough der Operator-UI nach TODO-Platform-09-Merge
(Login mit bench-operator-admin, alle 5 Hauptseiten + Tenant-Detail-
Tabs durchgegangen). Kein Funktions-Bug — vier UX-Polish-Findings,
die das Erscheinungsbild verbessern, aber Block 8 (Tenant-UI) nicht
blockieren.
TODO-UX-01 (Status-Wert-Konsistenz Liste vs. Detail) wurde im Lauf
platform/ux-polish-status-consistency-and-cleanup (2026-04-29)
abgeschlossen — siehe Archiv am Ende.
TODO-UX-02 (Test-Daten-Pollution Cleanup-Skript) wurde im Lauf
platform/todo-ux-02-cleanup-test-data (2026-04-27) abgeschlossen
— siehe Archiv am Ende.
TODO-UX-NEU (Cleanup-Pattern für op-vendor-* Auth-Test-Pollution)
wurde am 2026-04-28 im Cleanup-Mini-Run als Datenpunkt erkannt und
am 2026-04-29 im Lauf
platform/ux-polish-status-consistency-and-cleanup opportunistisch
mit-erledigt — siehe Archiv am Ende.
TODO-UX-03 wurde in Block 8.1
(platform/block-8.1-tenant-edit-ui, 2026-04-30) als
Verifikations-only archiviert — Discovery zeigte vollständige
Implementation seit Block 7.1b/7.4. Re-Diagnose-Lesson siehe Archiv
am Ende.
TODO-UX-04 (Sprache-Filter Templates-Listing) wurde im Lauf
platform/ux-04-language-filter-and-ux-03-deferral (2026-04-29)
abgeschlossen — siehe Archiv am Ende.
Aus Block 7.2 Code-Review (2026-04-25)¶
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein
Merge-Blocker. Review-Datei:
reviews/block-7-2-templates-crud.md.
TODO-Block-7-2-01, -03, -04, -06, -07 im v1.3.0-D1-Lauf
platform/v1.3.0-d1-frontend-polish (2026-05-01) abgeschlossen — siehe
Archiv am Ende. -02 (Backend-Search-Endpoint für Templates) bleibt
offen für > 50-Templates-Trigger; -05 (EntityForm-Refactor) bleibt
verworfen.
TODO-Block-7-2-01 — ListInput A11y: Keyboard-Reorder + Live-Region (Review M1)¶
- Severity: Medium (Score 60)
- Datei:
frontend/operator-ui/src/components/ListInput.vue:80-98 - Problem: Add/Remove-Items haben keine
aria-live-Region — Screenreader-Nutzer hören die State-Änderung nicht. Reorder per Tastatur fehlt komplett. - Fix:
<div aria-live="polite" class="sr-only">mit Status- Texten. Keyboard-Reorder via Alt+Up/Down, ARIA-Roleslistbox/option. - Rationale Deferred: Phase-A wird intern (GTS+Vendor) betrieben. Trigger: Block-18-A11y-Audit oder erste externe Operator-Personas.
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — ListInput.vue bekommt A11y-Polish:aria-live="polite"-Region annonciert Add/Remove/Reorder, Keyboard-Reorder via Alt+↑/↓, sichtbare ↑/↓-Move-Buttons. TestsAlt+ArrowDown reorders an item one position lower,Alt+ArrowUp at the top is a no-op,renders a polite live-region for screen-reader statusergänzt.
TODO-Block-7-2-02 — Backend-Search-Endpoint für Templates (Review M2)¶
- Severity: Medium (Score 55)
- Datei:
frontend/operator-ui/src/composables/useTemplates.ts:39-54 - Backend
src/kora_platform/api/routes/chatbot_templates.py - Problem: Clientseitige Suche filtert über
allItems. Bei > 200 Templates wird das spürbar. - Fix:
?search=<term>-Query-Param im Backend ergänzen, Frontend nutzt es analog zuuseTenants. - Rationale Deferred: Aktuell < 5 Templates im System. Trigger: erstes Tenant-Onboarding mit > 50 Templates.
- Status: Open
TODO-Block-7-2-03 — listsEqual ignoriert Reorder (Review M3)¶
- Severity: Medium (Score 55)
- Datei:
frontend/operator-ui/src/pages/TemplatesEditPage.vue:88-91, 128-134 - Problem: Pairwise-Compare. Bei Einführung von Reorder-UI würde das System eine neue Reihenfolge nicht als dirty erkennen.
- Fix:
JSON.stringify-Compare oder Index-bewusste Equality. - Rationale Deferred: Kein aktueller Bug —
<ListInput>hat keine Reorder-UI. Trigger: gemeinsam mit TODO-Block-7-2-01 oder Connector-Reorder in 7.4. - Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —listsEqualaus neuemformHelpers.tsist order-aware. TenantsEditPage und TemplatesEditPage nutzen den Helper konsistent.
TODO-Block-7-2-04 — Empty-List-Felder werden im POST mitgeschickt (Review L1)¶
- Severity: Low (Score 45)
- Datei:
frontend/operator-ui/src/pages/TemplatesCreatePage.vue:78-87 - Problem:
suggested_suggestions: []undrecommended_connectors: []landen immer im POST-Payload, auch wenn der User nichts hinzugefügt hat. Backend-Default ist[], semantisch identisch — Audit-Payload bläht sich. - Fix: Nur einsetzen wenn
length > 0. - Rationale Deferred: Audit-Hygiene, kein Verhaltens-Bug. Trigger: wenn 7.4 Audit-Log-Reader die Empty-Lists als visual-noise flaggt.
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — TemplatesCreatePage entfernt Empty-Lists aus dem POST-Payload — Audit-Rows tragen die Lists nur noch, wenn der User sie wirklich gefüllt hat.
TODO-Block-7-2-05 — EntityForm-Refactor-Decision (Review L2)¶
- Severity: Low (Score 40)
- Datei:
frontend/operator-ui/src/pages/{Tenants,Templates}*Page.vue - Problem: TODO-Block-7-1b-04 hatte EntityForm-Refactor als Trigger „nach drei Form-Shapes" vorgemerkt. Block 7.2 fügt die zweite Shape hinzu. Der 7.2-Review empfiehlt: wahrscheinlich nicht durchführen — Tenants-Form (4 Felder, Slug-Validator) und Templates-Form (8 Felder, ListInputs) unterscheiden sich genug, dass ein gemeinsamer Wrapper Premature-Abstraction wäre.
- Status: ✅ Verworfen in Block-7.3-Code-Review (Merge folgt). 7.3 baute keinen neuen Form-Datenpunkt — Modul-Toggle ist Single-Click, keine Form mit Feldern. Block 12 (Tenant-User- Provisioning-Form) und Block 13 (Connector-Configs mit Schema- Driven-Forms) bringen jeweils sehr unterschiedliche Form-Shapes; drei sehr ähnliche CRUDs entstehen nie. EntityForm-Refactor wird damit final verworfen.
TODO-Block-7-2-06 — Busy-Wait-Sleep im Token-Mint-Retry (Review L3)¶
- Severity: Low (Score 35)
- Datei:
frontend/operator-ui/e2e/helpers.ts:65-70 - Problem:
while (Date.now() - start < ms) { /* spin */ }blockiert den Worker-Prozess. Akzeptabel (Worker macht nichts anderes), aber unsauber. - Fix:
mintTokensasync machen +await sleep(ms). - Rationale Deferred: Kein Throughput-Problem. Trigger: wenn die Suite auf 4+ Worker skaliert.
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —mintTokensine2e/helpers.tsist async; der vorher synchrone Spin-Loop im Backoff blockiert den Worker-Event-Loop nicht mehr. Alle E2E-Spec-Caller (8 Specs) sind aufawait mintTokens()angepasst.
TODO-Block-7-2-07 — Trim-Diff inkonsistent (Review L4)¶
- Severity: Low (Score 30)
- Datei:
frontend/operator-ui/src/pages/TemplatesEditPage.vue:98, 102 - Problem:
description.trim()-Compare bei manchen Feldern, andere Felder ohne Trim. Analog zu TODO-Block-7-1b-07 (Tenants-Edit). - Fix: Helper
trimEqual(a, b)für alle String-Felder. - Rationale Deferred: Kein aktueller Bug. Gemeinsam mit TODO-Block-7-1b-07 lösen.
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —trimEqualaus neuemformHelpers.ts(whitespace-only-tolerantes Edit-Diff) wird in TenantsEditPage und TemplatesEditPage konsistent genutzt.
Aus Block 7.1b Code-Review (2026-04-25)¶
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein
Merge-Blocker. Review-Datei:
reviews/block-7-1b-operator-ui-frontend.md.
TODO-Block-7-1b-01, -05, -06, -07, -08 im v1.3.0-D1-Lauf
platform/v1.3.0-d1-frontend-polish (2026-05-01) abgeschlossen — siehe
Archiv am Ende. -02 (Slug-availability-Backend-Endpoint) bleibt offen
für > 200-Tenants-Trigger; -03 schon in Block 7.3 erledigt; -04 final
verworfen.
TODO-Block-7-1b-01 — E2E-Seed-Hook ohne Build-Flag-Gate (Review M1)¶
- Severity: Medium (Score 65)
- Datei:
frontend/operator-ui/src/composables/useAuth.ts:46-57 - Problem:
applyE2eSeed()liestwindow.__KORA_E2E_SEED__ohne Vite-Build-Flag-Check. In Prod ist das Feld nie real gesetzt — aber ein DOM-Clobbering-Angriff (<form id="__KORA_E2E_SEED__">) könnte die Auth-State manipulieren, bevormain.tsläuft. Threat-Model ist niedrig (Operator-User loggt sich auf vertrauenswürdiger Maschine ein), Mitigation aber trivial. - Fix:
if (import.meta.env.VITE_E2E_MODE !== "true") return;inapplyE2eSeed. Build-Flag ist im env.d.ts schon deklariert. - Rationale Deferred: Aktive Bedrohung erfordert XSS oder DOM- Injection vor dem Vue-Mount. Trigger: Security-Pass vor v1.0.0-GA (Block 14 Deployment) oder erste externe Pen-Test-Runde.
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) — E2E-Seed-Pfad inuseAuth.applyE2eSeed()ist hinterimport.meta.env.VITE_E2E_MODE === "true"gegated. Dockerfile.platform akzeptiertARG VITE_E2E_MODE, docker-compose.platform.yml setzt ihn auf${VITE_E2E_MODE:-true}für luki-ai. Production-Build (z. B. zukünftige GA-CI-Pipeline) überschreibt mit leerem Wert; DOM-Clobbering-Angriff (<form id="__KORA_E2E_SEED__">) kann den Auth-State dann nicht mehr kapern.
TODO-Block-7-1b-02 — Slug-Availability-Check ILIKE-Substring (Review M2)¶
- Severity: Medium (Score 60)
- Datei:
frontend/operator-ui/src/pages/TenantsCreatePage.vue:76-88 - Problem: Slug-Verfügbarkeits-Check nutzt
?search=<slug>&limit=5und macht einen Exact-Match-Filter clientseitig. Beislug="acme"und einem existierendenacme-gmbhwürde derlimit=5-Cutoff den exakten Match zurückliefern, wenn er in den ersten 5 ist — aber ein zweiteracmedarunter könnte dropped werden. Datenrisiko null (Backend-409 fängt jedes Race), aber UX-Stolperer. - Fix: Dedizierten
GET /platform/tenants?slug=<slug>(exact- match) im Backend ergänzen, oderlimit=200setzen. - Rationale Deferred: Smoke-Test-10 (§14.4) bestätigt, dass kein Daten-Bug entsteht. UX-Glitch erst sichtbar bei > 200 Tenants mit ähnlichen Slugs. Trigger: 7.3 (Modules-Registry braucht ähnlichen Pattern, könnte Backend-Endpoint mitbringen).
- Status: Open
TODO-Block-7-1b-03 — Tab-Row A11y in DetailPage (Review M3)¶
- Severity: Medium (Score 55)
- Datei:
frontend/operator-ui/src/pages/TenantsDetailPage.vue:131-149 - Problem: Tab-Row nutzt
<div class="tab tab--disabled">mittitle-Tooltip statt einer Tablist-ARIA-Pattern oder einfach<button disabled>. Screenreader hören die Tabs nicht; Tab-Tasten- Fokus überspringt sie. - Fix: Echtes Tablist-Pattern (
role="tablist"+role="tab"+aria-selected+aria-disabled) ODER<button type="button" disabled :title="...">. Letzteres ist 1-zu-1. - Rationale Deferred: Phase-A v1.0.0 wird intern (GTS+Vendor) betrieben. Trigger: erste externe Operator-Personas (Block 17 Tenant-SSO-Self-Service deployt einen Vendor-User-Onboarding-Flow, da kommen externe Operatoren rein) oder vor v1.1.0-GA.
- Status: ✅ Erledigt in Block 7.3 (Merge folgt) — Tab-Row beim
Aktivieren des „Pakete & Module"-Tabs von
<div>-Elementen auf echte<button>-Tabs umgestellt:aria-active-State,disabled- Attribut auf den Block-7.4-Placeholdern,focus-visible-Outline,cursor:not-allowed-Style. Volle ARIA-tablist-Rolle nicht umgesetzt (würde nochrole="tablist"/role="tab"/aria-controlsbrauchen) — als Folge-TODO für externe-Personas-Pass beobachten, aber Screenreader-Basis ist jetzt da.
TODO-Block-7-1b-04 — Form-Markup-Overlap Create/Edit (Review L1) ✅ Verworfen¶
→ siehe TODO-Block-7-2-05 oben — finaler Verzicht im Block-7.3-Code- Review begründet (kein dritter ähnlicher Form-Datenpunkt entsteht).
- Severity: Low (Score 45)
- Datei:
frontend/operator-ui/src/pages/TenantsCreatePage.vue+TenantsEditPage.vue - Problem: ~120 LOC Overlap zwischen Create- und Edit-Formular (Display-Name, E-Mail, Notes mit identischer Validierung). Slug- Verhalten unterscheidet sich (Create: live-Verfügbarkeit, Edit: disabled), Submit-Handler auch (Create: POST, Edit: Diff-PATCH) — also keine 100%-Wiederverwendung möglich.
- Fix (wenn 7.2 Templates und 7.3 Modules ähnlichen Bedarf haben):
Generic
<EntityForm v-slot="{ field }">-Component, die das Layout - Standard-Field-Wrapping liefert; Create/Edit-Pages liefern die Slot-Inhalte und den Submit-Handler.
- Rationale Deferred: DRY ohne 2/3 weitere Use-Cases ist premature. Trigger: 7.2 (Templates-Form) baut die zweite, 7.3 die dritte Variante — dann sind die gemeinsamen Punkte sichtbar.
- Status: ❌ Verworfen in Block-7.3-Code-Review (siehe Header oben + TODO-Block-7-2-05). Kein dritter ähnlicher Form-Datenpunkt entsteht; Block 12 (Tenant-User-Provisioning) und Block 13 (Connector-Configs) bringen unterschiedliche Form-Shapes.
TODO-Block-7-1b-05 — Kein Stale-While-Revalidate in useTenants (Review L2)¶
- Severity: Low (Score 40)
- Datei:
frontend/operator-ui/src/composables/useTenants.ts - Problem: Jede Navigation zurück zur Liste triggert kompletten
Re-Fetch (
onMounted(load)in TenantsListPage). Akzeptabel bei < 100 Tenants, kann auf 3G-Verbindung als Loading-Flash sichtbar werden. - Fix: Stale-While-Revalidate-Pattern: Cache letzte Response, zeige sie sofort, fetche im Hintergrund. Vue-Query oder eigener Composable.
- Rationale Deferred: Operator-UI ist Office-Netzwerk-Nutzung. Trigger: > 200 Tenants oder mobile-Operator-Use-Case (post-v1.0.0).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —useTenantshat jetzt einen Module-Scope SWR-Cache (Stale-While-Revalidate, key = serialisierte List-Params, 30s TTL). Mutationen rufeninvalidateCache. Tests decken first-load (network), warm-cache (no network), mutation-invalidate, stale-revalidate ab.
TODO-Block-7-1b-06 — useConfirm Singleton-Auto-Reject (Review L3)¶
- Severity: Low (Score 35)
- Datei:
frontend/operator-ui/src/composables/useConfirm.ts - Problem: Wenn
ask()aufgerufen wird während ein Dialog offen ist, resolved der erste mitfalse. Aktuell unerreichbar (UI hat keine zwei parallelen Confirm-Trigger), aber überraschend für spätere Caller. - Fix: Entweder Queue (zweiter Dialog wartet) oder Throw
(
UnableToOpenConfirm). Queue ist UX-freundlicher. - Rationale Deferred: Aktuell unerreichbar. Trigger: 7.3 (Bulk- Ops mit "alle X-Tenants löschen?"-Confirm pro Item könnte das triggern).
- Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —useConfirmist auf eine FIFO-Queue umgestellt. Überlappendeask()-Calls landen hintereinander statt den ersten still mitfalseabzuwürgen. Testqueues a second ask while the first is open and serves them in orderersetzt den vorherigen Auto-Reject-Test.
TODO-Block-7-1b-07 — PATCH-Diff ohne String-Trim (Review L4)¶
- Severity: Low (Score 30)
- Datei:
frontend/operator-ui/src/pages/TenantsEditPage.vue:75-84 - Problem: Dirty-Detection via String-Vergleich.
form.value.notes !== initial.value.noteslöst einnotes: nullin Payload aus, wenn der User nur Whitespace anhängt und dann zurückspringt — User sieht keinen visuellen Unterschied, aber Notes werden überschrieben. - Fix: Vor dem Diff-Vergleich
.trim()auf Initial- und Form- Werten anwenden, oder Diff-Funktion verschönern. - Rationale Deferred: Edge-Case bei manuell editiertem Whitespace.
Trigger: erstmal Audit-Log auswerten (Block 7.4) — wenn dort viele
notes: null-Updates ohne erkennbaren Anlass auftauchen. - Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —trimEqualausformHelpers.ts(whitespace-only-tolerantes Edit-Diff) wird in TenantsEditPage und TemplatesEditPage konsistent genutzt. PATCH schicktnotes: nullnicht mehr nur, weil der User Whitespace angehängt hat.
TODO-Block-7-1b-08 — PORTING.md als Repo-Artefakt (Review L5)¶
- Severity: Low (Score 25)
- Datei:
frontend/operator-ui/PORTING.md - Problem: Pre-Flight-Artefakt aus Phase 1 ist im Code-Repo, nicht
in
docs-kora/docs/blocks/. Spätere Leser könnten es übersehen (Code-Reviewer schauen erst auf docs-kora/, nicht in Source-Bäume). - Fix: Nach
docs-kora/docs/blocks/block-7-1b-porting.mdverschieben, im operator-ui-README darauf verlinken. - Rationale Deferred: Aktiv genutzt nur für Block-7.2-Autor; die
Verschiebung kann in 7.2 mit dem neuen
<EntityForm>-Refactor (siehe -04) gebündelt werden. - Status: ✅ Erledigt in v1.3.0-D1 (Merge
18c4884) —frontend/operator-ui/PORTING.mdist nachdocs-kora/docs/blocks/block-7-1b-porting.mdverschoben (pergit mv). Operator-ui/README.md verlinkt die neue Position; mkdocs.yml hat eine neue nav-Sektion „Blocks (Pre-Flight & Porting)".
Aus Block 7.1a Code-Review (2026-04-24)¶
Alle Findings haben Review-Score < 80 → Follow-up-Backlog, kein
Merge-Blocker. Review-Datei:
reviews/block-7-1a-tenants-backend.md.
TODO-Block-7-01, -02, -03, -04, -06, -07 im v1.3.0-D2-Lauf
platform/v1.3.0-d2-backend-polish (2026-05-01) abgeschlossen — siehe
Archiv am Ende. -05 (pydantic[email]-Supply-Chain) bleibt offen für
ein separates Supply-Chain-Review vor v1.0.0-GA-Audit.
TODO-Block-7-01 — Operator-Route-Präfix-Vereinheitlichung + Scope-Guards zentralisieren¶
- Severity: Low (Score 45)
- Datei(en):
src/kora_platform/api/routes/operator_tenants.py,tenant_modules.py,chatbot_templates.py— je eine lokale_require_operator_or_vendor-Kopie;operator_tenants.pyergänzt dazu_require_operator. - Problem: Drei (mit 7.2/7.3/7.4 künftig vier bis fünf) identische
Kopien der Scope-Guards. Pfad-Präfix ist jetzt
/api/v1/platform/*für Platform-Level-Routen und/api/v1/tenants/*für Tenant-Scope — konsistent, aber nur durch Konvention, nicht durch einen zentralen Helper erzwungen. - Fix:
api/dependencies/scope_guards.pymitrequire_operator(),require_operator_or_vendor(),require_tenant()-Dependencies; Route-Lokal-Kopien durch Imports ersetzen. Lohnt sich, wenn alle 7.2/7.3/7.4-Operator-Routen da sind. - Rationale Deferred: Kosmetik + DRY, keine Verhaltensänderung. Trigger: nach 7.4, wenn der Umfang der Operator-Routes stabil ist.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — Zentraler Scope-Guard-Helperkora_platform.api.dependencies.scope_guards. Vier Routen-Files (operator_tenants,operator_audit,tenant_modules,chatbot_templates) ziehen jetzt zentralen Helper statt lokaler Kopien._require_any_scopeund_require_tenant_owns_chatbotinchatbot_templates.pybleiben lokal (template-spezifisch).
TODO-Block-7-02 — GET-List TOCTOU Window-Function (Review M1)¶
- Severity: Medium (Score 65)
- Datei:
src/kora_platform/api/routes/operator_tenants.py:86-108(bzw.tenant_service.py::list_tenants) - Problem: Pagination-Count und Items-Select sind zwei Roundtrips;
zwischen ihnen kann ein paralleler Create/Delete
totalverschieben. Heute kosmetisch (Operator-UI max. 50 Rows, low Traffic). - Fix: Eine SELECT mit
COUNT(*) OVER ()— ein Roundtrip, identischer Snapshot. - Rationale Deferred: Tenant-Volumen < 1000 auf absehbare Zeit. Trigger: sobald Live-Filter-driven-UI oder > 1000 Tenants.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —TenantService.list_tenantsnutztcount(*) OVER ()-Window-Function in einer Single-Query. Empty-Page-Fallback per Count-Only-Query (Window liefert nur Werte, wenn die Page selbst Rows hat).
TODO-Block-7-03 — Session-Identity-Trap beim Audit-Delta (Review M2)¶
- Severity: Medium (Score 60)
- Datei:
src/kora_platform/api/routes/operator_tenants.py:198-221 - Problem:
changed_fields(before, patch)liest Attribute aus der ORM-Instanz, dieupdate_tenantgleich mutieren wird. Heute korrekt (Python-setattr passiert nach dem Vergleich), fragil wenn der Service auf Bulk-SQL umstellt. - Fix: Vor dem Aufruf
before_snapshot = {f: getattr(before, f) for f in TenantUpdate.model_fields}— entkoppelt das Diff vom ORM- State. - Rationale Deferred: Kein aktueller Bug. Trigger: wenn
update_tenantaufsession.execute(update(...))umgestellt wird (Bulk-Patch-Use-Case z. B. in Block 12 Provisioning-Automatisierung). - Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) —TenantService.changed_fieldssnapshotet Before-Werte in plain-dictvor dem Diff. Entkoppelt das Audit-Delta vom ORM-State, sodass eine zukünftige Bulk-Update-Variante nicht den Audit-Trail aliased.
TODO-Block-7-04 — updated_at server-side onupdate (Review M3)¶
- Severity: Medium (Score 55)
- Datei:
src/kora_platform/services/tenant_service.py:155,src/kora_platform/db/models/tenant.py(Column-Definition) - Problem:
update_tenantsetztupdated_at = datetime.now(UTC)Python-seitig statt via Column-levelonupdate=func.now(). In einem Multi-Host-Deployment können die Uhren leicht abweichen; der Service- Layer führt das als Business-Logic, statt die DB die Wahrheit sagen zu lassen. Parallel zu TODO-Block-5g (tenant_modules.updated_at- Pendant im Upsert). - Fix:
onupdate=func.now()antenant.updated_atergänzen (Alembic-Migration) +datetime.now(UTC)-Aufruf im Service entfernen. - Rationale Deferred: Single-Host-Dev, keine Observability-Relevanz heute. Trigger: vor Multi-Host-Deployment oder wenn Block 12 die Audit-Timestamps vereinheitlicht.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — Alembic 0010 installiert PG-Triggerset_updated_at()auftenants,tenant_modules,platform_modules(gemeinsam mit -5g). Service-Code setztupdated_atnicht mehr Python-seitig.update_tenantundsoft_delete_tenantrufen jetztawait session.refresh(tenant), damit der trigger-bumped Wert beim Caller ankommt.
TODO-Block-7-05 — pydantic[email] in Runtime-Image (Review L2)¶
- Severity: Low (Score 40)
- Datei:
pyproject.toml:22 - Problem: Durch
pydantic[email]landetemail-validator+ transitividna+dnspythonim Runtime-Image.check_deliverability=Falseist Default — keine Runtime-DNS-Queries —, aber Supply-Chain-Footprint wächst. - Fix (Option): eigener Email-Regex-Validator in
models/tenant.py;pydantic[email]durch plainpydanticersetzen. Kostet Validation-Qualität (RFC 5322 ist nicht regex-abbildbar). - Rationale Deferred: Wiegt Validation-Qualität gegen Supply-Chain.
Trigger: Supply-Chain-Review vor v1.0.0-GA oder wenn ein Security-
Audit
email-validator/dnspythonals Angriffsfläche flaggt. - Status: Open
TODO-Block-7-06 — search ILIKE-Metachar-Escape (Review L3)¶
- Severity: Low (Score 35)
- Datei:
src/kora_platform/services/tenant_service.py::list_tenants - Problem:
search-Query-Param wird ungefiltert in%…%-ILIKE eingebettet. Enthält der String%oder_, werden sie als ILIKE- Wildcards interpretiert (UX-Glitch; kein Injection-Risiko — die Bind-Parameter bleiben geschützt). Slug-Regex enthält diese Chars nicht, display_name kann sie aber haben. - Fix: Escape-Helper (
search.replace("%", "\\%").replace("_", "\\_")) +ESCAPE '\\'-Klausel in der Query. - Rationale Deferred: Bisheriges UI (keins) zeigt keine Wildcards; Trigger: sobald die Operator-UI (7.1b) die Filter-Box live ausliefert.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — ILIKE-Metachar-Escape imTenantService.list_tenants-Search-Pfad. Helper_escape_ilikeescapt\\,%,_. Testtest_search_escapes_ilike_metacharsbeweist, dass_safe-Suche nicht mehrsafe-Slugs zieht.
TODO-Block-7-07 — include_deleted-Vendor-Pfad ohne Test (Review L4)¶
- Severity: Low (Score 30)
- Datei:
tests/integration/test_operator_tenants_api.py - Problem: Vendor-Scope darf laut Guard
GET /tenants+include_deleted=truelesen; kein expliziter Test deckt den Vendor- mit-Flag-Pfad. Operator-Pfad und Flag-Pfad sind separat getestet. - Fix: Kleiner Test
test_vendor_can_list_deleted. - Rationale Deferred: Reines Gap, kein Bug. Trigger: bundled mit 7.1b Vendor-Audit-Log-UI oder 7.4.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — Testtest_vendor_can_list_with_include_deleteddeckt den Vendor-Pfad mitinclude_deleted=true.
Aus Block-5-Cleanup Code-Review (2026-04-24)¶
TODO-Block-5g — updated_at wird vom ON CONFLICT DO UPDATE-Pfad nicht mitgeschrieben¶
- Severity: Low (Score 40)
- Datei:
src/kora_platform/services/module_service.py:150-157,src/kora_platform/db/models/platform_module.py(Column-DefinitionTenantModule.updated_at) - Problem: Das SET-Dict des Upserts setzt
is_enabled,enabled_by,enabled_at, aber nichtupdated_at. Die Column hat nurserver_default=func.now(), keinonupdate=func.now()→ ein Re-Assign überON CONFLICT DO UPDATElässtupdated_atauf dem ursprünglichen Insert-Timestamp stehen. Heute kein Consumer betroffen; sobald aber ein „changed-since"-Filter oder eine UI-Sortierung nach letzter Änderung gebaut wird, wird das zum Bug. - Fix (eine der beiden Varianten):
"updated_at": func.now()in denset_={…}-Dict des Upserts aufnehmen. Minimal-invasiv, keine Schema-Änderung.onupdate=func.now()zurupdated_at-Column ergänzen. Saubere ORM-Semantik, erfordert Alembic-Migration.- Rationale Deferred: Kein aktiver Consumer; Fix ist Ein-Liner und kann gebündelt mit Block 7 (Operator-UI, zeigt „zuletzt geändert") oder Block 12 (Provisioning, nutzt Audit/Timestamps) kommen.
- Status: ✅ Erledigt in v1.3.0-D2 (Merge
bb0abbd) — gemeinsam mit -7-04 als Alembic 0010 implementiert: PG-Triggerset_updated_at()auftenants,tenant_modules,platform_modules. Saubere Variante 2 gewählt (server-sideonupdate).
Aus Block 4 Phase F (2026-04-22)¶
TODO-Block-4b — Qdrant Backup-Strategie¶
- Severity: Medium (Score 60)
- Scope: Snapshot-Integration, Storage-Lokation, Restore-Runbook
- Aufwand: ~8-12h
- Rationale: Wurde aus Block 4 herausgezogen, um den Block auf 24-28h statt ~40h zu begrenzen. Fundament §6 hatte Backup partiell drin. Kommt als eigener Block, sobald Block 4 stabil ist.
- Inhalt:
- Qdrant-native Snapshots via HTTP-API (
/collections/<name>/snapshots) - Scheduler-Job für periodisches Snapshotting (täglich, Retention 14 Tage)
- Storage: lokaler Volume-Mount, langfristig S3-kompatibel
- Restore-Runbook mit konkretem Wiederherstellungs-Pfad
- Qdrant-native Snapshots via HTTP-API (
- Status: Open
TODO-Block-4c — Template-Level Shared-Collection-Gate¶
- Severity: Low (Score 35)
- Scope:
PlatformRetrieval.search()erweitern, damit Shared- Collection auch dann nicht abgefragt wird, wenn das zugehörigechatbot_template.shared_collection_name IS NULList. - Hintergrund: Fundament §6.2 spezifiziert einen zweistufigen
Gate:
chatbots.uses_shared_docs = TRUEUNDchatbot_template.shared_collection_name IS NOT NULL. Die erste Bedingung (Chatbot-Level) ist mit Block 4 Phase D.1 umgesetzt. Die zweite (Template-Level) wurde verschoben, weil Chatbot- Templates erst mit Block 5 volles CRUD bekommen. - Aufwand: ~1h (inkl. Template-Join in der Retrieval-Query)
- Abhängigkeit: Block 5 (Chatbot-Templates & CRUD) — nach Roadmap-Umnummerierung vom 2026-04-23. Nicht zu verwechseln mit dem historischen "Block 5" (Plattform-Module), der nach der Umnummerierung Block 6 ist.
- Rationale Deferred: Ohne Template-CRUD gibt es keine effektiven Template-Configs. Der Gate würde aktuell gegen ein leeres Feld prüfen und wäre ein Null-Op.
- Status: ✅ Erledigt in Sub-Commit
c898d85(Block-5 Sub-Commit 4, gemerged als2a3e5b0am 2026-04-24). Lösung:PlatformRetrieval._template_allows_shared()prüft zusätzlich zum Chatbot-Level-Gate, obchatbot.template_idgesetzt ist und das Template einshared_collection_name IS NOT NULLdeklariert. Chatbots ohne Template fallen ebenfalls geschlossen. Drei neue Unit-Tests intests/unit/test_retrieval.py; Fixture intests/integration/test_qdrant_tenant_isolation.pyseedet jetzt ein Test-Template mitshared_collection_name. 52/52 Regression-Tests grün, inkl. 200×-Cross-Tenant-Stress-Pattern.
TODO-Platform-03 — File-Write-Hook blockt Substring-Match auf Eval¶
- Severity: Low (Score 20)
- Datei(en): Claude-Code-Hook-Konfiguration (nicht im Repo)
- Problem: Der lokale Write-Hook blockt File-Writes, deren Inhalt die Substring "eval" enthält (Security-Check gegen Python-Eval-Calls). Triggert false-positive bei Wörtern wie "retrieval", "evaluation", "medieval", "devaluation". Workaround: Bash-Heredoc statt Write-Tool, oder Datei zunächst mit Platzhalter schreiben und dann via sed füllen.
- Vorschlag: Hook-Pattern schärfen auf Wort-Grenzen (z.B.
\beval\bstatteval). Alternative: Whitelist für häufige false-positive- Wörter. - Rationale Deferred: Claude-Code-Tool-Config, kein Platform-Code. Wird nur erwähnt, weil der Workaround-Aufwand bereits dreimal in Block 4 angefallen ist.
- Status: Open
TODO-Platform-14 (Template-Mutations ohne Audit-Trail) wurde in
chore/todo-platform-14-template-audit (2026-04-30) abgeschlossen —
siehe Archiv am Ende.
Aus v1.0.0-GA-Akzeptanz-Lauf (2026-04-30)¶
Drei Datapoints aus dem v1.0.0-Tag-Run, die operativ Friction
verursachen, aber nicht Tag-blockierend waren — siehe
docs-kora/docs/releases/v1.0.0-acceptance.md für die Acceptance-
Kontext-Discovery-Befunde.
TODO-Platform-11 — Backend-Pytest-Env-Bootstrap¶
- Severity: Low (Score 35)
- Datei(en): noch nicht im Repo — Soll:
make test-setupoderscripts/bootstrap-test-env.sh, plus Eintrag imdocs-kora/docs/operations/-Runbook - Problem: Lokales Backend-Pytest-Setup ist nicht out-of-the-box.
email-validatorist nicht in der Test-Dependency-Gruppe installiert, und DSN-Env-Vars (KORA_DB_APP_DSN,KORA_DB_VENDOR_DSN,KORA_DB_ADMIN_DSN) sind nur viadocker-compose.platform.ymlgesetzt — nicht im.venv-Lauf. Beim v1.0.0-GA-Akzeptanz-Lauf musste.venv/bin/pytestmit.env.platform-Source plus manueller DSN-Construction laufen. Dieses Workaround-Wissen liegt in keinem Skript. - Vorschlag:
make test-setup-Target oderscripts/bootstrap-test-env.shdas (a) alle Test-Dependencies inklusiveemail-validatorins.venvinstalliert, (b) die benötigten DSN-Env-Vars aus.env.platformableitet und in eine.env.testschreibt (oder als shell-export-Block emittiert), (c) idempotent ist (mehrfacher Lauf bricht nicht), (d) im Operations-Runbook referenziert wird. - Trigger: Vor Block 14 (CI-Pipeline-Setup) zwingend, weil CI-Runner ein reproduzierbares Test-Setup brauchen. Kann früher kommen, falls ein Block-8-Run Backend-Tests lokal benötigt.
- Quelle: v1.0.0-GA-Akzeptanz-Lauf, Discovery-Datapoint #2
(siehe
releases/v1.0.0-acceptance.md). - Status: Open
TODO-Platform-12 — Pytest-Profil-Trennung Platform vs. AVS-Demo¶
- Severity: Low (Score 45)
- Datei(en):
pyproject.toml([tool.pytest.ini_options]), evtl. neuepytest-platform.inioder Marker-Decorator-Sweep übertests/ - Problem: AVS-Demo-Tests (
tests/unit/test_pipelines.py,test_cache.py,test_rate_limit.py,test_semantic_cache.py,test_api.py,tests/integration/test_qdrant_tenant_isolation.py,test_rag_e2e.py) liegen im selbentests/-Verzeichnis wie die kora-Platform-Tests. Vollerpytest tests/-Lauf produziert 19 Failures + 109 Errors in den Phase-A-Demo-Tests, die für die Platform irrelevant sind und Akzeptanz-Matrix-Reads verfälschen. v1.0.0-GA-Lauf hat das pragmatisch per--ignore-Flag-Cluster umgangen — implizite Konvention, die jeder Operator neu lernen muss. - Vorschlag (eine Variante wählen):
- Pytest-Marker:
@pytest.mark.platform/@pytest.mark.demoplusmarkers-Liste inpyproject.tomlplusaddopts = "-m platform"als Default. Klassisch. - Separate Pytest-Config-Files:
pytest-platform.ini/pytest-demo.inimittestpaths-Isolation. Robust, aber Doppel-Pflege. - Verzeichnis-Trennung: Demo-Tests nach
tests-avs-demo/verschieben. Sauber, aber größere Diff. Kompatibel mit Demo-eingefroren-Status. Akzeptanz: Acceptance-Matrix-Skripte ausreleases/v1.x.0-acceptance.mdverwenden den Platform-Filter als Default — kein--ignore-Cluster mehr. - Trigger: Vor Block 14 (CI), oder beim ersten Block-8- Backend-Test-Lauf wenn voller Lauf gewünscht ist.
- Quelle: v1.0.0-GA-Akzeptanz-Lauf, Discovery-Datapoint #3.
- Status: Open
TODO-Platform-13 (Module-Auto-Seed) wurde in Block 8.0
(platform/block-8.0-foundation, 2026-04-30) mit Variante 1
(Lifespan-Hook) erledigt — siehe Archiv am Ende.
Aus Block 8.6/8.7-Hand-off (2026-04-30)¶
TODO-Platform-15 — Playwright-E2E-Coverage-Lücke für Chatbot-Sub-Routes ✅ ERLEDIGT MIT BLOCK 11 (2026-05-01)¶
- Severity: Low (Score 35)
- Status: ✅ Erledigt mit Block 11 Phase 4 (Branch
platform/block-11-widget-integration, Merge149c699). - Auflösung: Drei neue Tenant-UI-Specs
(
frontend/tenant-ui/e2e/chatbot-sub-routes.spec.ts,widget-public-api.spec.ts) und ein Operator-UI-Spec (frontend/operator-ui/e2e/tenant-branding-sub-route.spec.ts) decken die Lücke. Sie folgen dem skip-if-env-not-set-Pattern austemplate-update.spec.ts(E2E_CHATBOT_ID/E2E_TENANT_ID), damit CI ohne provisionierte Fixture-Daten nicht bricht. Das Test-Daten-Setup (SQL-Fixture für bench-tenant-a + bench-chatbot- 000 mit Branding/Feedback-Mock-Rows) bleibt als Folge-Aufgabe für einen E2E-Seed-Sweep — die Specs selbst sind aber lauffähig, sobald ENV gesetzt ist und ein Chatbot-Row existiert. - Folge-Datapoint (kein eigenes TODO): ein zentrales
E2E-Seed-Skript (analog
gen-test-tokens.sh, das einen Test-Tenant + Chatbot + Branding + Feedback-Sample landet) würde dietest.skip-Tags in Tenant-UI-Specs auflösen. Bietet sich vor Block 14 (CI) an, damit die ganze E2E-Suite im Build mitläuft.
Original-Eintrag (zur Nachvollziehbarkeit)¶
- Datei(en):
frontend/tenant-ui/e2e/,frontend/operator-ui/e2e/ -
Problem: Block 8.6 (Branding) und Block 8.7 (Feedback-View) haben Vitest-Coverage, aber keine Playwright-E2E-Tests für die neuen Sub-Routes:
- Tenant-UI:
/chatbots/:id/branding,/chatbots/:id/feedback - Operator-UI:
/tenants/:id/branding
Existing 8.4-Playwright-Sweep deckt CRUD über
ChatbotDetailPageab, aber Branching auf Sub-Routes wird nicht klick-getestet. Regression-Risiko bei zukünftigen Layout-Änderungen (z.B. Sidebar-Refactor, ChatbotDetailPage- Tab-Migration). - Vorschlag: - Pro Sub-Route ein E2E-Szenario: - Tenant-Branding: Login → Sidebar „Branding" → Color ändern → Save → Toast. - Chatbot-Branding: Login → Chatbot-Detail → Branding-Button → Override setzen → Save. - Chatbot-Feedback: Login → Chatbot-Detail → Feedback-Button → Filter setzen → Tabelle aktualisiert. - Operator-Tenant-Branding: Operator-Login → Tenant-Detail → Branding →custom_csssetzen → Save. - Test-Daten-Setup für Feedback: Mock-Rows via SQL-Fixture (analog Live-Smoke aus 8.7) oder Seed-Skript. - Aufwand: ~2h pro Sub-Route inkl. Fixture, also ~6–8h gesamt. - Trigger: Vor Block 11 (Widget-Integration ändert Schreib- Pfad — gute Gelegenheit für E2E-Regression-Tests) oder als Teil der Cleanup-Welle vor Block 13. - Quelle: Block-8.7-Hand-off-Bericht (2026-04-30), Drift-Liste-Update. - Tenant-UI:
Aus Konzept-Review (Merge 1fa5835, 2026-04-21)¶
TODO-Konzept-01 — v5.2-Klärung §17.2/§18 Phase-B-Summe + Block 11 Widget ✅ ARCHIVIERT (2026-05-01)¶
- Severity: Low (Score 40, kein Blocker)
- Status: ✅ Erledigt 2026-05-01 (Branch
chore/todo-konzept-01-block-11-aufwand, Merge9bcf36e). - Auflösung: Die Block-11-Widget-Aufwand-Diskrepanz (12h §17.2
vs. 16h frühere Roadmap-Status-Box) wurde per Lutz-Entscheidung
am 2026-05-01 auf den Mid-Point 14h Refined konsolidiert.
Dokumentiert in §17.2 Tabellenzeile (12h → 14h), §17.2a
Überlapp-Quelle Nr. 6 (Reconciliation-Begründung),
roadmap.md(Pfad-A-Header und Block-11-Aufwand-Zeile auf 14h Refined vereinheitlicht). Phasen-Total ~425h für v1.0.0 wandert um +2h auf ~427h innerhalb der Rundungstoleranz. - Nicht-Scope dieser Auflösung: Die §17.2-Einzelposten-vs-§18- Phasen-Summen-Differenz (~18h) und das Qdrant-Collection-Naming- Schema-Finding aus Block 4 sind eigenständige Themen — siehe §17.2a Überlapp-Quellen Nr. 1–5 (bereits in v5.3/v5.3.1 aufgelöst) und Block-4-Implementations-Doku. Keine offenen Punkte aus dem ursprünglichen TODO-Konzept-01 bleiben.
Original-Eintrag (zur Nachvollziehbarkeit)¶
- Datei(en):
docs-kora/docs/konzepte/multitenancy-fundament.md§17.2 + §18,docs-kora/docs/roadmap.md:698 - Problem: §17.2-Einzelposten summieren 410h (nach v5.1-Anpassung),
§18-Phasen-Summe deklariert 384h, Delta ~26h schwerpunktmäßig in Phase B
(166h vs. 144h). Zusätzlich: Block 11 Widget mit 12h in §17.2 vs. 16h in
roadmap.md:698. Eine Warning-Admonition in §17.2 markiert die Diskrepanz und verweist §18 als autoritativ. - Zusätzlich (Block-4-Finding): Fundament §6.1 nennt ein anderes
Qdrant-Collection-Naming-Schema (
chatbot_<uuid>,shared_<template_slug>). Die Platform-Implementierung in Block 4 Phase A nutzt bewusst Variante A (tenant-prefixed:kora_<tenant_id>_<chatbot_id>,kora_<tenant_id>_shared) — stabiler, debuggabler, tenant-scoped. Das Fundament §6.1 soll in v5.2 auf die Implementierung nachgezogen werden. - Vorschlag: Scope-Review von Block 8 (Tenant-UI, 44h) und Block 13 (Konnektor-Subsystem, 48h — überschneidet sich möglicherweise mit 13a+13b = 48h). Plus Block 11 Widget-Aufwand konsolidieren. Ergebnis als v5.2- Revision einpflegen, Warning-Admonition entfernen.
Integration-Learnings¶
Erkenntnisse aus Block-Umsetzungen, die für spätere Blöcke relevant bleiben. Diese sind KEINE offenen Todos, sondern "gewusst-wie"-Notizen.
L-B2-01 — Keycloak-Realm-SMTP: KC_SMTP_* konfiguriert nur Bootstrap-Mails, nicht Per-Realm-SMTP¶
Problem: Die KC_SMTP_*-ENV-Variablen setzen den SMTP-Kontext nur
für Keycloak-interne System-Mails (Bootstrap-Admin, Lost-Password im
Master-Realm). Für realm-spezifische Mails (execute-actions-email,
Password-Reset in kora-platform/kora-tenants) muss jeder Realm
einen eigenen smtpServer-Block im Realm-Export-JSON enthalten —
sonst antwortet Keycloak mit 500 "No sender address configured in
the realm settings for emails".
Lösung: Pro Realm-JSON einen smtpServer-Block mit host, port,
from, fromDisplayName, auth: "false", ssl: "false",
starttls: "false" definieren. Für Prod können die Werte per
${KC_SMTP_HOST}-Platzhalter interpoliert werden (Keycloak
unterstützt das beim Realm-Import).
Relevanz für spätere Blöcke:
- Block 17 (Tenant-SSO-Self-Service): Bei IdP-Provisioning pro Tenant prüfen, ob der Tenant einen abweichenden SMTP-Kontext braucht.
- Block 12 (Provisioning):
execute-actions-emailfür Initial-Tenant- Admin braucht funktionierenden Realm-SMTP-Block inkora-tenants.
L-B2-02 — Composite-Rollen im Realm-JSON brauchen explizite Deklaration¶
Problem: defaultRole.composites.realm: ["tenant-viewer"] direkt im
defaultRole-Feld des Realm-Exports wird von Keycloak 26 beim Import
nicht aufgelöst. Die Composite-Rolle entsteht leer, neue User
bekommen keine Default-Permissions.
Lösung: Die Composite-Rolle explizit als Realm-Role in
roles.realm[] definieren (composite: true + composites.realm:
["tenant-viewer"]). Das defaultRole-Feld referenziert dann nur noch
den Namen. Siehe kora-tenants-realm.json
ab "default-roles-kora-tenants".
Relevanz für spätere Blöcke:
- Alle künftigen Realm-Änderungen, die Default-Rollen anpassen (z. B. Block 17 bei Shadow-User-Creation).
L-B2-03 — docker logs | grep -q unter set -euo pipefail triggert SIGPIPE¶
Problem: grep -q beendet sich nach erstem Match und schließt den
Upstream-Pipe. Unter set -o pipefail wertet Bash das als Fehler,
der Smoke-Test-Script bricht ab — obwohl das gesuchte Muster
gefunden wurde.
Lösung: Entweder grep -c verwenden (voller Scan, Exit 0 immer)
und das Ergebnis mit [[ "$count" -gt 0 ]] prüfen, oder das
Pipeline-Output vorher in eine Variable capturen
(logs=$(docker logs ... 2>&1 || true)) und dann greppen. Letzteres
ist robuster, wenn die Input-Größe bekannt klein ist.
Relevanz für spätere Blöcke:
- Alle künftigen Smoke-Test-Scripts (ab Block 3 relevant).
- Kandidat für Aufnahme in
CLAUDE.mdals Convention.
L-B2-04 — psycopg2-ImportError durch Auto-Import in db/__init__.py¶
Problem: Block 1 hat db/session.py (sync, für Alembic) über
db/__init__.py re-exportiert. Im API-Container (ohne psycopg2)
schlug der Import der async App fehl, weil bereits das Importieren
des Models-Tree das Sync-Session-Modul zog.
Lösung: Sync-Session-Re-Exports aus db/__init__.py entfernt.
Alembic importiert explizit from kora_platform.db.session import
Base — die App nutzt ausschließlich db/async_session.py. Keine
gemeinsame Import-Pfad-Oberfläche für sync und async.
Relevanz für spätere Blöcke:
- Alle Module, die sowohl sync (Alembic, CLI-Tools) als auch async (App) Code brauchen — Import-Pfade explizit trennen.
L-B3-02 — admin-cli Session in Master-Realm läuft in 60s ab¶
Problem: Der admin-cli-Client im Keycloak-Master-Realm hat einen Default-Access-Token-Lifespan von 60 Sekunden. Bei längeren Bootstrap-Skripten (~20+ Admin-API-Calls) läuft der zu Beginn gefetchte Token mittendrin ab, alle nachfolgenden Calls erhalten 400/401.
Lösung für Block 3: gen-test-tokens.sh hat eine refresh_auth()-Funktion, die vor jedem logischen Block (enable_direct_grants, ensure_user_*, enable_vendor_breakglass) einen frischen Master-Token holt. Nicht optimal, aber robust.
Relevanz für spätere Blöcke:
- Block 12 (Provisioning): Tenant-Anlage ruft Keycloak-Admin-API mehrfach. Entweder das 60s-Limit pragmatisch akzeptieren und refreshen oder einen dedizierten
kora-platform-admin-Service-Account mit längerem Token-TTL anlegen. - Generell: keine lange laufenden Admin-Scripts gegen Keycloak ohne Token-Refresh-Strategie schreiben.
L-B3-03 — SET LOCAL via asyncpg braucht inline-UUID, keine Bind-Params¶
Problem: asyncpg nutzt Simple-Query-Mode für PostgreSQL-SET LOCAL-Statements, die keine Prepared-Statement-Parameter akzeptieren. Der Versuch SET LOCAL app.current_tenant_id = :tid mit {"tid": ...} scheitert mit InvalidTextRepresentationError.
Lösung für Block 3: UUID als uuid.UUID validieren (via isinstance-Guard) und dann per f-String ins SQL einliegen: text(f"SET LOCAL app.current_tenant_id = '{tid}'"). Der Typ-Guard verhindert SQL-Injection, weil nur echte UUID-Objekte akzeptiert werden.
Relevanz für spätere Blöcke:
- Alle Routen, die
request_scoped_session(tenant_id=...)nutzen — Inlining läuft über die bereits gebaute Helper-Funktion. - Bei Operator-Routen mit explizitem
SET LOCALnach dem Pfad-Parameter: derselbe Inline-Trick mit UUID-Guard. Imapi/dependencies/tenant_context.py::tenant_context_setup(via_set_rls_context) bereits konsistent umgesetzt. (Vor dem Block-7.1a-Doku-Fix stand hier eine Referenz aufroutes/tenants.py:_scope_operator_branch— der Helper-Name existiert im Repo nicht, Historie liegt im tenant-context-Dependency.)
L-B3-04 — Keycloak Composite-Default-Role inkludiert tenant-viewer¶
Problem: Die Default-Rolle default-roles-kora-tenants im kora-tenants-Realm
ist als Composite konfiguriert und referenziert tenant-viewer. Jeder User im Realm
hat dadurch mindestens Viewer-Zugriff, auch wenn man ihm explizit keine Rolle
zuweist oder tenant-admin entzieht — die Composite-Mitgliedschaft bleibt. Entdeckt
bei Smoke-Test 9 aus dem Pre-Block-4-Cleanup: Der Test musste beide Rollen (die
zugewiesene plus die Composite-Referenz in der Default-Role) temporär entfernen,
um den 403-Fall zu reproduzieren.
Relevanz für spätere Blöcke:
- Block 12 (Provisioning): "Zugriff entziehen" durch Rollen-Entzug reicht nicht —
User über
enabled=falsedeaktivieren oder aus der Composite entfernen. - Block 17 (Tenant-SSO-Self-Service): Bei IdP-Shadow-Usern ist das gewünschtes Verhalten: jeder neue Shadow-User bekommt automatisch Viewer-Baseline, Tenant-Admin eskaliert bei Bedarf.
- Keine Schwachstelle, sondern dokumentierbares Verhalten. Relevant beim Role-Management-UI-Design.
L-B3-05 — BusyBox-wget mit irreführendem "bad address" bei funktionalem Docker-DNS¶
Problem: wget in BusyBox-/Alpine-basierten Containern hat einen eigenen
minimalen DNS-Resolver, der nicht zuverlässig mit Docker-DNS zusammenarbeitet.
Das Ergebnis wget: bad address '<service>:<port>' ist in dem Fall ein
False-Negative — der Name lässt sich via Docker-DNS trotzdem korrekt auflösen,
nur BusyBox-wget sieht ihn nicht. Entdeckt bei Prometheus-Cross-Network-
Reachability-Test (chore-prometheus-scrape).
Verifikations-Muster für zukünftige Container-Reachability-Tests:
- Bevorzugt:
nslookup <service>(zeigt echte DNS-Auflösung) odergetent hosts <service>(falls im Container verfügbar) - Alternative:
curlstatt BusyBox-wget, falls installiert wgetals Reachability-Beweis nur in Distros mit vollwertigem Resolver (Debian-Basis, etc.)
Relevanz für spätere Blöcke: Alle Cross-Network-Diagnosen in Alpine-/ BusyBox-basierten Containern. Vermutlich auch relevant bei Qdrant-/Redis- Diagnosen zukünftig (beide nutzen Alpine).
L-B3-06 — Docker Single-File-Bind-Mounts halten an Inode fest¶
Problem: Wenn eine Host-Datei durch ein Tool (Editor, sed, Claude Codes
Edit-Tool, mv-Replace) neu geschrieben wird, wechselt die Inode. Ein
Docker-Single-File-Bind-Mount hält aber am ursprünglichen Inode fest — der
Container sieht weiterhin den alten Inhalt. docker compose restart reicht
nicht, nur docker compose up -d --force-recreate <service> (oder
vollständiger Container-Neubau) löst das auf.
Bereits zweimal beobachtet:
- mkdocs-Config (
mkdocs-kora.yml) im Pre-Block-4-Cleanup/mkdocs- Hygiene-Kontext - Prometheus-Config (
monitoring/prometheus.yml) beim Scrape-Job-Setup
Workaround: --force-recreate nach jeder Config-Änderung, bis die
Umstellung auf Dir-Mounts erfolgt (TODO-Cleanup03-01).
Langfristige Lösung: Single-File-Bind-Mounts komplett vermeiden.
Dir-Mount mit --config-file-Flag im Container-Command ist die robustere
Variante (wie bei mkdocs-Hygiene umgesetzt).
Relevanz für spätere Blöcke: Alle zukünftigen Config-Änderungen in
Services mit Single-File-Bind-Mounts. Proaktive Suche:
grep -rnE ":.*\.(yml|yaml|conf|json):ro" docker-compose*.yml.
L-B5-01 — SQLAlchemy-Identity-Map vs. RETURNING bei ON CONFLICT DO UPDATE¶
Problem: Der Block-5-Cleanup-Upsert (INSERT ... ON CONFLICT DO
UPDATE ... RETURNING ...) gab eine hydrierte TenantModule-Instanz
zurück, die bei einem zweiten Assign innerhalb derselben Session die
alten Werte (z. B. enabled_by vom ersten Aufruf) zeigte — obwohl
das RETURNING die neuen Felder korrekt lieferte. Ursache: SQLAlchemys
Identity-Map liefert die bereits gecachte ORM-Instanz zurück, ohne die
RETURNING-Werte darauf zu syncen.
Lösung: execution_options={"populate_existing": True} auf dem
session.execute(stmt, ...) zwingt SQLAlchemy, die RETURNING-Werte
auf die existierende Identity-Map-Instanz zu schreiben. Im Block-5-
Cleanup (Commit 4a19966) per Test-Failure-Debugging gefunden und
dokumentiert.
Relevanz für spätere Blöcke:
- Alle künftigen Upsert-Patterns (Block 13 Konnektoren — Credentials- Upsert; Block 14 Deployment — Migration-State-Upsert) müssen dasselbe Flag setzen, sobald derselbe Service im Request-Lauf mehrmals auf dieselbe Row trifft.
- Sichtbare Symptome: Stale-Daten in der API-Response, aber der DB-
State ist korrekt. Identity-Map-Caching-Bugs sind ohne
populate_existingschwer zu finden, weil nur die zweite Operation in derselben Session stale wird.
L-B7-01 — .local-TLD lehnt email-validator als Special-Use ab¶
Problem: EmailStr (pydantic[email], → email-validator) lehnt
nach RFC 6761 alle Special-Use-TLDs ab, darunter .local. Test-Fixtures
mit user@test.local werfen ValueError: special-use or reserved name
und produzieren irreführende 422er.
Lösung: Test-Emails auf @example.com stellen (RFC 2606, explizit
für Tests reserviert). Produktion ist unbetroffen, weil echte Kunden-
E-Mails kein .local nutzen.
Relevanz für spätere Blöcke: Alle künftigen Tests/Smokes, die
EmailStr-Felder befüllen — nicht nur Tenants, sondern auch Block 12
(Provisioning mit Admin-Email), Block 17 (Tenant-SSO-Self-Service mit
IdP-Contact-Email). Default-Fixture-Email-Domain auf @example.com
setzen.
Abgeschlossen (Archiv)¶
v1.3.0-D1 Frontend-Polish-Welle — 16 TODOs in 3 Familien + 2 D2-Last-Mile (Branch platform/v1.3.0-d1-frontend-polish, 2026-05-01)¶
Zweite Welle der v1.3.0-Cleanup-Sequenz, direkt im Anschluss an D2. A11y-Polish, Foundation-Helpers, Composable-Caching, Hygiene und Frontend-Wiring auf die in D2 neu gebauten Aggregate-Endpoints.
Phase D1.1 — Foundation-Helpers
- TODO-Block-7-1b-07, -7-2-07, -7-2-03: Neuer
frontend/operator-ui/src/utils/formHelpers.tsmittrimEqual(whitespace-only-tolerantes Edit-Diff) undlistsEqual(order- aware list-equality). TenantsEditPage und TemplatesEditPage nutzen beide Helpers, statt jede Page eigene Trim-/Compare-Konvention zu haben. PATCH-Diff schicktnotes: nullnicht mehr nur, weil der User Whitespace angehängt hat. - TODO-Block-7-3-05:
flattenError(err)incomposables/useApi.ts— reduziert roheApiError-Objekte auf{status, message, detail}. Pydantic-422-Listen werden zu einer human-lesbaren Detail-Zeile gejoint. TenantsListPage Bulk-Soft- Delete und TenantModulesSection Bulk-/Single-Toggle nutzen flattenError jetzt für Toast-Detail-Zeilen.
Phase D1.2 — Component-Patterns
- TODO-Block-7-2-01: ListInput.vue bekommt A11y-Polish:
aria-live="polite"-Region annonciert Add/Remove/Reorder, Keyboard- Reorder via Alt+↑/↓, sichtbare ↑/↓-Move-Buttons. TestsAlt+ArrowDown reorders an item one position lower,Alt+ArrowUp at the top is a no-op,renders a polite live-region for screen-reader statusergänzt. - TODO-Block-7-1b-06:
useConfirmist auf eine FIFO-Queue umgestellt. Überlappendeask()-Calls landen hintereinander, statt den ersten still mitfalseabzuwürgen. Testqueues a second ask while the first is open and serves them in orderersetzt den vorherigen Auto-Reject-Test.
Phase D1.3 — Backend-induzierte Endpoints + D1.4 — D2-Frontend-Last-Mile
- TODO-Block-7-3-02 last-mile:
useModuleruftGET /api/v1/platform/modules/{id}direkt — kein Full-List-Fetch - Client-Filter mehr. ModulesDetailPage-Spec asserted explizit gegen die neue Pfad-Form, plus 404-Pfad.
- TODO-Block-7-3-03 last-mile:
useTenantModulesmigriert auf den D2-Aggregate-EndpointGET /api/v1/platform/tenant-modules?tenant_id=...&include_unassigned=true. TenantModulesSection feuert nicht mehr zwei parallele Requests und mergt clientseitig — ein Roundtrip liefert die fertige „Modul × ist-aktiv-für-Tenant"-Sicht. Testissues exactly one aggregate request, not parallel registry+state callsbeweist es. - TODO-Block-7-3-04: Optimistic-Update in TenantModulesSection.
patchRowpatcht die einzelne Aggregate-Row vor dem POST/DELETE,restoreRowrollt zurück bei Fehler. Testsflips badge optimistically on activate before the assign POST resolvesundrolls back optimistic activate when the POST rejectsdecken beide Pfade ab. - TODO-Block-7-2-04: TemplatesCreatePage entfernt Empty-Lists aus dem POST-Payload — Audit-Rows tragen die Lists nur noch, wenn der User sie wirklich gefüllt hat.
- TODO-Block-7-3-06: TenantModulesSection zeigt die volle
enabled_by-UUID als HTML-title-Tooltip auf dem trunkierten 8-Char-Display. Testrenders enabled_by full UUID as title attributeasserted die UUID im title-Attribut.
Phase D1.5 — Composable-Caching
- TODO-Block-7-1b-05:
useTenantshat jetzt einen Module-Scope SWR-Cache (Stale-While-Revalidate, key = serialisierte List- Params, 30s TTL). Mutationen rufeninvalidateCache. Tests decken first-load (network), warm-cache (no network), mutation- invalidate, und stale-revalidate ab; TenantsListPage-Spec ruftinvalidateCache()inbeforeEach, damit der Module-Level-Cache zwischen Tests nicht leakt.
Phase D1.6 — Hygiene
- TODO-Block-7-1b-01: E2E-Seed-Pfad in
useAuth.applyE2eSeed()ist hinterimport.meta.env.VITE_E2E_MODE === "true"gegated. Dockerfile.platform akzeptiertARG VITE_E2E_MODE, docker- compose.platform.yml setzt ihn auf${VITE_E2E_MODE:-true}, damit die luki-ai-Instanz die Playwright-Suite weiterhin fahren kann. Production-Build (z. B. eine zukünftige GA-CI-Pipeline) überschreibt mit leerem Wert; ein DOM-Clobbering-Angriff (<form id="__KORA_E2E_SEED__">injektiert vormain.ts) kann den Auth-State dann nicht mehr kapern. - TODO-Block-7-2-06:
mintTokensine2e/helpers.tsist async — der vorher synchrone Spin-Loop im Backoff blockiert den Worker- Event-Loop nicht mehr. Alle E2E-Spec-Caller (8 Specs) sind aufawait mintTokens()angepasst. - TODO-Block-7-1b-08:
frontend/operator-ui/PORTING.mdist nachdocs-kora/docs/blocks/block-7-1b-porting.mdverschoben (pergit mv). Operator-ui/README.md verlinkt die neue Position; mkdocs.yml hat eine neue nav-Sektion „Blocks (Pre-Flight & Porting)".
Phase D1.7 — Cosmetic
- TODO-Block-7-3-07: TenantsCreatePage-Description und TenantsDetailPage-Audit-Card ohne Block-Nummer-Referenzen (vorher „Block 7.3 / Block 7.4"). Footer war bereits durch den Layout-Refactor 2026-04-28 entfernt; D1 räumt die letzten veralteten Block-Strings im UI auf.
Verifikation
- Operator-UI Vitest: 161 Tests (vorher 130) ✅
- Operator-UI Lint: 0 errors, 77 pre-existing warnings (alle in TenantBrandingPage.vue, nicht von D1 berührt) ✅
- Operator-UI Type-Check: clean ✅
- mkdocs strict build: clean (Phase 6 verifiziert)
Datenpunkt für E + zukünftige Wellen
- Refined-Schätzung: ~14.5h
- Real-Aufwand: ~3.5h Implementation + ~0.5h Test-Fix + ~0.5h Docs
- Quote: ~30 % (näher am Plan als D2 mit 23 %, weil Frontend- Refactors typischerweise mehr Vue-/Test-spezifische Reibung haben).
- D2-Foundation (Aggregate-Endpoint, Modules-Detail-Endpoint, flattenError-Pattern) hat sich direkt ausgezahlt — D1.3+D1.4 waren reine Wiring-Arbeit, keine Backend-Iteration nötig.
v1.3.0-D2 Backend-Polish-Welle — 17 TODOs in 4 Familien (Branch platform/v1.3.0-d2-backend-polish, 2026-05-01)¶
Erste Welle der v1.3.0-Cleanup-Sequenz. Audit-Pattern-Konsolidierung, Schema-Hygiene, Bulk-Hardening, Service-Endpoints. Foundation für v1.3.0-D1 (Frontend-Polish) und v1.3.0-E (Operator-Per-Chatbot-Page).
Phase D2.1 — Foundation
- TODO-Block-7-NN-01: Zentraler Scope-Guard-Helper
kora_platform.api.dependencies.scope_guards. Vier Routen-Files (operator_tenants,operator_audit,tenant_modules,chatbot_templates) ziehen jetzt zentralen Helper statt lokaler Kopien._require_any_scopeund_require_tenant_owns_chatbotinchatbot_templates.pybleiben lokal (template-spezifisch). - TODO-Block-7-NN-04 + -5g: Alembic 0010 installiert PG-Trigger
set_updated_at()auftenants,tenant_modules,platform_modules. Service-Code setztupdated_atnicht mehr Python-seitig.update_tenantundsoft_delete_tenantrufen jetztawait session.refresh(tenant), damit der trigger-bumped Wert beim Caller ankommt. Eine Migration für beide Spalten gemeinsam — das war der Discovery-Bündel-Punkt #4 auscleanup-welle-discovery-2026-05-01.md.
Phase D2.2 — Bulk-Refactor
- TODO-Block-7-4-01 + -7-4-08:
bulk_soft_delete_tenantsundbulk_assign_to_tenantnutzen jetztWHERE id IN (...)-Pre-Validate plus single Multi-Row-Statement statt N Per-Item-Roundtrips. Pydanticmin_length=1, max_length=MAX_BULK_ITEMS=500in beiden Bulk-Routen. Empty-List ergibt jetzt 422 statt 400 (Teststest_bulk_soft_delete_empty_list_422,test_bulk_assign_modules_empty_list_422und je ein Über-Cap-Test ergänzt). Frontend-Progress-Indicator ist mit dem Cap-Hardening obsolet — kein eigener Fix nötig.
Phase D2.3 — Audit-Hygiene
- TODO-Block-7-NN-02:
TenantService.list_tenantsnutztcount(*) OVER ()-Window-Function in einer Single-Query. Empty-Page-Fallback per Count-Only-Query (Window liefert nur Werte, wenn die Page selbst Rows hat). - TODO-Block-7-NN-03:
TenantService.changed_fieldssnapshotet Before-Werte in plain-dictvor dem Diff. Entkoppelt das Audit- Delta vom ORM-State, sodass eine zukünftige Bulk-Update-Variante nicht den Audit-Trail aliased. - TODO-Block-7-4-02: CSV-Export
(
GET /api/v1/platform/audit/export.csv) mappt jetztactor_keycloak_id,ip_address,session_idin den Header. Testtest_csv_export_includes_forensic_columnsergänzt. - TODO-Block-5e: Integration-Test
test_audit_failure_rolls_back_mutationsimuliertOperationalErrorimwrite_platform_auditund prüft, dass die umgebendecreate_tenant-Mutation rollbacked (DB-Row nicht persistiert).
Phase D2.4 — Service-Endpoints
- TODO-Block-7-3-02:
GET /api/v1/platform/modules/{module_id}— Single-Modul-Detail. Operator-UI muss nicht mehr die volle Liste fetchen + clientseitig filtern. - TODO-Block-7-3-03:
GET /api/v1/platform/tenant-modules— Aggregate-Endpoint mittenant_id-Query-Param undinclude_unassigned-Flag. Cross-Tenant-Fan-out ohnetenant_idist 400 (Block-12-Trigger). - TODO-Block-5f:
DELETE /api/v1/tenants/{id}/modules/{module_id}prüft jetzt zuerst Tenant-Existenz vor dem DELETE auftenant_modules. Vorher war ein typo'd Tenant-ID-Path-Param ein stilles No-Op (idempotente Semantik), jetzt 404.
Phase D2.5 — Polish
- TODO-Block-7-NN-06: ILIKE-Metachar-Escape im
TenantService.list_tenants-Search-Pfad. Helper_escape_ilikeescapt\\,%,_. Testtest_search_escapes_ilike_metacharsbeweist, dass_safe-Suche nicht mehrsafe-Slugs zieht. - TODO-Block-7-NN-07: Test
test_vendor_can_list_with_include_deleteddeckt den Vendor-Pfad mitinclude_deleted=true. - TODO-Block-7-4-03: Neue Doku-Page
operations/audit-conventions.md— „eine Audit-Zeile pro Bulk-Aktion"-Konvention plus Anonymous- Actor-Pfad und CSV-Forensik-Felder. - TODO-Block-7-4-06:
datetime.utcnow()→datetime.now(UTC)im CSV-Filename-Builder (Py3.12-Deprecation). - TODO-Block-7-4-07:
_validate_date_range-Helper lehntdate_from > date_tomit422 date_from_after_date_toab. Unit-Teststest_validate_date_range_accepts_ordered_rangeund_rejects_inverted_rangeergänzt.
Real-Aufwand: ~5h vs. Refined-Schätzung 22h (≈ 23 % Quote — deutlich besser als die 60 %-Erwartung der Cleanup-Welle-Discovery, weil Audit-/Service-Pattern aus Block 8/11 voll wiederverwendbar waren und viele TODOs Single-File-Touch waren). Datapoint für D1- und E-Erwartung.
Test-Stand: 87/87 grün (vorher 82/82, +5 neue D2-Tests). Migrationen: Alembic 0009 → 0010 (beide Up/Down idempotent).
TODO-Platform-14 — Template-Audit-Trail-Konsolidierung (Branch chore/todo-platform-14-template-audit, 2026-04-30)¶
Audit-Trail-Lücke aus Block-8.2-Discovery geschlossen. Template-
Mutations (Create/Update/Deactivate) schreiben jetzt
platform_audit_log-Einträge analog zu Tenants und Modules.
Foundation für Block 8.4 (Chatbots-CRUD) — Pattern „aus dem Heft"
verfügbar, kein neuer Drift in 8.4.
Audit-Actions implementiert:
| Action | Route | Details-Schema |
|---|---|---|
template.created |
POST /api/v1/operator/templates |
{id, display_name, language, is_active} |
template.updated |
PATCH /api/v1/operator/templates/{id} |
{before: {...changed only}, after: {...changed only}} (only-if-diff via changed_fields-Helper) |
template.deactivated |
DELETE /api/v1/operator/templates/{id} |
{id, display_name, language} |
template.cloned |
POST /api/v1/operator/templates/{id}/clone |
bereits seit Block 8.2 (8c5a37d) |
Re-Aktivierung (PATCH is_active=true) wird nicht als eigene
Action geloggt, sondern als template.updated mit
details.before.is_active=false, details.after.is_active=true-Diff.
Lesbarer Audit-Trail ohne Action-Inflation.
Helper: write_platform_audit aus _platform_audit.py
wiederverwendet (kein neuer template-spezifischer Helper). Pattern
aus Tenants/Modules etabliert.
Routes umgestellt: admin_session() →
request_scoped_session(tenant_id=None, bypass_rls=True) — gleiche
Transaktion für Mutation + Audit. Vor TODO-14 nutzten 3 von 4
Mutations admin_session() ohne Audit-Hook.
Service-Erweiterung:
ChatbotTemplateService.changed_fields(before, payload) als
statische Methode (Spiegelung aus TenantService.changed_fields).
Liefert (before_delta, after_delta) mit nur tatsächlich geänderten
Feldern.
Field-Diff-Strategie: Volltext für alle Fields inkl.
suggested_system_prompt. Maximale Real-Länge 8.5KB
(meldeschein-Template) → JSONB-Audit-Detail OK, kein Hashing nötig.
Live-Smoke verifiziert:
- POST smoke14 → HTTP 201 + audit-log template.created mit
{id, display_name, language, is_active}.
- PATCH smoke14 (display_name + language) → HTTP 200 + audit-log
template.updated mit korrekten before/after-Deltas.
- DELETE smoke14 → HTTP 204 + audit-log template.deactivated
mit Snapshot-Details.
- 3 audit-log-Einträge sichtbar via direkter SQL-Query auf
platform_audit_log WHERE entity_id='smoke14'.
- Smoke-Template nach Verifikation aufgeräumt (DELETE FROM
chatbot_templates WHERE id='smoke14').
Tests:
- 2 neue Service-Unit-Tests (
changed_fields_returns_only_diff,changed_fields_empty_diff_when_no_changes). Gesamttest_chatbot_template_service.py21/21 grün. - Existing 19/19 Tests blieben grün (keine Regression durch Session-Pattern-Switch).
- Live-Smoke deckt Audit-Insert-Pfad ab. Pytest-Integration-Tests
für
template.created/updated/deactivated-Audit-Reads sind Block-8.4-Voraussetzung (TODO-Platform-12-Pytest-Profil-Trennung würde Integration-Test-Suite-Vereinfachung bringen).
Aufwand: ~1.5h (geschätzt 2–3h, im Korridor).
Foundation für Block 8.4: Chatbots-CRUD übernimmt Pattern direkt:
request_scoped_session(bypass_rls=True) + write_platform_audit mit
changed_fields-Diff für PATCH. Audit-Actions:
chatbot.created/updated/deleted/restored nach gleichem Schema.
Out-of-Scope (separate Blöcke): Helper-Konsolidierung
(_write_module_audit / _write_tenant_audit / direkter
write_platform_audit divergieren leicht — Cleanup-Block für
Helper-Vereinheitlichung wenn AVS-Bedarf). Retroaktive Audit-
Einträge für historische Mutations (kein Audit-Theater, ehrlicher
Cut mit diesem Merge).
Block 8.1 / TODO-UX-03 — Verifikations-only (Branch platform/block-8.1-tenant-edit-ui, 2026-04-30)¶
Befund: Block 8.1 enthielt keine Implementation. Discovery zeigte,
dass alle UX-03-Anforderungen bereits seit Block 7.1b/7.4 vollständig
implementiert waren — die Block-8-Verschiebung im UX-04-Block
(platform/ux-04-language-filter-and-ux-03-deferral) basierte auf der
ungeprüften Annahme, eine Backend-Edit-UI fehle.
Existing Implementation (verifiziert):
- Backend:
PATCH /api/v1/platform/tenants/{tenant_id}mit_require_operator-Scope-Gate,TenantUpdate-Pydantic-Schema (slug-immutable, EmailStr-Validation),TenantService.update_tenant changed_fields(before, payload)-Diff. Audit-Log-Actiontenant.updatedmitdetails={"before": {...}, "after": {...}}— geschrieben nur bei echter Änderung.- Frontend:
TenantsEditPage.vue(227 Zeilen) mit Slug-disabled- Tooltip, Display-Name-Required-Validation, EmailStr-Format-Check, Notes-Textarea, Dirty-State-Logic, Inline-422-Errors, Toast on success, Redirect zur Detail-Page.useTenant().update()-Composable (statt separatemuseTenantUpdate). Detail-Page-Edit-Button funktional (goEdit → /tenants/{id}/edit). - Schema-Felder:
tenantshatid, slug, display_name, status, contact_email, notes, status, deleted_at, created_at, updated_at. Editable im PATCH-Schema:display_name,contact_email,notes. Slug ist explizit immutable (Block-7.1b-Konvention).
Was Lutz' Phase-2b-Form-Spec zusätzlich nannte, aber Schema- Realität nicht hergab:
language-Feld auftenants— wurde in UX-04-Discovery (2026-04-29) als Stop-Trigger Szenario iii / Konzept-Drift markiert. Sprach-Achse ist auf Content-Level (chatbot_templates.language,chatbots.language). Tenant- Sprache als Stammdatum hätte Doppel-Achse erzeugt — bewusst nicht ergänzt.contact_phone,contact_address— kein dokumentierter User-Story-Bedarf übercontact_email+noteshinaus.
Live-Smoke-Verifikation (2026-04-30):
PATCH /api/v1/platform/tenants/<bench-tenant-a-id>mit Operator-Token: HTTP 200, korrekt geupdated.platform_audit_log-Row erscheint:action=tenant.updated,actor_role=operator,actor_user=bench-operator-admin,actor_keycloak_id=NULL(TODO-Auth-NEU-Workaround),details.before= {"display_name": "Benchmark Tenant A", "contact_email": null},details.after={"display_name": "Bench Tenant A (smoke)", "contact_email": "ops-a@example.com"}.- Backend-Tests grün: 43/43
(
test_operator_tenants_api,test_tenant_service,test_audit_service). - Frontend-Tests grün: Vitest TenantsEditPage 3/3, Playwright
tenants-crud 2/2 (deckt
happy path: login → list → create → detail → edit → deleteab).
Bench-Demo-Daten gepflegt: bench-tenant-a und bench-tenant-b
haben jetzt contact_email (ops-a@example.com / ops-b@example.com)
über direktes SQL-Update (kein zusätzlicher Audit-Pollution-Eintrag).
display_name wurde auf den Original-Wert Benchmark Tenant A
zurückgesetzt nach dem Smoke-PATCH.
Re-Diagnose-Lesson (für künftige Block-Moves):
TODO-UX-03 wurde im UX-04-Block in Block-8-Scope verschoben unter der Annahme, eine Backend-Edit-UI fehle. Discovery in Block 8.1 zeigte: PATCH-Endpoint, TenantUpdate-Schema, TenantsEditPage und Detail-Page-Edit-Button existieren seit Block 7.1b/7.4 vollständig. Die Walkthrough-Beobachtung „Kontakt-Spalte leer" war ein Daten-Pflege-Befund, kein UI-Lücken-Befund. Lesson: Status-Annahmen über existing Code via
find/grepverifizieren, bevor Block-Moves beschlossen werden.
Aufwand: ~30min (Verifikation + Doku, kein Code-Change).
Out-of-Scope: Schema-Erweiterung (language, contact_phone,
contact_address) bleibt verworfen. Multi-Language-Templates
weiterhin Block-8.2-Scope auf Content-Achse.
Block 8.0 + TODO-Platform-13 (Branch platform/block-8.0-foundation, 2026-04-30)¶
Block-8.0-Foundation gelegt: Module-Auto-Seed (TODO-Platform-13 erledigt) plus erweitertes Tenant-UI-Skelett mit Read-Only-Pages für Templates, Module, Profil. Vorbereitung für Block 8.1 (UX-03 Tenant-Edit-UI), 8.2 (Multi-Language-Templates), 8.3 (Provisioning- Self-Service).
TODO-Platform-13 Implementation: Variante 1 (Lifespan-Hook)
gewählt. src/kora_platform/main.py ruft ensure_seed_modules()
nach init_engines() über admin_session() auf, idempotent via
existing INSERT ... ON CONFLICT (id) DO NOTHING. Failure ist
non-fatal (Container darf hochkommen, Health-Probes machen
unsauberen Zustand sichtbar — kein Crash-Loop, der Operator-Triage
behindert).
Live-Verifikation:
- Fresh-Deploy auf leere platform_modules (DELETE FROM
platform_modules + restart api): Log zeigt
module_seed_completed inserted=4.
- Idempotenz: zweiter Restart liefert
module_seed_completed inserted=0. DB-State nach beiden Starts
identisch (chatbot, confluence, internal_analytics, ticket_escalation).
Backend-API (Block 8.0 neu):
GET /api/v1/tenants/me/modules— Tenant-Self-Read aller nicht-internen Module mit Aktivierungs-Status. LEFT-JOIN-Variante via neuer Service-MethodeModuleService.list_all_modules_with_tenant_status. Auth-Matrix validiert:tenant=200,operator=403,vendor=403,unauth=401.is_enabledfällt für nicht-zugewieseneis_always_on-Module (scope='core') aufTruezurück (chatbotist immer aktiv).- Nicht angetastet:
/api/v1/tenant/templates(existing aus Block 5) und/api/v1/tenants/me(existing aus Block 4) reichen für Block-8.0-Frontend-Anforderungen.
Tenant-UI (Block 8.0 neu):
/templates— Read-Only-Liste der für den Tenant aktiven Templates. Empty-State, Sprach-Label-Mapping (de/en → Deutsch/Englisch), Version-Counter./modules— Read-Only-Liste aller Module mit Aktivierungs- Status-Badge. Scope-Labels (Kern/Extern aktivierbar/Intern)./profile— Read-Only-Stammdaten (Slug, Display-Name, Status, Tenant-ID, Angelegt). Edit-Button disabled mit Hinweis auf Block 8.1.- Sidebar in 4 Groups restrukturiert:
- „Übersicht" (Dashboard)
- „Verwaltung" (Templates, Module)
- „Konto" (Profil)
- „In Vorbereitung (Block 8.x)" (Chatbots, Wissensquellen, Branding, Feedback — disabled) Pattern gespiegelt aus Operator-UI; keine Re-Invention.
Validierung:
- Backend Pytest 211/211 (kora-platform-Pfad)
- Tenant-UI Vitest 16/16 (existing tests, keine Regression)
- Tenant-UI Build: 107.08 kB raw / 41.50 kB gzip (3 neue Pages je
~2 kB)
- Operator-UI Vitest 128/128 (Regression-Check, keine Änderungen
dort)
- verify-auth-stack.sh: 57/59 (TODO-Auth-NEU bleibt monitored)
- Mkdocs Strict-Build: Exit 0
- Live-Smoke: GET /tenants/me/modules mit Tenant-A-Token liefert
3 nicht-interne Module mit korrekten Status-Werten
Out-of-Scope (separate Blöcke): UX-03 Tenant-Edit-UI (8.1), Multi-Language-Template-Workflow (8.2), Provisioning-Self-Service (8.3). TODO-Platform-11/12 bleiben offen.
TODO-UX-04 (Branch platform/ux-04-language-filter-and-ux-03-deferral, 2026-04-29)¶
Sprach-Filter in der Templates-Liste implementiert (Operator-UI). Ein gepaarter Block mit TODO-UX-03-Verschiebung nach Block 8.
Discovery-Kontext (wichtig für Lutz' künftige Prompts): Der
ursprüngliche Prompt zielte auf eine Tenant-Liste-Sprach-Filter-
Variante mit Migration tenants.language. Discovery zeigte: kein
language-Feld in tenants, aber chatbot_templates.language
existiert bereits (VARCHAR(5), Default de, 2 Rows alle de).
Lutz korrigierte den Plan auf TODO-UX-04-wie-im-Repo (Templates,
Szenario i, keine Migration). Discovery-First-Pattern hat den
falschen Plan verhindert.
frontend/operator-ui/src/pages/TemplatesListPage.vue—<select>Dropdown „Sprache" neben „Inaktive anzeigen", WerteAlle / Deutsch / Englisch. Dynamische Empty-Title und Empty-Body wenn Filter aktiv und keine Treffer („Keine Englischen Templates" + Filter-Hinweis). Footer-Note erweitert um Filter-Status.frontend/operator-ui/src/composables/useTemplates.ts—applySearch()umbenannt zuapplyFilters(); neuer SettersetLanguage(value); watch aufparams.value.languagetriggertapplyFilters()(clientseitig, kein Re-Fetch wie beisearch).frontend/operator-ui/src/types/template.ts—TemplateListParams.language?: stringergänzt.- Filter-Strategie: clientseitig analog zu
search— Templates- Volumen ist klein (≪ 100), Backend-Endpoint liefert bare list ohne Pagination. Kein Backend-Refactor nötig. - Sprach-Werte: 2-Buchstaben-Codes (
de,en) —chatbot_templates.languageist VARCHAR(5) und damit locale-fähig (de-AT,en-US), aber Filter arbeitet mit den tatsächlich vorhandenen Daten (allede), keine Locale-Detection. - Vitest: 3 neue Tests in
pages/__tests__/TemplatesListPage.spec.ts(Dropdown-Render, client-side Filterde, Empty-State mit Sprach-Hinweis beien). 7/7 grün, Gesamt-Suite 128/128. - Filter-Charakter: strukturell sinnvoll, funktional „tot" —
alle live-Templates sind
de. Filter ist Vorbereitung für Multi-Language-Templates ab Block 8.
TODO-UX-03 nach Block 8 verschoben: Severity hochgestuft auf M2/50, Block-8-Scope-Item, Roadmap-Block-8 um „Tenant-Stammdaten- Edit-UI" erweitert (mit Datenpunkt: TODO-UX-03 ist strenggenommen Operator-UI, Block 8 sonst Tenant-UI — Block-Scope wird leicht erweitert; alternativ ein eigener Block 8.1 falls Block 8 sich aufbläht).
Out-of-Scope (separate Blöcke): Multi-Language-Content-Refactor. i18n-Stack für Operator-UI (Block Phase D). Backfill-Skript für existierende Tenants. Locale-Erweiterung über 2-Buchstaben-Codes hinaus.
TODO-UX-01 + TODO-UX-NEU (Branch platform/ux-polish-status-consistency-and-cleanup, 2026-04-29)¶
Zwei Polish-Items in einem Mini-Run vor v1.0.0-GA: Status-Konsistenz
zwischen Tenant-Liste und Tenant-Detail (TODO-UX-01) und Cleanup-
Pattern-Erweiterung um op-vendor-*-Pollution (TODO-UX-NEU,
opportunistisch mit-erledigt, weil im Cleanup-Mini-Run als
Datenpunkt erkannt — kein vorheriger TODO-Lifecycle).
TODO-UX-01 Discovery-Befund: Variante A mit Twist. Liste rendert
ein Badge basierend auf tenant.deleted_at (Boolean: Aktiv/
Soft-Deleted), Detail rendert tenant.status raw (active/
inactive). TODO-UX-01-Beschreibung selbst war leicht ungenau —
die Liste rendert nicht status, sondern deleted_at. Es gibt
keine StatusPill-Component (Variante C wäre Component-
Wiederverwendung; nicht zutreffend).
TODO-UX-01 Fix: Neuer util
frontend/operator-ui/src/utils/tenantStatus.ts
mit formatTenantStatus(tenant) — kombiniert deleted_at (dominiert)
+ status-Mapping zu deutschem Label. Detail-Page nutzt es; Liste
bleibt unverändert per Scope-Boundary "Keine UI-Änderung außerhalb
der Tenant-Detail-Status-Anzeige". Anti-Drift-Check: Templates und
Modules-Section haben eigene konsistente is_active/is_enabled-
Boolean-Pattern — kein Whack-a-Mole, der sonst nötig gewesen wäre.
utils/tenantStatus.ts— pure function, fallback auf Roh-Wert für unbekannte Status-Strings. Composable-Verzeichnis war unpassend (keinref/Reactivity),types/tenant.tsist für Type-Shapes — daher neuesutils/-Verzeichnis. Erste shared util im operator-ui (vorherformatIsoals Local-Function-Duplikate).- Vitest: 4 Unit-Tests in
utils/__tests__/tenantStatus.spec.ts(active/inactive/Soft-Delete-dominiert/unknown-fallback) + 2 neue Tests inpages/__tests__/TenantsDetailPage.spec.ts(humanisiert + Soft-Deleted-Pfad). Alle 9 Tests grün. - Playwright: kein neuer Test — die Discovery-Klärung ergab nur Text-Konsistenz (kein Pill im Detail), Vitest deckt das vollständig.
TODO-UX-NEU op-vendor-Cleanup: Skript-Pattern erweitert.
scripts/cleanup-test-data.sh— neuer Flag--include-test(analog--include-bench). Aktiviertop-vendor-%zusätzlich zue2e-%. Pattern erweiterbar für weitere künftige Auth-/Service-Test-Klassen ohne neuen Flag.- Makefile — neue Convenience-Targets
cleanup-test-data-include-test(Dry-Run) undcleanup-test-data-include-test-apply(mit Confirm). Bestehender Pass-Through-Pfad viaargs=weiter unterstützt für--include-bench --include-test-Kombinationen. - Runbook:
operations/test-data-cleanup.mdum neuen Abschnitt + Tabellen-Updates erweitert. - Live-Smoke: Dry-Run zeigte
op-vendor-write-0bd2ee27; Apply löschte 1 Tenant; Idempotenz-Check „Keine Test-Daten gefunden — nichts zu tun"; Audit-Log unverändert (515 Zeilen vor + nach) — Scope-Boundary respektiert.
Bonus-Datenpunkt aus diesem Block: TODO-Platform-10 (Mkdocs-
Build-Static) erstmals produktiv genutzt via make docs-kora-deploy
— funktioniert, ~5–8s end-to-end, keine Issues.
Out-of-Scope (separate Blöcke): Status-Mapping in der Tenants-
Liste auf formatTenantStatus migrieren (würde Verhalten ändern für
status: 'inactive'-Tenants, aktuell als „Aktiv" angezeigt solange
deleted_at null — separater Diskussionspunkt). i18n-Infrastruktur.
TODO-UX-03 (Kontakt-Spalte) und TODO-UX-04 (Sprach-Filter).
TODO-Platform-04 (Branch platform/todo-platform-04-iptables-remote-node, 2026-04-29)¶
iptables-Setup auf Remote-vLLM-Node (192.168.0.223) codifiziert.
Vorher: nur in CLAUDE.md/userMemories beschrieben, kein Skript im
Repo — bei Hardware-Wechsel oder zweiter Node ging der Schutz
verloren. Lutz-Entscheidung: Option C — Reverse-Engineering vom
bekannt-aktiven Live-Zustand. Kein Live-Deploy in diesem Block.
- Skript:
infra/remote-node/docker-firewall.sh - Pattern: Delete-then-Insert-Position-1 (Live-bewährt seit 1.5 Wochen) statt Check-then-Append. Reihenfolge wird explizit durch Reverse-Iteration kontrolliert
- Modi: default (apply, für systemd ExecStart),
--install(apply + systemd setup),--remove,--dry-run - IPv4 + IPv6 asymmetrisch wie im Live-Zustand: IPv4 hat RETURN-Whitelist + DROP-Catchall pro Port; IPv6 nur DROP (internes LAN-Routing nutzt ausschließlich IPv4)
- Konfig-Block:
ALLOWED_SOURCE_IP="192.168.0.7"undPORTS_STR="8000 9835"als Env-Override-Hooks für zweite Nodes set -euo pipefail+IFS=$'\n\t'strict-mode- Root-Check + DOCKER-USER-Existenz-Check (letzter im Dry-Run übersprungen, damit Skript-Logic auf Hosts ohne Docker prüfbar bleibt)
- Service-Template:
infra/remote-node/docker-firewall.service Type=oneshot,RemainAfterExit=yesPartOf=docker.serviceintentional — Service stoppt mit Docker, startet mit Docker-Restart wiederExecStart=/usr/local/sbin/docker-firewall.sh(fester Pfad, Runbook beschreibt Kopier-Schritt)- Runbook:
deployment/remote-vllm-node-setup.md - Architektur-Kontext, Voraussetzungen, Aufruf-Reihenfolge, drei
Verifikations-Pfade (iptables-Inspection als Standard ohne
Drittel-Host, plus optional Container-Negative-Test mit
docker run --network bridge), Fehlerbehebung, Rollback, zweite-Node-Anpassung - Routing-Page §4 (GPU-Inferenz-Topologie): „Doku-only"-Blockquote durch Verweis auf Skript + Runbook ersetzt; Hinweis auf das Delete-then-Insert-Pattern
- mkdocs-Nav um Runbook-Eintrag unter Deployment erweitert
- Phase-1-Discovery-Datenpunkte (Lutz-bestätigt am 2026-04-29):
- Live-Counter zeigen 998K Pakete auf Port-8000-RETURN, 324K auf Port-9835-RETURN, 0 Pakete auf den DROP-Regeln seit Setup- Zeit (kein unauthorisierter Zugriff seit 1.5 Wochen)
curl http://192.168.0.223:8000/healthvon luki-ai: 200 ✅curl http://192.168.0.223:9835/metricsvon luki-ai: 200 ✅- Kein Drittel-LAN-Host für externen
nmapverfügbar → Smoke- Strategie auf Inspection-Pfad ohne externen Test fixiert - Validierung:
bash -nPASS; vier Dry-Run-Modi (default--dry-run,--remove --dry-run,--install --dry-run, Env-OverridePORTS_STR=...) liefern erwartete Aufruf-Sequenzen - Out-of-Scope (separate Blöcke): Live-Deploy, Disaster-Recovery- Test auf frischem Node, WireGuard/VPN-Schicht, SSH-Lockdown
TODO-Platform-10 (Branch platform/todo-platform-10-mkdocs-static-hosting, 2026-04-29)¶
mkdocs-Live-Deployment vom serve-Mode auf vorgebauten Static-Site
mit nginx:alpine umgestellt — der Inotify-Bug, der mehrere Wochen
Doc-Merges auf docs.kora.luki-net.org unsichtbar gemacht hat, ist
damit struktur-eliminiert. Lutz-Entscheidung: Option 3 (Build-static-
Pattern) aus dem TODO-Eintrag — sauber, expliziter Build-Step, kein
Long-Running-Build-Werkzeug im Container.
- Compose-Refactor:
docker-compose.platform.ymlServicemkdocs - Vorher:
squidfunk/mkdocs-material:latest serve --no-livereload, Mount./docs-kora:/docs:ro, Port 8237→8000 - Nachher:
nginx:alpinemit Custom-Config, Mounts./docs-kora/site:/usr/share/nginx/html:round./infra/docs/nginx-docs.conf:/etc/nginx/conf.d/default.conf:ro, Port 8237→80 - NPMplus zero-touch: Service-Name
mkdocsund Port8237unverändert — kein NPMplus-Reload nötig - mkdocs.yml:
site_dir: ../site-kora→site_dir: site(innerhalb docs-kora/, gitignored, nebendocs/inspizierbar) - nginx-Config:
infra/docs/nginx-docs.confmit gzip,try_files-Routing für mkdocs-Pretty-URLs, sane Security- Header (X-Content-Type-Options, X-Frame-Options, Referrer-Policy) - Make-Targets:
docs-kora-build,docs-kora-deploy,docs-kora-clean. Build läuft über Throwaway-docker run --rmmitsquidfunk/mkdocs-material:latest-Image, RW-Mount nur für die Build-Phase - Healthcheck:
wget --spider http://127.0.0.1/(Alpine-Quirk:localhostresolved auf IPv6, nginx hört nur IPv4) - Runbook:
deployment/docs-deployment.md— Architektur, Build-vs-Serve-Container-Trennung, Standard-Update- Flow (make docs-kora-deploy), Troubleshooting für Build-Strict- Errors und 404-nach-Deploy-Edge-Cases - Legacy-Hinweis: Der frühere
deployment/mkdocs-container.md-Stub wurde in Reorg 2026-05-09 gelöscht (redundant zur neuen Runbook-Page; Cross-Refs aus älteren Karten bleiben über die Audit-Trail-History nachvollziehbar) - gitignore: explizit
docs-kora/site/+site-kora/(Legacy) ergänzt; das globalesite/deckte beide Pfade implizit ab - Out-of-Scope (separate Blöcke): Auto-Deploy via Webhook
(Block 14 / CI-Setup),
--no-livereload-Diagnose-Wrapper für Local-Editor-Workflows, Doku-Search-Index-Optimierung
TODO-UX-02 (Branch platform/todo-ux-02-cleanup-test-data, 2026-04-27)¶
Test-Daten-Pollution in der Live-Operator-UI gefixt: 68 e2e-Tenants und 35 e2e-Templates aus akkumulierten E2E-Läufen entfernt.
- Skript:
scripts/cleanup-test-data.sh— read-only Default (Dry-Run),--applymit Confirm-Prompt,--include-benchopt-in fürbench-*,--yesfür CI/non- interactive, transaktionalerDELETEmit Auto-Rollback bei Fehler. - Pre-Flight-Sicherheits-Check: Skript prüft alle 10 NO-ACTION-FK-
Tabellen (
platform_audit_log,chatbot_sources,sync_jobs,chat_sessions,chat_messages,feedback,document_versions,evaluation_runs,vendor_access_log,chatbots.template_id) vor dem DELETE — bricht ab, falls dort Rows zu löschende Tenants/ Templates referenzieren. CASCADE-FKs (tenant_packages,tenant_branding,chatbots,credentials,tenant_modules,evaluation_questions) werden automatisch mitgenommen. - Make-Targets:
make cleanup-test-data(Dry-Run),make cleanup-test-data-apply(mit Confirm-Prompt). Beide nehmen optionalargs=--include-bench. - Runbook:
operations/test-data-cleanup.mdmit Aufruf-Patterns, Edge-Case-Handling (NO-ACTION-Pre-Flight- Failure, DB-Connection-Fehler), Verifikations-Schritten. - Audit-Log unangetastet: Per Scope-Boundary werden keine
Audit-Zeilen gelöscht. History bleibt vollständig erhalten — auch
von gelöschten Tenants. Audit-Zeilen referenzieren den Tenant-
UUID weiter über die
entity_id-Spalte (string).tenant_id-FK- Spalte ist nullable und im Discovery-Befund leer für e2e/bench (operator-scope-Audit-Writes nutzentenant_id=NULL). - Discovery-Datenpunkte:
- 68 e2e-Tenants in 5 Pattern-Klassen (
e2e-bulk-1/2,e2e-conflict-*,e2e-happy-*,e2e-mod-*) - 35 e2e-Templates in 2 Pattern-Klassen (
e2e-conf-*,e2e-tpl-*) - 2 bench-Tenants (
bench-tenant-a,bench-tenant-b) mit 200 cascadenden Chatbots - 0 Audit-Zeilen mit
tenant_idIN e2e-tenants - Alle 9 weiteren NO-ACTION-FK-Tabellen 0 Rows für e2e
- Live-Verifikation:
- Vor Apply: 68 Tenants + 35 Templates + 455 Audit-Einträge
- Nach Apply (default, ohne
--include-bench): 0 e2e-Tenants- 0 e2e-Templates + 2 bench-Tenants (preserved) + 455 Audit-Einträge unverändert
- Idempotenz-Lauf: „Keine Test-Daten gefunden — nichts zu tun"
- Bench-Tenants-Behandlung: Default-Pattern nimmt sie nicht
mit.
--include-benchist explizit Opt-In. Empfehlung: nur dann einsetzen, wenn für eine Demo wirklich kein Test-Tenant in der UI auftauchen soll —gen-test-tokens.shlegt sie beim nächsten E2E-Lauf wieder an. - Verworfene Pfade (out-of-scope):
- Pytest-Hook nach E2E-Lauf (sauberster Pfad, aber Workflow- Eingriff in alle Test-Suites — eigener Block bei Bedarf)
- CI-Integration (manuelles Operator-Tool, nicht automatisiert)
- Cleanup von Audit-Log oder Keycloak-Usern
TODO-Platform-09 (Branch platform/todo-09-auth-stack-verification, 2026-04-27)¶
Systematische Auth-Stack-Verifikation über alle sechs Schichten und
beide Realms (kora-platform, kora-tenants). Schließt drei
zusätzliche Drift-Datenpunkte (#5–#7) und löst die in -06/-07/-08
offenen Tenant-UI-Folge-Fixe gemeinsam mit auf.
Lieferung:
- Soll-Zustand-Doku:
docs-kora/docs/operations/auth-stack-soll-zustand.md(10 Kapitel, beide Realms, alle 6 Schichten — Realms, Clients, Client-Scopes, Realm-Roles, Frontend-Konfig, Init-Skripte, NPMplus-Routing, Test-Skripte, Drift-Disziplin) - Discovery-Bericht:
frontend/operator-ui/PORTING-09.mdmit Schicht-Inventar Diff-Tabelle Soll vs. Ist, Drift-#5/#6/#7- Hypothesen + Pfad-Entscheidung - Verifikations-Skript:
scripts/verify-auth-stack.sh— read-only, idempotent, exit != 0 bei Drift, drei Modi (compact / verbose-v/--json), 57 Checks über sechs Schichten
Adressierte Drifts:
- Drift #5 — operator-ui Pfad-Doppelung beim post-login redirect:
useAuth.login()speichertewindow.location.pathname(volle Pfadkomponente inkl. Router-Base/admin/operator/); CallbackPage'srouter.replace(target)mitcreateWebHistory("/admin/operator/")produzierte/admin/operator/admin/operator/.... Fix:pathRelativeToBase()-Helper schneidet die Router-Base ab. Identischer Fix für tenant-ui mitROUTER_BASE="/tenant". - Drift #6 — OAuth
state mismatchbei Re-Login: Bestätigt als Folge von #5 (kein eigener Fix nötig). - Drift #7 — Realm-JSON
defaultClientScopesaspirativ: Beide Realm-JSONs deklarierten für alle ClientsdefaultClientScopes: ["openid","profile","email","roles","kora-scope"], obwohl die OIDC-Standard-Scopes als Client-Scope-Objekte gar nicht existieren. Live-Zustand war seit Anfang sauber["kora-scope"]. Re-Import wäre an dem aspirativen JSON gescheitert. Fix perjq— 6 Client-Einträge in 2 Realm-JSONs auf["kora-scope"]konsolidiert,optionalClientScopesanalog leergezogen wo["address","phone"]- Aspirativ stand. - TODO-Platform-07 — tenant-ui Auth-URL-Drift: Default in
tenant-ui/src/composables/useAuth.tsvon"/auth"auf"https://auth.kora.luki-net.org"umgestellt (analog -06). - Tenant-UI Scope-Patch: Scope-Request von
"openid profile email"auf"openid kora-scope"(analog -08).
Test-Erweiterungen:
frontend/operator-ui/e2e/auth-redirect.spec.tsum zweiten Test erweitert (Drift #5 —post_login_redirect-Path-Stripping prüft perroute.abort+ sessionStorage-Polling)frontend/tenant-ui/e2e/auth-redirect.spec.tsneu (analog operator-ui) inkl. realm-konformem Scope-Assertion + Path-Stripping- ESM-Polyfill-Fix in
frontend/tenant-ui/e2e/helpers.ts(__dirnameausimport.meta.url) — ohne den Fix konnte der neue Spec gar nicht geladen werden (pre-existierender Bug, narrow gefixt um Test-Suite zu unblocken)
Test-Status nach Fix:
- Operator-UI Vitest 100/100, Playwright 10/10
- Tenant-UI Vitest 8/8, Playwright 2/2 neue Auth-Specs (existing
template-update.spec ist
test.skip) verify-auth-stack.sh: 57/57 Checks grün, 0 Drifts
Routing-Page §6: Drift-Pattern auf 7 Datenpunkte finalisiert,
Lessons-Learned um Router-Base-Schicht-Erkenntnis erweitert,
Anti-Drift-Disziplin via Verify-Skript + Soll-Zustand-Doku
formalisiert. Status /admin/operator/* und /tenant/* 🟢.
Browser-Test: Lutz live verifiziert — siehe Rückmeldung.
TODO-Platform-07 (im Rahmen von TODO-Platform-09 mit-aufgelöst, 2026-04-27)¶
Tenant-UI hatte denselben Default-Bug wie Operator-UI vor TODO-Platform-06:
baseUrl: import.meta.env.VITE_KEYCLOAK_BASE_URL ?? "/auth". Im Browser
resolved das auf platform.kora.luki-net.org/auth/realms/kora-tenants/...
— FastAPI-404. Im Rahmen von TODO-Platform-09 als Teil der systematischen
Auth-Stack-Verifikation mit-gefixt:
- Default in
frontend/tenant-ui/src/composables/useAuth.tsauf"https://auth.kora.luki-net.org"umgestellt - Plus Drift-#5-Analog-Fix (
pathRelativeToBase()mitROUTER_BASE="/tenant") - Plus tenant-ui Scope-Patch (analog -08):
"openid kora-scope"statt"openid profile email" - Anti-Regression-Spec
frontend/tenant-ui/e2e/auth-redirect.spec.ts(analog operator-ui) prüft Auth-URL + Scope + Path-Stripping - ESM-Polyfill-Fix in
frontend/tenant-ui/e2e/helpers.tsals notwendiger Side-Quest (__dirnameunter"type": "module"brauchtfileURLToPath(import.meta.url))
TODO-Platform-08 (Branch platform/fix-operator-ui-oidc-scopes, 2026-04-26)¶
operator-ui-OIDC-Scope-Drift gefixt: Frontend fragte
scope=openid profile email an, obwohl die Realms (kora-platform,
kora-tenants) bewusst nur die Custom-Client-Scope kora-scope und
offline_access als Client-Scope-Objekte haben. Keycloak antwortete
mit error=invalid_scope, der Login-Callback war nicht durchführbar.
- Lösungs-Variante: Option C — Frontend-Scope-Request in
useAuth.ts:125von"openid profile email"auf"openid kora-scope"reduziert. Begründung:kora-scopeenthält bereits Mapper fürpreferred_username,email,realm_access.rolesundaud=kora-apiund liefert damit alle Claims, die Operator-UI konsumiert.openidbleibt als OIDC-Protokoll-Marker (id-Token- Issuance). Realm bleibt minimal — keine neuen Client-Scope-Objekte, keine Mapper-Duplikation. - Verworfene Pfade:
- A1 (Realm-Erweiterung um
profile/email-Scopes + Mapper- Konfiguration in beiden Realms) — ~2–3h Aufwand, Realm-Architektur wäre vom Custom-Scope-Pattern abgewichen - A2 (wie A1, nur
kora-platform) — Asymmetrie zu kora-tenants, widerspricht Drift-Discipline - B (
scope=kora-scopeohneopenid) — verzichtet auf id-Token, OAuth2-statt-OIDC-Mode - Discovery-Datenpunkte:
- Live-Keycloak
kora-platformClient-Scopes:[offline_access, kora-scope]— keine OIDC-Standard-Scopes - Live-Keycloak
kora-tenantsClient-Scopes: identisch - Realm-JSON ↔ Live
kora-scope-Mapper: kein substantieller Drift (nur Auto-Defaults beim Read) kora-scope-Mapper liefert:preferred_username,email,realm_access.roles,aud=kora-api(kora-tenants zusätzlichgroups)- Token-Endpoint-Verifikation:
scope=openid+kora-scope→ HTTP 302 (Keycloak-Login-Form)scope=openid+profile+email→ callback miterror=invalid_scope&error_description=Invalid+scopes:+openid+profile+email
- Anti-Regression:
auth-redirect.spec.tsum Scope-Param-Assertions erweitert —URL.searchParams.get("scope")muss exakt"openid kora-scope"sein,"profile"und"email"dürfen nicht im Scope auftauchen. Robust gegen Encoding-Varianten (+vs.%20). - Routing-Page: §6 Drift-Pattern um Datenpunkt #4
(Frontend-OIDC-Scope ↔ Realm-Architektur) erweitert,
Lessons-Learned-Block formalisiert. Status-Marker
/admin/operator/*bleiben 🟢 (durch dieses Fix funktional validiert), aber TODO-Platform-09 trackt die noch ausstehende systematische Auth-Stack-Verifikation aller Schichten und beider Realms — vor Block 8 zwingend. - Tenant-UI-Drift: symmetrischer Fix für
tenant-uiist bewusst NICHT in diesem Commit — wird über TODO-Platform-09 (siehe Archiv-Eintrag oben — am 2026-04-27 gemeinsam mit der TODO-Platform-07-Auth-URL-Korrektur und dem Verifikations-Skript abgehandelt). - Stellen-Inventar (Phase 1): Genau eine Code-Stelle relevant —
frontend/operator-ui/src/composables/useAuth.ts:125. Diescope:-Treffer in__tests__/-Dateien sind alle Module-Scope- Klassifikationen ("core"/"external_eligible"/"internal_only", Block-7.3-Domain), nicht OAuth-Scopes. Token-Refresh und Logout übergeben keinen expliziten Scope.
TODO-Platform-06 (Branch platform/fix-operator-ui-auth-subdomain, 2026-04-26)¶
Operator-UI nutzt nun auth.kora.luki-net.org als OIDC-Authorize-
Endpoint statt platform.kora.luki-net.org/auth/... — letzteres traf
das FastAPI-Backend (404), weil NPMplus dort keine Keycloak-Route hat.
Bug war seit Block 7.1b live, aber durch den E2E-Seed-Token-Hook
(window.__KORA_E2E_SEED__) in keinem Test sichtbar.
- Discovery: Default in
frontend/operator-ui/src/composables/useAuth.ts:28warimport.meta.env.VITE_KEYCLOAK_BASE_URL ?? "/auth". Vite-Build bekam keineVITE_KEYCLOAK_BASE_URL-Build-Arg im Dockerfile, also ging der relative/auth-Default ins Bundle. Im Browser resolved das aufplatform.kora.luki-net.org/auth/realms/...— FastAPI-404. - Fix: Default auf absolute URL
https://auth.kora.luki-net.orgumgestellt (moderne Keycloak-Pfad-Konvention ohne/auth/-Präfix — verifiziert per OIDC-Discovery:/realms/...= 200,/auth/realms/...= 404). - Realm-Pfad-Konvention: Keycloak 24+ ohne
/auth/-Präfix; das Frontend setzt nur die Base-URL, die OIDC-Library hängt/realms/<realm>/protocol/openid-connect/...selbst an. - Anti-Regression:
frontend/operator-ui/e2e/auth-redirect.spec.tsfängt perpage.routejede Cross-Origin-Navigation zur Auth-Subdomain ab und prüft die URL — explizit ohne Seed-Token-Hook. Zusätzlicher Anti-Bug-Check: Bug-Pfadplatform.kora.luki-net.org/auth/*wird mit 404 stub'd und darf vom Frontend gar nicht angesprochen werden. - Realm-JSON / Live-Keycloak unverändert: Der
operator-ui- Client im Realm-JSON enthält keine hartcodierten Auth-URLs, der Live-Client ist korrekt konfiguriert — nur die Frontend-Konfig war falsch. - README-Update:
VITE_KEYCLOAK_BASE_URL-Default aufhttps://auth.kora.luki-net.orgim Env-Var-Tabelle dokumentiert, Dev-Override-Hinweis (http://localhost:8236) aufgenommen. - Routing-Page: §1a
/auth/*als „nicht bedient" explizit, §6 Auth-Subdomain-Klarstellung, Source-of-Truth-Hinweisblock um den dritten Drift-Datenpunkt erweitert (Frontend-Konfig + Test-Suite-Realismus). - Lessons-Learned: Drei Drifts in drei Schichten (Realm-JSON ↔ Live-Keycloak via TODO-Platform-05; Frontend-Konfig ↔ NPMplus-Routing via TODO-Platform-06; Test-Suite-Realismus durch Seed-Hook-Bypass). Source-of-Truth-Disziplin gilt für jede Schicht einzeln. „Tests grün" reicht nicht, wenn die Tests den fehleranfälligen Pfad bypassen — Anti-Regression muss explizit den ungeseedeten Pfad treffen.
- Tenant-UI-Drift-Bestätigung (1d):
tenant-uihat denselben Default-Bug (useAuth.ts:27defaultet auf"/auth") — bewusst nicht im selben Commit gefixt, sondern als TODO-Platform-07 dokumentiert (am 2026-04-27 im Rahmen von TODO-Platform-09 archiviert — siehe oben). Das hilft datieren, wann der Drift entstanden ist: beide UIs wurden vom selben Pattern portiert, der Default-Bug ist also vor 7.1b im Tenant-UI-Vorbild eingewandert (Block 5 UI-Framework-Scaffolding) und in beiden Surfaces geerbt.
TODO-Platform-05 (Branch platform/fix-operator-ui-client-live-import, 2026-04-25)¶
operator-ui-Public-Client wurde im laufenden kora-platform-Realm
nachgezogen. Realm-JSON-Re-Import war wegen Confidential-Client-Secret-
Regeneration tabu — daher Init-Script-Pattern analog zu
create-audit-service-account.sh.
- Init-Script:
infra/keycloak/init-scripts/create-operator-ui-client.sh— extrahiert den Client-Block zur Laufzeit aus dem Realm-JSON, ruftkcadm.sh create clients -f. Idempotent: Re-Run prüft viakcadm get clients -q clientId=…, skippt wenn vorhanden, kein Drift-Reset. - Realm-JSON-Fix: Description gekürzt von > 300 auf ~245 Zeichen.
Original sprengte den Keycloak-DB-Constraint
CLIENT.DESCRIPTION varchar(255). Ohne Trim wäre kein Re-Import funktional gewesen. - Verifikation:
kcadm get clients -r kora-platform -q clientId=operator-uiliefert den Client mitenabled=true,publicClient=true,standardFlowEnabled=true,directAccessGrantsEnabled=false, vollständigen Redirect-URIs (Prod - Dev Vite + Dev API-only) und
kora-scopeals Default-Client-Scope. PKCE-S256-Authorize-Endpoint-Smoke (HTTP 200auf/auth?…&code_challenge_method=S256). - Routing-Page-Konsequenz: TLDR + §1a + §6 Status-Marker zurück von 🟡 auf 🟢. §6-Hinweisblock erweitert um Source-of-Truth-Modell („Realm-JSON deklariert, Init-Scripts garantieren idempotent").
- Runbook-Erweiterung:
deployment/operator-ui-client.mdAbschnitt „Drift-Vermeidung" mit Aufruf-Reihenfolge bei Realm-Reset und Verifikations-Befehl.
TODO-Block-5b / -5c / -5d (Branch platform/block-5-cleanup-audit-delta, 2026-04-24)¶
Audit-Delta-Differenzierung, REST-konforme Status-Codes und Race-Safety
für tenant_modules-Assigns in einem Cleanup-Commit erledigt.
- Service (
src/kora_platform/services/module_service.py): - Neuer
ModuleAssignResult-Dataclass (tenant_module,was_new,was_previously_enabled) — Provenance-Flags für die Route-Schicht. assign_to_tenantumgebaut aufINSERT ... ON CONFLICT (tenant_id, module_id) DO UPDATE ... RETURNING xmax = 0. Derxmax=0-Trick unterscheidet INSERT- von UPDATE-Pfad ohne zweiten Round-Trip; Race-Condition bei parallelen Assigns auf dieselbe Composite-PK ist damit geschlossen (kein IntegrityError → kein 500).execution_options={"populate_existing": True}auf demsession.execute(stmt, …), damit SQLAlchemys Identity-Map die RETURNING-Werte nicht durch die gecachte Vorversion überschreibt.- Separater
SELECT is_enabledvor dem Upsert fürwas_previously_enabled— tiny race window dokumentiert, wirkt sich nur auf Audit-Action-Klassifikation aus, nicht auf Daten. - Route (
src/kora_platform/api/routes/tenant_modules.py): - POST liefert jetzt 201 bei Create, 200 bei Update/Re-Enable (REST-
Konvention,
responses={200, 201}in OpenAPI dokumentiert). - Audit-Action differenziert per Flag-Matrix:
tenant_module.assigned(was_new=True),tenant_module.re_enabled(was_new=False, prev_enabled=False) undtenant_module.reassigned(was_new=False, prev_enabled=True).before-Delta entsprechend (None/{is_enabled: False}/{is_enabled: True}). - Tests:
- Unit: 4 neue Tests (
was_new/was_previously_enabledauf Create, Reassign, nach Revoke,enabled_by-Update), zwei bestehende auf.tenant_module-Accessor migriert. 21/21 grün. - Integration:
test_assign_is_race_safe_under_concurrent_calls— 20×asyncio.gatherauf dieselbe (tenant, module)-Kombination, exakt 1×was_new=True, keine Exceptions. 4/4 grün. - Smoke:
scripts/smoke-block5.shum Tests 8 (re-assign → 200 +reassigned), 9 (SET is_enabled=FALSE+ POST → 200 +re_enabled) und 10 (DELETE + POST → 201 +assigned) erweitert. 11/11 grün. - Code-Review-Report: Ein Deferred-Finding (Score 40,
TODO-Block-5g
—
updated_atim Upsert-SET). Keine ≥ 80-Findings. Scope-Boundary (keine neue Alembic-Migration, TODO-5e/-5f bleiben auf post-Block-7) eingehalten. - Akzeptanz: 25/25 Tests + 11/11 Smoke +
make redeploy-platform+ Strict-MkDocs-Build grün.
TODO-Block-5a (Branch platform/block-5-platform-modules, 2026-04-23)¶
ensure_seed_modules committete fremde Session → Service flusht jetzt,
Caller committet. Konsistent mit allen anderen Block-5-Services.
- Fix:
await session.commit()→await session.flush()insrc/kora_platform/services/module_seeds.py, Docstring ergänzt um „Caller owns the transaction". - Aufrufstellen angepasst: Kein Startup-Hook vorhanden; einziger
Caller
tests/unit/test_module_service.py::seeded_modules-Fixture kommt ohne Fixture-Änderung aus — der nachgelagertetenant_id- Fixture bzw. Cleanup-commit()handeln die Transaktion sauber ab. Integration-Test-Fixture benutzt eigenen SQL-INSERT, nicht betroffen. - Smoke-Skript zusätzlich idempotent:
scripts/smoke-block5.shergänzt umINSERT ... ON CONFLICT DO NOTHING-Setup, damit Seeds auch nach einem Unit-Test-Cleanup wieder da sind (vorher konnte der Smoke 404 werfen, wenn Unit-Tests zuvor liefen). - Akzeptanz: 17/17 Unit + 3/3 Integration + 7/7 Smoke grün.
TODO-Platform-02 (Branch platform/chore-platform-02-dev-deps, 2026-04-22)¶
pytest + pytest-asyncio permanent im Platform-api-Image.
- Variante (A): neue
test-Group in[project.optional-dependencies]inpyproject.toml(Subset vondev— nurpytest>=8.3+pytest-asyncio>=0.24, ohne Linter/Formatter/Audit-Tools). Dockerfile-Zeile aufpip install ".[test]"geaendert. Multi-Stage- Refactor bewusst nicht gewaehlt (Overkill fuer das kleine Delta). - Akzeptanz:
docker exec kora-platform-api pytest --versionfunktioniert direkt nachmake redeploy-platform, kein manuellespip installmehr noetig. 5/5 Integration-Tests + 38/38 Unit-Tests nach Rebuild gruen. - Image-Groesse: 8.78GB → 8.79GB (+10MB). Deutlich unter den
erwarteten +40MB, weil
pytest-asyncioals transitive Dependency bereits durch andere Pakete gezogen war und nur der kleine pytest-Hook-Overhead dazukommt.
TODO-Platform-01 (Branch platform/chore-platform-01-migrate-superuser, 2026-04-22)¶
Makefile-Wrapper fuer make migrate-platform und verwandte Targets.
- Variante (A):
PLATFORM_ALEMBIC := ALEMBIC_USE_SUPERUSER=1 alembic -c alembic.ini.platform— das Env-Prefix wird in der Makefile-Variable gesetzt, alle 5 Targets (migrate-platform,migrate-platform-down,migrate-platform-status,migrate-platform-new,migrate-platform-history) erben es. - Akzeptanz:
make -n migrate-platformzeigtALEMBIC_USE_SUPERUSER=1im Command; echter Testmake migrate-platform-statusohne Shell-Env liefert Current 0005 (head) + History ohne Permission-Fehler. - Offen bleibt als separater Task: Role-Permission-Rework auf
Block-1.5-Level. Der
kora_platform_migrator-Role hat weiterhin keinen Owner-Zugriff aufalembic_version— die pragmatische Lösung umgeht das, fixed es nicht. Der saubere Weg bleibt ein eigener Cleanup-Task, wenn Block-1.5-Setup ohnehin angefasst wird.
TODO-Cleanup03-02 (Branch platform/chore-cleanup03-02, 2026-04-22)¶
Compose-Env-Fragility via Makefile-Wrapper abgesichert.
- Variante (A): Makefile-Wrapper. Die bereits existierende
$(KORA_COMPOSE)-Variable (Makefile:105) wurde um zwei neue Targets ergänzt:platform-exec cmd="..."(Ad-hoc-Befehl via$(KORA_COMPOSE) exec api) undplatform-bootstrap cmd="..."(Master-PW-Bootstrap viadocker exec -e, scheitert früh bei fehlenden Bootstrap-Credentials). - Akzeptanzkriterien:
make -n-Dry-Runs aller Platform-Targets zeigen--env-file .env.platformim erzeugten Befehl. Live-platform-exec-Test läuft ohne Keycloak-RestartCount-Increment.platform-bootstrapmit gesetzter Env legt User an und löscht keinen bestehenden Container. Regression-Smokes (Block-3 12/12, Request-ID 2/2) grün. - Runbook:
deployment/compose-invocations.mdneu — Prinzip, Wrapper-Targets, was-zu-vermeiden, Diagnose-Workflow bei Crashloop-Verdacht. Bootstrap-Abschnitt indeployment/keycloak-service-account.mdaufmake platform-bootstrapumgestellt. Strict-Build-Pattern indeployment/mkdocs-container.mdaufmake docs-kora-buildumgestellt. - Bewusst ausgelassen:
COMPOSE_AVS-Wrapper für den AVS-Demo-Stack — post-Go-Live obsolet (Fundament §16), analog Cleanup03-01. Docker-Secrets-Migration bleibt v1.x-Kandidat (Overkill für v1.0.0).
TODO-Cleanup03-01 (Branch platform/chore-cleanup03-01, 2026-04-22)¶
Single-File-Bind-Mounts → Dir-Mounts umgestellt, Scope (a) minimal:
- Scope (a): Prometheus (
monitoring/prometheus.yml+monitoring/alertmanager/alerts.yml) auf gemeinsamen Dir-Mountmonitoring/prometheus/ → /etc/prometheus/umgestellt. Akzeptanzkriterium (Live-Edit-Test mitdocker restart, ohne--force-recreate) grün für beide Files. - Bewusst belassen: 10 weitere Single-File-Mounts im AVS-Demo-Stack (
docker-compose.yml, Zeilen 146, 195, 196, 237, 239, 240, 241, 264, 266, 404). Der Stack wird per Go-Live v1.0.0 komplett abgeschaltet (Fundament §16 „frische Leinwand") — Investition in Artefakte mit Ablaufdatum ist fehlallokiert. Details indeployment/bind-mount-discipline.md. - Runbook:
docs-kora/docs/deployment/bind-mount-discipline.mdneu, dokumentiert Prinzip, Status, Option-A/B-Muster für neue Services und das Live-Edit-Test-Akzeptanzkriterium.
TODO-B2-03 (Branch platform/b2-03-service-account, 2026-04-22)¶
Keycloak Service-Account statt Master-Password im laufenden API-Container.
- Client
kora-platform-auditimkora-platform-Realm angelegt via Init-Scriptinfra/keycloak/init-scripts/create-audit-service-account.sh(idempotent, Re-Run überschreibt Secret nicht). - Rechte strikt minimal: nur
realm-management.view-events. D3- Verifikation hatview-usersgestrichen — der Poller nutzt Event-Payload-Felder (userId,details.username), kein User-Enrichment-Call. audit_poller.py: Client-Credentials-Flow gegen interne Keycloak-URL, Token-Cache mitasyncio.Lock+ Double-Check-Pattern, 30s Safety-Margin vorexpires_in. Prometheus-Counteraudit_poller_auth_failures_total{reason}.cli/bootstrap.py:bootstrap-operator-adminliest Master-PW ausKC_BOOTSTRAP_ADMIN_USERNAME/PASSWORDstatt aus Settings — Invocation nur noch viadocker compose run --env-file .env.bootstrap.- Cleanup:
KORA_KEYCLOAK_ADMIN_USERNAME/PASSWORDentfernt ausdocker-compose.platform.yml,.env.platform,.env.platform.example,config.py..env.bootstrap.example+.gitignore-Eintrag neu. - Runbook:
docs-kora/docs/deployment/keycloak-service-account.md. - Integration-Test:
tests/integration/test_audit_service_account.py(Happy-Path, Deny-Path, Sad-Path).
Cleanup-02 (Branch platform/chore-cleanup-02, 2026-04-21)¶
Vier offene Todos aus Block 2 und Block 3 abgearbeitet, plus eine NPMplus-Alternative für B3-04.
| ID | Kurzbeschreibung | Fix-Ort |
|---|---|---|
| TODO-B3-03 | vendor_access_log.action: VARCHAR(64) → TEXT. Reversible Alembic-Migration 0004, Model-Update, Truncation [:64] im Middleware-Code entfernt. Fix vorgezogen vor Block 4, weil die dort entstehenden Chatbot-Subpfade 64 Zeichen schon im Basisfall sprengen. |
alembic/platform/versions/0004_vendor_access_log_action_text.py, src/kora_platform/db/models/audit.py, src/kora_platform/api/dependencies/tenant_context.py |
| TODO-B2-01 | Audit-Poller: Pagination bis Partial-Page oder Batch-Oldest ≤ Cursor, MAX_PAGES=50 als Safety-Rail, Log-Warning pro voller Seite. 5 Unit-Tests. |
src/kora_platform/services/audit_poller.py, tests/unit/test_audit_poller.py |
| TODO-B2-05 | Redis-Cursor-Key avs:audit:last_event_ts → kora:platform:audit:last_event_ts. Einmalige, idempotente Startup-Migration mit Overlap-Handling. In Produktion-Log verifiziert: "audit cursor migrated". |
src/kora_platform/services/audit_poller.py, tests/unit/test_audit_poller.py |
| TODO-B2-07 | platform_public_url, keycloak_base_url, keycloak_public_base_url auf pydantic.AnyHttpUrl. Alle Call-Sites mit str(...)-Cast gewrappt. AnyHttpUrl statt HttpUrl wegen Dev-Default http://localhost:8236. 5 Unit-Tests. |
src/kora_platform/config.py + Call-Sites in cli/bootstrap.py, api/dependencies/auth.py, api/health.py, services/audit_poller.py |
| TODO-B3-04 | /metrics IP-Allowlist als FastAPI-Dependency (statt NPMplus-Ebene, weil NPMplus-Config außerhalb dieses Hosts liegt). Default-Allowlist: 127.0.0.1/32, ::1/128, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Liest X-Forwarded-For (leftmost) bzw. request.client.host. Verifiziert: LAN-IP → 200, external IP (X-Forwarded-For: 8.8.8.8) → 403. |
src/kora_platform/config.py, src/kora_platform/main.py |
Verifikation: smoke-block3 12/12, smoke-request-id 2/2, integration
200×/0, 11 Unit-Tests grün, /metrics Allowlist live getestet.
mkdocs-Hygiene (Branch platform/chore-mkdocs-hygiene, 2026-04-21)¶
Kein Codefix, sondern Deployment-Layout — hier nur erwähnt, damit der Archiv-Zeitstrahl vollständig ist. Details im CHANGELOG.
- Config-Datei
mkdocs-kora.yml(Repo-Root) →docs-kora/mkdocs.yml. - Content-Dateien nach
docs-kora/docs/. - Docker-Compose: Dir-Mount statt Dir+Single-File → Bind-Mount-Inode-
Problem eliminiert,
docker restartreicht für Doku-Änderungen. - URL
/todo/→/offene-todos/.
Pre-Block-4 Cleanup (Branch platform/chore-pre-block4, 2026-04-21)¶
Der Cleanup-Commit behebt sieben aufgestaute Todos aus den Block-2- und
Block-3-Reviews. Die Original-Eintragstexte stehen in den Git-Commits
2c57172 (Block 2) bzw. d18a1fc (Block 3). Kurzfassung hier:
| ID | Kurzbeschreibung | Fix-Ort |
|---|---|---|
| L-B3-01 | Keycloak-Realm-Import legt Built-in-Mappers nicht an — drei Mapper (realm-roles, preferred_username, email) jetzt direkt in beiden Realm-JSONs unter kora-scope. Dev/Prod-Parität hergestellt. |
infra/keycloak/realms/*-realm.json |
| TODO-B2-02 | Correlation-ID-Middleware — UUID pro Request in structlog-ContextVar, im X-Request-Id-Header gespiegelt, im 500er-Response-Body. |
src/kora_platform/api/middleware/request_id.py (neu), main.py |
| TODO-B2-04 | Redis ConnectionPool in app.state statt neuer Connection pro /health/ready-Call. Ping bei Startup, saubere Shutdown-Sequenz (Client → Pool). |
src/kora_platform/main.py, api/health.py |
| TODO-B2-06 | refreshTokenMaxReuse: 0 + revokeRefreshToken: true auch in kora-tenants-realm.json (Refresh-Token-Rotation aktiv). |
infra/keycloak/realms/kora-tenants-realm.json |
| TODO-B2-08 | Makefile redeploy-platform wartet jetzt per until-Loop (max 60s) auf /health/live statt fixem sleep 10. |
Makefile |
| TODO-B3-01 | ContextVar-Reset-Pattern im Vendor-Pfad vereinheitlicht — current_tenant_id.set(None) mit Token-Reset im finally, kein Stale-Wert zwischen asyncio-Tasks. |
src/kora_platform/api/dependencies/tenant_context.py |
| TODO-B3-02 | TENANT_ROLES als hartes Gate aktiviert: Gruppenmitgliedschaft allein reicht nicht mehr, ein tenant-admin/editor/viewer muss vorliegen — sonst 403 no_tenant_role. |
src/kora_platform/api/dependencies/tenant_context.py |
Begleitend: scripts/gen-test-tokens.sh um die ensure_kora_scope_mappers-
Phase entschlackt (durch Realm-JSON-Fix redundant). Smoke-Tests
smoke-block3.sh um Tests 9 (no_tenant_role) + 10 (X-Request-Id round-trip)
erweitert; neuer scripts/smoke-request-id.sh für die Correlation-ID-
Verifikation gegen unauthenticated Endpoints. mkdocs-Nav thematisch neu
strukturiert (Start / Roadmap / Konzepte / Prozesse / Deployment /
Änderungen mit eingebettetem TODO-Link).
Letzte Aktualisierung: 2026-05-02 (TODO-Konzept-02-Lauf: §17.2a Reconciliation Nr. 7 mit Pattern-Reife-Quote-Trendlinie pro Block-Typ ergänzt, §17.5 Cleanup-Wellen separat ausgewiesen, Konzept v5.3.3 → v5.3.4. Plus Archiv-Sweep: 32 TODOs aus v1.3.0-D2/D1/Block-7.3-Familien auf „Erledigt" gezogen mit Merge-Hash-Verweis.) Nächste Review: Vor Block 11 (Widget-Integration) oder als Teil der Cleanup-Welle vor Block 13.