From 0e2b938fbd95c70f955a5a47f839fb03163cd947 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:03:57 +0200 Subject: [PATCH 01/10] Sprint-0-Grundlagen: Spec-Handover, Designprinzipien-Referenz, Medien optional. Co-authored-by: Cursor --- .cursor/rules/kairo-architecture.mdc | 18 + .env.example | 22 +- CLAUDE.md | 143 +++++-- README.md | 98 ++--- docker-compose.dev-env.yml | 13 +- docker-compose.yml | 12 +- docs/DEPLOYMENT.md | 29 +- .../Kairo_Architecture_References_v0.1.md | 61 +++ docs/reference/design-principles/README.md | 56 +++ .../alignment/DESIGN_PRINCIPLES_ALIGNMENT.md | 358 +++++++++++++++++ .../alignment/FAMILY_ENTITLEMENT_MODEL.md | 316 +++++++++++++++ .../mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md | 326 +++++++++++++++ .../DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md | 363 +++++++++++++++++ .../mitai/DATA_LAYER_DESIGN_PRINCIPLES.md | 359 +++++++++++++++++ .../FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md | 370 ++++++++++++++++++ .../MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md | 369 +++++++++++++++++ .../mitai/NAVIGATION_IA_DESIGN_PRINCIPLES.md | 346 ++++++++++++++++ .../mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md | 305 +++++++++++++++ .../REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md | 315 +++++++++++++++ .../UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md | 325 +++++++++++++++ .../shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md | 170 ++++++++ .../AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md | 140 +++++++ .../shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md | 114 ++++++ ...APABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md | 125 ++++++ .../CONTENT_REPORTS_DESIGN_PRINCIPLES.md | 117 ++++++ .../DASHBOARD_KPI_DESIGN_PRINCIPLES.md | 105 +++++ .../shinkan/DESIGN_PRINCIPLES_INDEX.md | 140 +++++++ .../EXERCISE_CATALOG_DESIGN_PRINCIPLES.md | 128 ++++++ .../MATURITY_MODELS_DESIGN_PRINCIPLES.md | 117 ++++++ .../shinkan/MEDIA_ASSETS_DESIGN_PRINCIPLES.md | 119 ++++++ .../MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md | 117 ++++++ .../NAVIGATION_IA_DESIGN_PRINCIPLES.md | 107 +++++ .../RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md | 114 ++++++ .../SKILL_SCORING_DESIGN_PRINCIPLES.md | 107 +++++ .../TRAINING_PLANNING_DESIGN_PRINCIPLES.md | 129 ++++++ .../shinkan/WIKI_IMPORT_DESIGN_PRINCIPLES.md | 118 ++++++ 36 files changed, 6041 insertions(+), 130 deletions(-) create mode 100644 .cursor/rules/kairo-architecture.mdc create mode 100644 docs/architecture/Kairo_Architecture_References_v0.1.md create mode 100644 docs/reference/design-principles/README.md create mode 100644 docs/reference/design-principles/alignment/DESIGN_PRINCIPLES_ALIGNMENT.md create mode 100644 docs/reference/design-principles/alignment/FAMILY_ENTITLEMENT_MODEL.md create mode 100644 docs/reference/design-principles/mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/DATA_LAYER_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/NAVIGATION_IA_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/mitai/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/CONTENT_REPORTS_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/DASHBOARD_KPI_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/DESIGN_PRINCIPLES_INDEX.md create mode 100644 docs/reference/design-principles/shinkan/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/MATURITY_MODELS_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/MEDIA_ASSETS_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/NAVIGATION_IA_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/SKILL_SCORING_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/TRAINING_PLANNING_DESIGN_PRINCIPLES.md create mode 100644 docs/reference/design-principles/shinkan/WIKI_IMPORT_DESIGN_PRINCIPLES.md diff --git a/.cursor/rules/kairo-architecture.mdc b/.cursor/rules/kairo-architecture.mdc new file mode 100644 index 0000000..057eb77 --- /dev/null +++ b/.cursor/rules/kairo-architecture.mdc @@ -0,0 +1,18 @@ + + +## Reference design principles + +Reference material lives in: + +- docs/reference/design-principles/mitai/ +- docs/reference/design-principles/shinkan/ +- docs/reference/design-principles/alignment/ + +These files explain why certain patterns exist. They are not implementation scope unless the Kairo Sprint-0 Principle Gate or an Architecture Decision explicitly pulls them into scope. + +Conflict order: +1. Kairo_Sprint0_Principle_Gate_v0.1.md +2. Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md +3. Jinkendo_Kairo_Product_Spec_v0.2.md +4. Alignment documents +5. Mitai/Shinkan individual principle documents diff --git a/.env.example b/.env.example index dda5441..8d38b4a 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,6 @@ # APP_URL=https://kairo.jinkendo.de # ALLOWED_ORIGINS=https://kairo.jinkendo.de # ENVIRONMENT=production -# KAIRO_MEDIA_HOST=/kairo-media -# MEDIA_ROOT=/app/media # ─── Typische Werte DEV (docker-compose.dev-env.yml) ───────────────────────── # DB_NAME=kairo_dev @@ -22,8 +20,6 @@ # APP_URL=https://dev.kairo.jinkendo.de # ALLOWED_ORIGINS=https://dev.kairo.jinkendo.de,http://192.168.2.49:3097 # ENVIRONMENT=development -# KAIRO_MEDIA_HOST=/kairo-media/dev -# MEDIA_ROOT=/app/media # ─── Ab hier: eine ausfüllbare Vorlage (bei uns meist Prod-Defaults) ─────────── DB_HOST=postgres @@ -56,15 +52,9 @@ APP_URL=https://kairo.jinkendo.de ALLOWED_ORIGINS=https://kairo.jinkendo.de ENVIRONMENT=production -# Medien (Docker Compose): KAIRO_MEDIA_HOST = Verzeichnis auf dem Host (Bind-Mount), -# MEDIA_ROOT = gleicher Pfad im Container (muss mit dem Mount-Ziel übereinstimmen). -KAIRO_MEDIA_HOST=/kairo-media -MEDIA_ROOT=/app/media - -MEDIAWIKI_API_URL=https://karatetrainer.net/api.php -MEDIAWIKI_USER=Jinkendo -MEDIAWIKI_PASSWORD=CHANGE_ME -MEDIAWIKI_CATEGORY_EXERCISES=Übungen -MEDIAWIKI_CATEGORY_SKILLS=Fähigkeitsbeschreibung -MEDIAWIKI_CATEGORY_METHODS=Methodenbeschreibung -MEDIAWIKI_CATEGORY_MODELS=Reifegradmodelle +# ─── Medien (optional, derzeit nicht aktiv) ─────────────────────────────────── +# Kairo benötigt aktuell keine Medien-Speicherung. Falls später nötig: +# 1. NAS-Freigabe auf dem Pi mounten (nicht lokal auf dem Raspberry!) +# 2. docker-compose.override.yml mit Bind-Mount ergänzen (siehe docs/DEPLOYMENT.md) +# KAIRO_MEDIA_HOST=/mnt/nas/kairo-media +# MEDIA_ROOT=/app/media diff --git a/CLAUDE.md b/CLAUDE.md index da3c2d2..63cee2a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,59 +1,120 @@ -# Kairo Jinkendo – Entwickler-Kontext +# CLAUDE.md – Jinkendo Kairo -## Projekt-Übersicht +Du arbeitest im Projekt **Jinkendo Kairo**. -**Kairo Jinkendo** (回廊 Jinkendo) — Schwesterprodukt in der **Jinkendo**-App-Familie (人拳道). -Domains: kairo.jinkendo.de · dev.kairo.jinkendo.de +Kairo ist der operative Program Director der Jinkendo-Produktfamilie. -**Status:** Infrastruktur (Docker, Gitea Actions, Deployment-Doku) ist eingerichtet. Anwendungscode folgt. +## Leitfrage -## Tech-Stack (geplant) +Welcher nächste Schritt bringt ein Vorhaben aktuell am wirkungsvollsten voran? -| Komponente | Technologie | -|-----------|-------------| -| Frontend | React 18 + Vite + PWA (Node 20) | -| Backend | FastAPI Python 3.12 | -| Datenbank | PostgreSQL 16 Alpine | -| Container | Docker + Docker Compose | -| Auth | Token-basiert + bcrypt (Familien-Standard) | +--- -**Ports:** Prod 3004/8004 · Dev 3097/8097 — nicht ändern ohne explizite Freigabe (Reverse Proxy/Fritz!Box). +## 1. Aktueller Entwicklungsstand -## Deployment +Sprint 0. -``` -Internet → Fritz!Box (privat.stommer.com) → Synology NAS → Raspberry Pi 5 (192.168.2.49) +Es geht noch nicht um die vollständige Fachanwendung, sondern um das Fundament. -Git Workflow: - develop → Auto-Deploy → dev.kairo.jinkendo.de (kairo-dev/, Port 3097/8097) - main → Auto-Deploy → kairo.jinkendo.de (kairo/, Port 3004/8004) +Sprint 0 baut: -Gitea: http://192.168.2.144:3000/Lars/Kairo-Jinkendo -Runner: Raspberry Pi (/home/lars/gitea-runner/) — gemeinsam mit Shinkan/Mitai +- Tenant +- User +- Actor +- TenantContext +- Auth-Gates +- Capability / Rights Registry +- minimale Feature Registry +- minimale Prompt Registry +- Placeholder Validation +- Configuration-Grundmodell +- Audit +- Migration/Deploy-Grundlage -Manuell: - cd /home/lars/docker/kairo[-dev] - docker compose -f docker-compose[.dev-env].yml build --no-cache && up -d +--- + +## 2. Verbindliche Primärdokumente + +Lies bei Projektstart in dieser Reihenfolge: + +1. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +2. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +3. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +4. `docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md` +5. `docs/architecture/Kairo_Architecture_References_v0.1.md` +6. `.cursor/rules/kairo-architecture.mdc` + +--- + +## 3. Designprinzipien als Referenz + +Die Designprinzipien aus Mitai und Shinkan liegen unter: + +```text +docs/reference/design-principles/ + mitai/ + shinkan/ + alignment/ ``` -## Verzeichnisstruktur (Zielbild) +Diese Dokumente sind **Referenzmaterial**, nicht direkter Arbeitsauftrag. -``` -backend/ # FastAPI — noch anzulegen -frontend/ # React + Vite — noch anzulegen -.gitea/workflows/ # CI/CD (deploy + test) -docs/ # Deployment-Doku -scripts/load/ # k6 Health-Baseline -``` +Sie dienen dazu, Entscheidungen zu begründen und bekannte Anti-Patterns zu vermeiden. -## Referenz +Sie dürfen nicht dazu verwendet werden, Kairo mit Mitai- oder Shinkan-Domänenlogik zu überladen. -Deployment-Muster und Familien-Standards: Schwesterprojekt **shinkan-jinkendo** (`c:\Dev\shinkan-jinkendo`). +--- -## Jinkendo-Familie +## 4. Auslegungsreihenfolge bei Konflikten -``` -mitai.jinkendo.de → Körper-Tracker (身体) -shinkan.jinkendo.de → Trainingsplanung (真観) -kairo.jinkendo.de → (回廊 — Produktdefinition folgt) -``` +Bei Konflikten gilt: + +1. `Kairo_Sprint0_Principle_Gate_v0.1.md` +2. `Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +3. `Jinkendo_Kairo_Product_Spec_v0.2.md` +4. `Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md` +5. `DESIGN_PRINCIPLES_ALIGNMENT.md` +6. Mitai/Shinkan Einzelprinzipien + +Mitai/Shinkan-Prinzipien dürfen Kairo nicht überstimmen. + +--- + +## 5. Verbindliche Regeln + +1. Kairo ist mandantenfähig. +2. Alle operativen Zuweisungen laufen über Actors. +3. User und Actor sind nicht dasselbe. +4. Agenten sind Actors, keine Sonderlogik. +5. Jeder geschützte Request soll über TenantContext aufgelöst werden. +6. Auth, Capability, Feature und Governance sind getrennte Konzepte. +7. Rechte und Capabilities werden nicht verstreut hardcodiert. +8. Prompts werden nicht hardcodiert. +9. Platzhalter werden validiert. +10. Fachliche Konfiguration wird nicht hardcodiert. +11. Admin- und Agentenaktionen werden auditiert. +12. Migrationen sind nummeriert und reproduzierbar. + +--- + +## 6. Nicht tun + +- keine Mitai-Domänenlogik kopieren +- keine Shinkan-Domänenlogik kopieren +- keine Trainingsplanung bauen +- keine Gesundheitslogik bauen +- keine Lebensmanager-/Seichō-Logik in Kairo einbauen +- keine Vorhaben-/Projektlogik in AP0.1 vorziehen +- kein Billing oder SSO bauen +- keine strategischen Produktentscheidungen eigenmächtig ändern +- keine Designprinzipien aus `docs/reference/` ohne Principle-Gate oder Architecture Decision in Scope ziehen + +--- + +## 7. Abweichungen + +Bei notwendiger Abweichung erstelle ein Architecture Decision Proposal. + +## 8. Aktueller erster Auftrag + +AP0.1 – Projektgrundlage. diff --git a/README.md b/README.md index d3808d5..b12eb6c 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,65 @@ -# Kairo Jinkendo (回廊) +# Jinkendo Kairo -**Programmmanager für alles im Leben** — Produktfamilie Jinkendo (人拳道), Schwesterprodukt zu Shinkan und Mitai. +Operativer Program Director der Jinkendo-Produktfamilie. -> Infrastruktur-Setup: Deployment, Docker und Gitea Actions sind vorbereitet. -> Anwendungscode (Backend/Frontend) folgt in einem separaten Schritt. +Kairo steuert Vorhaben, Programme, Projekte, Meilensteine, Maßnahmen, Backlogs, Reviews, Nachweise und die Zusammenarbeit von Menschen, Arbeitsgruppen und KI-Agenten. -## Deployment-Übersicht +## Leitfrage -| Umgebung | Branch | Server-Pfad | Host-Ports (UI/API) | Domain | -|----------|--------|-------------|---------------------|--------| -| **Development** | `develop` | `/home/lars/docker/kairo-dev` | 3097 / 8097 | https://dev.kairo.jinkendo.de | -| **Production** | `main` | `/home/lars/docker/kairo` | 3004 / 8004 | https://kairo.jinkendo.de | +> Welcher nächste Schritt bringt ein Vorhaben aktuell am wirkungsvollsten voran? -Auto-Deploy via Gitea Actions auf dem Raspberry Pi (gleicher Runner wie Shinkan/Mitai). +## Startzustand -**Gitea:** http://192.168.2.144:3000/Lars/Kairo-Jinkendo +Dieses Repository startet mit einem reinen Spezifikations- und Sprint-0-Handover-Paket. -## Tech-Stack (geplant) +Noch nicht enthalten: -- **Frontend:** React 18 + Vite + PWA -- **Backend:** FastAPI (Python 3.12) -- **Datenbank:** PostgreSQL 16 -- **Container:** Docker + Docker Compose +- produktiver Anwendungscode +- endgültiger Technologie-Stack +- vollständige Jinkendo-Foundation +- Mitai- oder Shinkan-Code +- Billing / SSO / zentrale Produktfamilien-Konvergenz -## Lokales Setup (wenn App-Code vorhanden) +## Verbindliche Dokumente für Sprint 0 -```bash -git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git -cd Kairo-Jinkendo -cp .env.example .env -# .env anpassen +1. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +2. `docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md` +3. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +4. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +5. `docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md` +6. `docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md` +7. `CLAUDE.md` +8. `.cursor/rules/kairo-architecture.mdc` -# Development -docker compose -f docker-compose.dev-env.yml up --build +## Arbeitsmodus -# Production (lokal) -docker compose up --build +Kairo wird iterativ entwickelt. + +Sprint 0 baut nur das Fundament: + +- Tenant +- User +- Actor +- TenantContext +- Auth-Gates +- Capability/Rights Registry +- minimale Feature Registry +- minimale Prompt Registry +- Platzhaltermodell +- Audit +- Migration/Deploy-Grundlage + +Vorhaben, Projekte, Meilensteine und Maßnahmen kommen erst nach Sprint 0. + + +## Designprinzipien-Referenzen + +Die extrahierten Designprinzipien aus Mitai und Shinkan liegen im Repository unter: + +```text +docs/reference/design-principles/ ``` -Frontend (Dev): http://localhost:3097 -Backend (Dev): http://localhost:8097 +Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. -## Server-Einrichtung - -Siehe [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) für Verzeichnisse, `.env`-Dateien und Reverse-Proxy-Hinweise auf dem Pi. - -## Git-Workflow - -``` -develop → Auto-Deploy → kairo-dev (Dev) -main → Auto-Deploy → kairo (Prod) -``` - -## Dokumentation - -- **Deployment:** [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) -- **Entwickler-Kontext:** [CLAUDE.md](CLAUDE.md) -- **Schwesterprojekt (Referenz):** [shinkan-jinkendo](http://192.168.2.144:3000/Lars/shinkan-jinkendo) - -## Lizenz - -Proprietary – Lars Stommer +Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 1f8cf22..647987e 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -1,6 +1,5 @@ # Keine festen container_name — Compose-Namen haben Projektprefix (-postgres-1). -# Medien: In .env KAIRO_MEDIA_HOST (Host-Pfad) und optional MEDIA_ROOT (Container-Pfad) setzen. -# Default Host /kairo-media/dev — Verzeichnis ggf. anlegen oder Compose legt es an. +# Medien: aktuell nicht vorgesehen. Bei Bedarf NAS-Mount + docker-compose.override.yml (siehe docs/DEPLOYMENT.md). services: postgres: @@ -39,16 +38,6 @@ services: ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.kairo.jinkendo.de,http://192.168.2.49:3097}" ENVIRONMENT: "${ENVIRONMENT:-development}" CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}" - MEDIAWIKI_API_URL: "${MEDIAWIKI_API_URL:-https://karatetrainer.net/api.php}" - MEDIAWIKI_USER: "${MEDIAWIKI_USER:-Jinkendo}" - MEDIAWIKI_PASSWORD: "${MEDIAWIKI_PASSWORD:-CHANGE_ME}" - MEDIAWIKI_CATEGORY_EXERCISES: "${MEDIAWIKI_CATEGORY_EXERCISES:-Übungen}" - MEDIAWIKI_CATEGORY_SKILLS: "${MEDIAWIKI_CATEGORY_SKILLS:-Fähigkeitsbeschreibung}" - MEDIAWIKI_CATEGORY_METHODS: "${MEDIAWIKI_CATEGORY_METHODS:-Methodenbeschreibung}" - MEDIAWIKI_CATEGORY_MODELS: "${MEDIAWIKI_CATEGORY_MODELS:-Reifegradmodelle}" - MEDIA_ROOT: "${MEDIA_ROOT:-/app/media}" - volumes: - - ${KAIRO_MEDIA_HOST:-/kairo-media/dev}:${MEDIA_ROOT:-/app/media} ports: - "8097:8000" depends_on: diff --git a/docker-compose.yml b/docker-compose.yml index 1227c11..7724240 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +# Medien: aktuell nicht vorgesehen. Bei Bedarf NAS-Mount + docker-compose.override.yml (siehe docs/DEPLOYMENT.md). + services: postgres: image: postgres:16-alpine @@ -41,16 +43,6 @@ services: ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://kairo.jinkendo.de}" ENVIRONMENT: "${ENVIRONMENT:-production}" CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}" - MEDIAWIKI_API_URL: "${MEDIAWIKI_API_URL:-https://karatetrainer.net/api.php}" - MEDIAWIKI_USER: "${MEDIAWIKI_USER:-}" - MEDIAWIKI_PASSWORD: "${MEDIAWIKI_PASSWORD:-}" - MEDIAWIKI_CATEGORY_EXERCISES: "${MEDIAWIKI_CATEGORY_EXERCISES:-Übungen}" - MEDIAWIKI_CATEGORY_SKILLS: "${MEDIAWIKI_CATEGORY_SKILLS:-Fähigkeitsbeschreibung}" - MEDIAWIKI_CATEGORY_METHODS: "${MEDIAWIKI_CATEGORY_METHODS:-Methodenbeschreibung}" - MEDIAWIKI_CATEGORY_MODELS: "${MEDIAWIKI_CATEGORY_MODELS:-Reifegradmodelle}" - MEDIA_ROOT: "${MEDIA_ROOT:-/app/media}" - volumes: - - ${KAIRO_MEDIA_HOST:-/kairo-media}:${MEDIA_ROOT:-/app/media} ports: - "8004:8000" depends_on: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index fb26212..6ea4d67 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -16,7 +16,6 @@ | **Backend-Port** | 8004 | 8097 | | **PostgreSQL (localhost)** | 5436 | 5437 | | **Domain** | kairo.jinkendo.de | dev.kairo.jinkendo.de | -| **Medien-Host-Pfad** | `/kairo-media` | `/kairo-media/dev` | **Familien-Referenz (bereits belegt):** @@ -33,10 +32,9 @@ Auf dem Pi als User `lars` ausführen: ```bash -# Verzeichnisse anlegen +# Deploy-Verzeichnisse anlegen mkdir -p /home/lars/docker/kairo mkdir -p /home/lars/docker/kairo-dev -mkdir -p /kairo-media /kairo-media/dev # Production cd /home/lars/docker/kairo @@ -57,6 +55,31 @@ cp .env.example .env --- +## Medien (optional, derzeit nicht eingerichtet) + +Kairo benötigt **aktuell keine Medien-Speicherung**. Es sind keine Medien-Verzeichnisse auf dem Pi anzulegen. + +Falls später Datei-Uploads o. Ä. nötig werden: + +1. **NAS-Freigabe** auf dem Synology anlegen (nicht auf dem Raspberry Pi speichern). +2. **Mount auf dem Pi** einrichten (z. B. `/mnt/nas/kairo-media` bzw. `/mnt/nas/kairo-media/dev`). +3. **`docker-compose.override.yml`** im jeweiligen Deploy-Verzeichnis ergänzen (wird von Git ignoriert): + +```yaml +services: + backend: + environment: + MEDIA_ROOT: /app/media + volumes: + - /mnt/nas/kairo-media:/app/media # Dev: …/kairo-media/dev +``` + +4. Im Backend `MEDIA_ROOT` auswerten — erst wenn die App Medien unterstützt. + +Analog Shinkan (`SHINKAN_MEDIA_HOST`), aber bewusst **nicht** Teil des Initial-Setups. + +--- + ## Reverse Proxy (Synology / Fritz!Box) Analog zu Shinkan — neue Hostnamen im Proxy eintragen: diff --git a/docs/architecture/Kairo_Architecture_References_v0.1.md b/docs/architecture/Kairo_Architecture_References_v0.1.md new file mode 100644 index 0000000..272637a --- /dev/null +++ b/docs/architecture/Kairo_Architecture_References_v0.1.md @@ -0,0 +1,61 @@ +# Kairo Architecture References v0.1 + +Status: verbindliche Referenzliste für Coding-Agenten +Stand: 2026-07-04 + +## Primäre Kairo-Dokumente + +1. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +2. `docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md` +3. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +4. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +5. `docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md` +6. `CLAUDE.md` +7. `.cursor/rules/kairo-architecture.mdc` + +## Referenzdokumente + +### Alignment + +- `docs/reference/design-principles/alignment/DESIGN_PRINCIPLES_ALIGNMENT.md` +- `docs/reference/design-principles/alignment/FAMILY_ENTITLEMENT_MODEL.md` + +### Mitai + +- `docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/mitai/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/mitai/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/mitai/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` + +### Shinkan + +- `docs/reference/design-principles/shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/shinkan/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/shinkan/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/shinkan/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md` +- `docs/reference/design-principles/shinkan/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` + +## Auslegungsregel + +Bei Konflikten gilt diese Reihenfolge: + +1. Kairo Sprint-0 Principle Gate +2. Sprint-0 Foundation v0.3 +3. Kairo Product Spec +4. Kairo Foundation v0.2 +5. Alignment-Dokumente +6. Mitai/Shinkan Einzelprinzipien + +Mitai/Shinkan-Dokumente dürfen Kairo nicht überstimmen. + +## Drift-Regel + +Ein Agent darf Designprinzipien nur als Begründung verwenden, nicht als Erweiterungsauftrag. + +Erlaubt: +> Shinkan Rights Registry begründet, warum Kairo eine Capability Registry braucht. + +Nicht erlaubt: +> Shinkan hat Club Features, also bauen wir Club Features in Kairo. diff --git a/docs/reference/design-principles/README.md b/docs/reference/design-principles/README.md new file mode 100644 index 0000000..d0b79ba --- /dev/null +++ b/docs/reference/design-principles/README.md @@ -0,0 +1,56 @@ +# Designprinzipien – Referenzpaket für Kairo + +Status: Referenz / nicht automatisch verbindlich +Stand: 2026-07-04 + +Dieses Verzeichnis enthält die extrahierten Designprinzipien aus Mitai und Shinkan sowie den Abgleich. + +Diese Dokumente sind **Referenzmaterial**, kein direkter Sprint-Scope. + +Verbindlich für Kairo ist nur, was in folgenden Dokumenten steht: + +1. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +2. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +3. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +4. eine spätere explizite Architecture Decision + +## Struktur + +```text +docs/reference/design-principles/ + mitai/ + shinkan/ + alignment/ +``` + +## Für Kairo Sprint 0 relevant + +- TenantContext / mandantenfähiger Request-Kontext +- Actor-first statt User-first +- Auth, Capability, Feature und Governance trennen +- Rights / Capability Registry +- minimale Feature Registry +- minimale Prompt Registry +- Prompt Templates nicht hardcoden +- Template und Kontext trennen +- typisierte und validierte Platzhalter +- nummerierte Migrationen und Fail-Fast + +## Nicht automatisch übernehmen + +- Mitai Data Layer +- Mitai Widget Dashboard +- Mitai Universal Import +- vollständige Mitai Prompt Engine +- Shinkan Exercise Catalog +- Shinkan Training Planning +- Shinkan Skill Scoring +- Shinkan Media Assets +- Shinkan Content Reports +- Shinkan Maturity Models +- Billing, Tiers, Coupons, Usage-Limits +- zentrale Familien-Konvergenz + +## Regel + +Wenn ein Agent ein Prinzip aus diesem Referenzpaket übernehmen möchte, das nicht im Sprint-0-Gate steht, muss er ein Architecture Decision Proposal erstellen. diff --git a/docs/reference/design-principles/alignment/DESIGN_PRINCIPLES_ALIGNMENT.md b/docs/reference/design-principles/alignment/DESIGN_PRINCIPLES_ALIGNMENT.md new file mode 100644 index 0000000..b686a18 --- /dev/null +++ b/docs/reference/design-principles/alignment/DESIGN_PRINCIPLES_ALIGNMENT.md @@ -0,0 +1,358 @@ +# Designprinzipien – Abgleich Mitai ↔ Shinkan + +**Status:** Review / Entscheidungsgrundlage +**Stand:** 2026-07-04 +**Zweck:** Widersprüche, bewusste Abweichungen und Implementierungslücken zwischen den Designprinzipien-Serien identifizieren — Basis für **Familien-Entscheidungen** und langfristige Konvergenz von Mitai und Shinkan. + +**Quellen:** + +| App | Index | +|-----|--------| +| Mitai (Foundation) | [mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md) — 9 Module | +| Shinkan | [design-principles/DESIGN_PRINCIPLES_INDEX.md](./design-principles/DESIGN_PRINCIPLES_INDEX.md) — 15 Module | + +--- + +## 1. Kurzfassung + +| Kategorie | Anzahl | Bedeutung | +|-----------|--------|-----------| +| **Familien-Konsens** | 12 Muster | In beiden Serien gleich oder kompatibel — **verbindlich für neue Apps** | +| **Bewusste Produkt-Abweichung** | 8 | Fachlich/Architektur begründet — **nicht angleichen**, aber im Familienmodell verankern | +| **Konzeptuelle Spannung** | 6 | Widersprüche oder gegenläufige Defaults — **Familien-Entscheidung nötig** | +| **Ist vs. Prinzip (Schuld)** | 14+ | Mindestens eine App verletzt eigene oder Schwester-Prinzipien — **Remediation** | +| **Nur Shinkan** | 6 Module | Mandanten-/Domänen-Bausteine ohne Mitai-Pendant | +| **Nur Mitai (reifer)** | 3 Muster | Data Layer, Widget-Dashboard, Universal Import — Shinkan vereinfacht oder fehlt | + +**Kernbefund:** Mitai und Shinkan teilen dieselbe **technische Basis** (Auth, Migration, Nav-SSoT, Registry-Denken, Probe→Enforce), divergieren aber strukturell bei **Entitlement-Subjekt** (Profil vs. Verein), **Berechnungsarchitektur** (generischer Data Layer vs. domänenspezifisches Scoring) und **KI-Reife** (Unified Executor vs. schmale Laufzeit). + +--- + +## 2. Familien-Konsens (für neue Produkte übernehmen) + +Diese Muster sind in beiden Serien explizit oder implizit tragfähig: + +| # | Muster | Mitai | Shinkan | +|---|--------|-------|---------| +| F1 | Server-Sessions + `Depends(require_auth)` | Auth #1–3 | Auth #1–2 | +| F2 | `profile_id` aus Session, nie aus Client-Header | Auth #3 | Auth #3 | +| F3 | Auth getrennt von Authorization/Entitlements | Auth #10 | Capabilities + Access Layer | +| F4 | Nummerierte SQL-Migrationen + Tracking beim Container-Start | Migration #1–5 | Migration #1–3 | +| F5 | Fail-fast, kein Auto-Rollback | Migration #5 | Migration #2 | +| F6 | `appNav` / zentrale Nav-Config als SSoT | Navigation #1 | Navigation #1 | +| F7 | Admin als eigener Hub/Realm | Navigation #6–7 | Navigation #2 | +| F8 | DB-konfigurierbare KI-Prompts (nicht hardcoded Prod) | Prompt #2 | AI Runtime #2 | +| F9 | Template vs. Kontext/Daten trennen | Prompt #5 | AI Runtime #4 | +| F10 | Ingest ≠ Interpretation beim Import | Import #2 | Wiki Import #1 | +| F11 | Preview/Dry-Run vor Massenimport | Import #3+ | Wiki Import #2 | +| F12 | 4-Phasen-Rollout Entitlements (Log → Enforce) | Feature #8 | Capability #2 | + +**Empfehlung:** Als **`JINKENDO_FOUNDATION_CHECKLIST`** in künftigen Apps verpflichtend; Details pro Modul in den Einzeldokumenten. + +--- + +## 3. Modul-Abgleich (9 vergleichbare Paare) + +Legende **Bewertung:** + +| Symbol | Bedeutung | +|--------|-----------| +| ✅ | Prinzipien aligned / kompatibel | +| ⚠️ | Teilweise aligned; Lücken in Implementierung oder Doku | +| 🔀 | Bewusste Produkt-Divergenz (kein Bug) | +| ❌ | Widerspruch oder gegenläufiges Konzept — Entscheidung nötig | +| 🏗️ | Ist-Stand verletzt dokumentierte Prinzipien (Architekturschuld) | + +--- + +### 3.1 Prompt Engine (Mitai #1) ↔ AI Prompt Runtime (Shinkan #5) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Single Entry Point | `execute_prompt` / Unified System | `ai_prompt_runtime` + verteilte Orchestratoren | ❌ Konzept | +| Prompt-Typen | base / pipeline / workflow | nur slug + Mustache | 🔀 Shinkan bewusst schlanker | +| Platzhalter-Registry | zentral, API-Verträge | Kontext-Arten (`AiPromptContextKind`), kein Registry-Katalog | ⚠️ | +| Data Layer-Anbindung | Layer 1 → Resolver | Domänen-Builder ad hoc | ⚠️ | +| Debug/Preview | ausgereift | Admin-Vorschau, weniger Runtime-Transparenz | ⚠️ | +| Feature-Gating an Execute | teils fehlend (Legacy) | Capability geplant, teils Probe | 🏗️ beide | + +**Widersprüche / gegenläufig:** + +- Mitai: **Ein Executor** ist Kernprinzip. Shinkan: **kein** vergleichbarer Executor — Planungs-KI umgeht teils die Laufzeit. +- Beide warnen vor **parallelen KI-Pfaden**; beide haben sie noch (Mitai `insights.py`, Shinkan Router-OpenRouter). + +**Familien-Entscheidung (Vorschlag):** + +| Option | Inhalt | +|--------|--------| +| **Zielbild** | Gemeinsame **`prompt_executor`-Fassade** (Package oder Copy mit Namespace); Shinkan-Kontext-Builder als Plugins | +| **Shinkan-Roadmap** | Planungs-Orchestrierung in Laufzeit ziehen; keine Workflow-Graphs vor Planungs-Kontext-Reife | +| **Nicht kopieren** | Mitai: doppelte Pipeline-Modelle, PLACEHOLDER_MAP-Duplikat, Roh-SQL im Executor | + +--- + +### 3.2 Data Layer (Mitai #2) ↔ Skill Scoring (Shinkan #8) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Berechnungs-SSoT | `data_layer/` Layer 0→1→2 | nur `skill_scoring.py` | ❌ Abdeckung | +| Router delegieren | explizites Prinzip #13 | Skill-Router ja; Planung teils nicht | ⚠️ | +| Confidence / data_points | Pflicht-Metadaten | nicht analog | 🔀 Domäne anders | +| Chart/KPI-Anbindung | Layer 2b Adapter | KPI-Dashboard ruft Router-Helfer | ⚠️ | +| Import-Grenze | keine Scores beim Insert | Wiki: explizit kein Scoring beim Insert | ✅ | + +**Widerspruch:** + +- Mitai postuliert **generische Berechnungsschicht** für die ganze App. Shinkan hat **kein** Data Layer — nur ein **domänenspezifisches** Scoring-Modul. Das ist keine Implementierungslücke allein, sondern **unterschiedliche Architektur-Tiefe**. + +**Familien-Entscheidung (Vorschlag):** + +| Option | Inhalt | +|--------|--------| +| **Familien-Prinzip** | „Berechnungen in benannter Schicht, nicht in Router/React“ — **Ja** | +| **Implementierung** | Mitai: `data_layer/` bleibt Referenz. Shinkan: Skill Scoring **ist** Layer-1-Vorbild; langfristig **`planning_metrics/`** o. ä. statt Router-SQL | +| **Nicht verallgemeinern** | Mitai-Formeln (TDEE, WHR) — nur Schichtenmodell übernehmen | + +--- + +### 3.3 Feature & Entitlement (Mitai #3) ↔ Capability & Club Features (Shinkan #2) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Subjekt | **Profil** + Tier | **Verein** (`club_id`) + Plan | ❌ Scope | +| Auflösungs-API | `check_feature_access` | `check_capability` + `club_features` + `/me/entitlements` | 🔀 | +| Rollout 4 Phasen | ja | ja (Env-Flags) | ✅ | +| Registry | DB `features` + Tiers | Code-Registry → DB Sync | ⚠️ | +| Capabilities vs. Features | Features only | Capabilities **und** Kontingente getrennt | 🔀 Shinkan feiner | +| Widget/Layout-Gating | zentral | Entitlements-API, kein Widget-Layout | ⚠️ | + +**Größter Familien-Konflikt:** + +Mitai-Dokument #3 „Nicht übernehmen“ Punkt 10: *„Profile as Entitlement-Subject — Multi-App-Familie braucht separates Identity/Subscription-Boundary.“* +Shinkan **ist** die Antwort mit Verein als Subjekt — aber es gibt **kein gemeinsames Familienmodell**, das Profil-Tier **und** Org-Limits kombiniert. + +**Familien-Entscheidung (Vorschlag):** + +``` +Entitlement-Subjekt (familie): + ├── account (profile_id) → Tier, persönliche Limits (Mitai) + └── tenant (club_id?) → Org-Plan, Capabilities (Shinkan, optional null) + +API: GET /me/entitlements?tenant_id= +Enforcement: eine resolve_entitlement(subject, capability|feature) +``` + +| App | Remediation | +|-----|-------------| +| Mitai | Org-Scope reservieren; Tier-Drift (#3 Schuld) bereinigen | +| Shinkan | `CLUB_FEATURE_ENFORCE=1` produktiv; Mitai-Legacy `check_feature_access` nicht nutzen (bereits Regel) | + +--- + +### 3.4 Registry / Plugin (Mitai #4) ↔ Rights Registry (Shinkan #3) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Registry-first | Platzhalter, Widgets, CSV-Module | Capabilities + Features only | ⚠️ Abdeckung | +| Validierung an Grenze | ja | ja (`register_*` wirft) | ✅ | +| Runtime + DB | Dual (Katalog + DB-Overrides) | Code → DB Upsert | ✅ | +| Dual Registry FE/BE | Widgets + `registerDashboardWidgets` | nicht vorhanden | 🔀 | +| Metadaten-Tiefe | nach Risiko (Matrix in Doc) | schlankere Dataclasses | ✅ | + +**Kein Widerspruch** — Shinkan Rights Registry ist **Teilmenge** des Mitai-Meta-Musters. + +**Familien-Entscheidung:** Mitai-Registry-Matrix (Platzhalter / UI-Plugin / Import-Modul / Rechte) als **Familien-Taxonomie**; Shinkan erweitert um Import-/Prompt-Registry wenn Wiki-Import generisch wird. + +--- + +### 3.5 Auth & Session (Mitai #5 ↔ Shinkan #4) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Session-Modell | opaque token | gleich (shared `auth.py`) | ✅ | +| Depends-Pattern | ja | ja (+ TenantContext) | ✅ | +| IDOR Profile-Header | dokumentierte Schwäche | Shinkan: Session-only betont | ⚠️ prüfen | +| RBAC | `role` admin/user | Portal-Rolle **+** Vereinsrollen | 🔀 | +| Rate Limiting | ja | (Mitai-spezifisch in Router) | ⚠️ | +| Feature-Flags in Session | Legacy-Spalten | Account-Lifecycle separat | 🏗️ Mitai | + +**Gegenläufig:** Shinkan **erweitert** Auth um Mandanten — darf Mandantenlogik **nicht** in `auth.py` legen (Shinkan-Prinzip „Nicht übernehmen“). + +**Familien-Entscheidung:** Gemeinsames Auth-Modul; **TenantContext** als optionales Add-on-Pattern für mandantenfähige Apps. + +--- + +### 3.6 Universal Import (Mitai #6) ↔ Wiki Import (Shinkan #11) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Ingest ≠ Interpretation | ✅ | ✅ | ✅ | +| Modul-Registry | zentral | fehlt (wiki-hardcoded) | ⚠️ | +| SAVEPOINT pro Zeile | ja | nicht dokumentiert | ⚠️ | +| Vorlagen/Mappings | generisch | SMW-Kategorien via Env | 🔀 | +| Feature-Limits | an Import gebunden | Admin-only | ⚠️ | + +**Kein Konflikt** — unterschiedliche Reife. Shinkan ist **Spezialfall** des Mitai-Musters. + +**Familien-Entscheidung:** Neue Import-Quellen über **Universal-Import-Gerüst** (Mitai); Wiki als `import_type=mediawiki` registrieren. + +--- + +### 3.7 Dashboard Widgets (Mitai #7) ↔ Dashboard KPIs (Shinkan #14) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| UX-Modell | konfigurierbares Widget-Layout | festes KPI-Aggregat | ❌ UX-Konzept | +| Chatty Client vermeiden | via Widget-Daten | via `/dashboard/kpis` | ✅ Ziel | +| Entitlements | `allowed` pro Widget | TenantContext auf KPIs | ✅ | +| Data Layer | Widgets konsumieren Layer 1 | intern Router-Helfer | ⚠️ | + +**Gegenläufig:** Mitai: **Nutzer konfiguriert Dashboard**. Shinkan: **Produkt definiert feste Kacheln** — bewusste MVP-Vereinfachung. + +**Familien-Entscheidung:** + +| App-Typ | Dashboard-Pattern | +|---------|-------------------| +| Personal Tracking (Mitai) | Widget-Katalog + Layout-JSON | +| Trainer/Verein (Shinkan) | Aggregierte KPI-Endpoints ausreichend; Widget-System optional Phase 2 | +| Neue App | Aggregat-Endpoint **mindestens**; Widget-System wenn Personalisierung nötig | + +--- + +### 3.8 Navigation / IA (Mitai #8 ↔ Shinkan #12) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| appNav SSoT | ja | ja | ✅ | +| Admin-Hub | Shell + Hub-Gruppen | horizontale `AdminPageNav` | ⚠️ | +| Breakpoint 1024px | explizit | „prüfen“ | ⚠️ | +| Onboarding-Nav | — | reduziert ohne Verein | 🔀 Shinkan | +| adminNav.js SSoT | empfohlen | hardcoded Array in JSX | 🏗️ Shinkan | +| Safe Area PWA | ja | Design-System, weniger explizit | ⚠️ | + +**Familien-Entscheidung:** `appNav.js` + **`adminNav.js`** als Pflicht; Shinkan `AdminPageNav` refactoren. + +--- + +### 3.9 Migration & Deploy (Mitai #9 ↔ Shinkan #13) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| XXX_*.sql + schema_migrations | ✅ | ✅ | ✅ | +| Startup vor App | ✅ | ✅ | ✅ | +| develop/main | ✅ | ✅ | ✅ | +| Feste Ports | ✅ | ✅ | ✅ | +| Immutabler Docker-Build | dokumentiert | nicht im Shinkan-Doc | ⚠️ Doku | +| Health-Check / PG wait | ausführlich | kürzer | ⚠️ Doku | + +**Aligned** — Shinkan-Dokument ist **Untermenge**; Implementierung vermutlich gleich (shared Infra). + +--- + +## 4. Nur Shinkan (6 Module) — Einordnung für die Familie + +| Modul | Familien-Relevanz | Mitai-Bezug | +|-------|-------------------|------------| +| **Access Layer & Tenant** | **Pflicht** für mandantenfähige Apps | Mitai #3 fordert Org-Boundary — hier ausformuliert | +| **Media Assets & Archiv** | Optional (Content-Apps) | — | +| **Exercise Catalog** | Shinkan-Domäne | — | +| **Training Planning** | Shinkan-Domäne | — | +| **Content Reports (P-13)** | Empfohlen für UGC/Plattform | — | +| **Maturity Models** | Optional (Kompetenz-Apps) | — | + +**Kein Widerspruch zu Mitai** — ergänzen das Familienmodell um **Mandant + Content-Governance**. + +--- + +## 5. Querschnitt: Ist-Stand vs. dokumentierte Prinzipien + +Gemeinsame **Architekturschuld** (beide Apps verletzen teils eigene „Nicht übernehmen“-Listen): + +| Thema | Mitai | Shinkan | +|-------|-------|---------| +| Parallele KI-Pfade | `insights.py` Legacy | OpenRouter direkt in Routern | +| Entitlement Enforcement | teils UI-only / Legacy-Spalten | Env Probe-only | +| Registry-Sync / Duplikat | PLACEHOLDER_MAP + Registry | Capabilities-Sync, kein Prompt-Registry | +| Frontend ohne Backend-Gate | teils | Capabilities Probe | +| Dokumentations-Drift | Tier vs. Enforcement-Docs | Endpoint-Audit unvollständig | +| Monolithische Pages/Client | God Pages, api.js | God Pages, api.js (Roadmap Phase 4) | +| Fehlende JSON-Schema-KI | TODO | TODO (explizit vermeiden) | + +--- + +## 6. Entscheidungs-Matrix (Priorisiert) + +| Prio | Entscheidung | Betroffene Apps | Empfohlene Familien-Regel | +|------|--------------|-----------------|---------------------------| +| **P0** | Entitlement-Subjekt: Profil **und** optional Tenant | Mitai, Shinkan, neu | Ein API-Shape `/me/entitlements`; zwei Subjekt-Ebenen | +| **P0** | Kein Client-`profile_id` für AuthZ | alle | Session-only; Tenant via Header + Membership | +| **P1** | KI: ein Executor pro App | Mitai (fertig), Shinkan (Ziel) | `execute_prompt(slug, context_dto)` | +| **P1** | Berechnungs-SSoT-Schicht | Shinkan erweitern | Mindestens ein `*/metrics.py` pro Domäne mit KPIs | +| **P1** | Enforcement produktiv | beide | Phase 4 Enforce in Prod für kritische Features | +| **P2** | Registry-Taxonomie vereinheitlichen | beide | Rechte / Platzhalter / Import / UI-Plugin | +| **P2** | Admin-Nav SSoT | Shinkan | `adminNav.js` wie Mitai | +| **P2** | Import: Universal + Spezialmodule | Shinkan | Wiki als registriertes Modul | +| **P3** | Dashboard: Aggregat vs. Widgets | produktabhängig | Entscheidungsbaum §3.7 | +| **P3** | Shared `auth.py` / `db_init` | beide | Monorepo-Package oder Sync-Disziplin | + +--- + +## 7. Konvergenz-Roadmap (langfristig) + +```mermaid +flowchart LR + subgraph foundation [Familien-Foundation] + M9[Migration] + M5[Auth] + F3[Entitlements 2-Ebenen] + R4[Registry Meta] + end + + subgraph mitai [Mitai] + DL[Data Layer] + PE[Prompt Engine] + DW[Dashboard Widgets] + end + + subgraph shinkan [Shinkan] + AL[Access Layer] + AR[AI Runtime → Executor] + SS[Skill Scoring → Layer 1] + end + + M9 --> mitai + M9 --> shinkan + M5 --> mitai + M5 --> shinkan + F3 --> mitai + F3 --> shinkan + R4 --> mitai + R4 --> shinkan + AL -.->|Mandanten-Apps| foundation + PE -.->|Konvergenz| AR + DL -.->|Schichtenmodell| SS +``` + +| Phase | Mitai | Shinkan | +|-------|-------|---------| +| **Kurz** | Legacy KI-Pfade entfernen; Enforcement-Doku vereinheitlichen | Access-Layer-Audit abschließen; `CAPABILITY_ENFORCE` | +| **Mittel** | Org-Scope in Entitlements vorbereiten | `ai_prompt_runtime` → Unified Executor; Router-Helfer statt KPI-Duplikat | +| **Lang** | SSO/Identity (Vision) | Data-Layer-ähnliche Module für Planung; optional Widget-Dashboard | + +--- + +## 8. Nächste Schritte + +1. **Review-Workshop:** Tabelle §6 P0–P1 durchgehen und Familien-Regeln verbindlich markieren. +2. ~~**`FAMILY_ENTITLEMENT_MODEL.md`** anlegen (P0)~~ → [FAMILY_ENTITLEMENT_MODEL.md](./FAMILY_ENTITLEMENT_MODEL.md) (Entwurf 2026-07-04) +3. **Shinkan-Index** und **Mitai-Foundation-README** auf dieses Dokument verlinken. +4. **`FAMILY_ENTITLEMENT_MODEL.md`** — Entwurf angelegt (§6 Produkt-Abweichungen, §8 Regeln neue Apps). +5. Pro **P1-Punkt** Issue/Remediation-Eintrag in jeweiliger `SCHULDEN_UND_REMEDIATION` / Mitai-Äquivalent. + +--- + +## 9. Changelog + +| Datum | Änderung | +|-------|----------| +| 2026-07-04 | Erstfassung Abgleich Mitai Foundation (9) ↔ Shinkan (15) | diff --git a/docs/reference/design-principles/alignment/FAMILY_ENTITLEMENT_MODEL.md b/docs/reference/design-principles/alignment/FAMILY_ENTITLEMENT_MODEL.md new file mode 100644 index 0000000..b5df696 --- /dev/null +++ b/docs/reference/design-principles/alignment/FAMILY_ENTITLEMENT_MODEL.md @@ -0,0 +1,316 @@ +# Familien-Modell: Entitlements & Limits + +**Status:** Entwurf / verbindliche Zielrichtung (mit bewussten Produkt-Ausnahmen) +**Stand:** 2026-07-04 +**Bezüge:** + +- [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) — Abgleich Mitai ↔ Shinkan +- Mitai: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Shinkan: [CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) + +--- + +## 1. Zweck + +Dieses Dokument definiert das **gemeinsame Entitlement-Modell** der Jinkendo-Produktfamilie: + +- **Was** für neue Apps und Refactors **Standard** ist +- **Welche Produkt-Profile** welche Scopes nutzen (Account, Tenant, beides, keins) +- **Wie** bewusste Abweichungen dokumentiert werden — Abweichung ist erlaubt, **Undokumentiertheit** nicht + +**Nicht enthalten:** Stripe/SSO (`auth.jinkendo.de`), vollständige Billing-Implementierung, app-spezifische Feature-IDs. + +--- + +## 2. Leitgedanke: Vier getrennte Fragen + +Jede geschützte Aktion durchläuft konzeptionell **vier unabhängige Prüfungen**. Nicht jede App implementiert alle vier — siehe §6. + +| # | Frage | Familien-Begriff | Typische Quelle | +|---|--------|------------------|-----------------| +| **A** | Wer ist eingeloggt? | **Auth** (Identität) | Session → `profile_id` | +| **B** | Darf diese Rolle die **Funktion** ausführen? | **Capability** (Permission) | Rollen-Matrix, `min_account_state` | +| **C** | Ist das **Kontingent** erschöpft? | **Feature / Limit** (Quota) | Plan, Tier, Usage-Zähler | +| **D** | Darf ich **dieses Objekt** lesen/ändern? | **Governance** (Object ACL) | `visibility`, `club_id`, Owner | + +``` +Request → Auth (A) → [Capability (B)] → [Feature-Limit (C)] → [Governance (D)] → Handler +``` + +**Familien-Regel:** B, C und D **nicht** in React-Widgets oder Router-Inline-Logik vermischen — jeweils eine Auflösungsfunktion pro Ebene. + +**Shinkan-Ergänzung:** Governance ist dort ausgebaut (`TenantContext`, Access Layer); Mitai fokussiert A+B+C auf Profil-Ebene. + +--- + +## 3. Familien-Standard: Zwei Subjekt-Ebenen + +Limits und Pläne können an **zwei Subjekte** hängen. Beide sind im Familienmodell **first-class** — Apps wählen, welche sie nutzen (§6). + +| Subjekt | ID | Typische Frage | Beispiel | +|---------|-----|----------------|----------| +| **Account** | `profile_id` | Was darf **ich** als Nutzer (Tarif)? | Mitai Free vs. Premium | +| **Tenant** | `tenant_id` (z. B. `club_id`) | Was darf **meine Organisation**? | Shinkan Vereinsplan, KI-Kontingent | + +### 3.1 Auflösungs-Reihenfolge (wenn beide Ebenen aktiv) + +Für eine Aktion mit Capability `X` und Feature `Y`: + +1. **Account-Lifecycle** — z. B. E-Mail verifiziert, Onboarding abgeschlossen +2. **Capability(B)** — Rolle darf Funktion (Account- und/oder Tenant-Rollen) +3. **Feature(C)** — Kontingent am **primären Billing-Subjekt** der App (siehe Produkt-Profil) +4. **Governance(D)** — Objekt sichtbar/bearbeitbar + +**AND-Verknüpfung:** Alle aktiven Ebenen müssen passieren. Ausnahmen nur in §6.3 dokumentiert. + +### 3.2 Primäres Billing-Subjekt pro App + +| Profil | Primäres Subjekt für Limits | Capability-Subjekt | +|--------|----------------------------|-------------------| +| Personal App (Mitai) | **Account** | Account | +| Mandanten-App (Shinkan) | **Tenant** | Account + Tenant-Rolle | +| Hybrid (Zukunft) | konfigurierbar | beide | + +--- + +## 4. Familien-Standard: Capabilities vs. Features + +| Konzept | Familien-Definition | Subjekt | Beispiel | +|---------|---------------------|---------|----------| +| **Capability** | Binäre oder rollenbasierte **Erlaubnis** („darf ich?“) | meist Account + Tenant-Kontext | `exercises.ai.suggest` | +| **Feature** | **Kontingent** oder Boolean-Limit („wie oft/noch?“) | Account **oder** Tenant | `ai_calls` / Monat | +| **Verknüpfung** | Capability kann `linked_feature_id` haben | — | KI-Capability → KI-Kontingent | + +**Familien-Regel:** + +- Capabilities **registry-first** registrieren (Code → DB-Sync, Shinkan-Muster). +- Feature-IDs **nicht** in UI hardcoden — nur aus Entitlements-Response. +- `NULL` Limit = unbegrenzt; `0` = deaktiviert (Mitai-Semantik, familienweit). + +--- + +## 5. Familien-Standard: API & Enforcement + +### 5.1 Ziel-API (neue Apps) + +Ein **einheitlicher Snapshot** für das Frontend: + +``` +GET /api/me/entitlements + ?tenant_id= + +Response (skizziert): +{ + "account": { + "profile_id": 1, + "account_state": "active_member", + "tier_id": "premium", // optional, Account-Apps + "features": { "ai_calls": { "allowed", "used", "limit", "remaining", "reset_at" } }, + "capabilities": { "analysis.run": { "allowed": true, "reason": null } } + }, + "tenant": { // null wenn App keinen Tenant kennt + "tenant_id": 42, + "tenant_type": "club", + "plan_id": "pro", + "features": { ... }, + "capabilities": { ... }, + "roles": ["trainer"] + }, + "enforcement": { + "capabilities": "enforce|probe", + "features": "enforce|probe" + } +} +``` + +**Familien-Regel:** UI liest **nur** diesen Snapshot (oder domänenspezifische Teilmenge) — keine parallelen `/subscription/me` + `/features/usage` + Ad-hoc-Checks in neuen Apps. + +### 5.2 Ist-API (bestehende Apps — Abweichung dokumentiert) + +| App | Endpoint heute | Familien-Ziel | +|-----|----------------|---------------| +| **Mitai** | `/subscription/me`, `/features/usage`, `check_feature_access` | Snapshot schrittweise; Account-Block reicht | +| **Shinkan** | `GET /api/me/entitlements?club_id=` | Tenant-Block + Capabilities; Account-Tier fehlt bewusst | + +Migration: **kein Big-Bang** — alte Endpoints als Facade auf Snapshot mappen. + +### 5.3 Vier-Phasen-Rollout (familienweit verbindlich) + +| Phase | Verhalten | Env-Beispiel | +|-------|-----------|--------------| +| **1** | Cleanup Legacy-Flags | — | +| **2** | **Probe** — JSON-Log, HTTP 200 | `*_ENFORCE=0` | +| **3** | Frontend-Gates aus Entitlements | — | +| **4** | **Enforce** — HTTP 403 | `*_ENFORCE=1` | + +**Familien-Regel:** Phase 4 für **neue** kritische Features von Anfang an planbar; Bestands-Apps dürfen in Phase 2–3 bleiben bis kalibriert. + +### 5.4 Enforcement-Priorität + +1. **API** — autoritativ (`403`) +2. **Frontend** — UX (Badges, disabled Buttons) +3. **Niemals** — nur UI ohne API-Gate + +--- + +## 6. Produkt-Profile & bewusste Abweichungen + +Abweichungen vom Familien-Standard sind **zulässig**, wenn sie in der Tabelle **§6.2** stehen und begründet sind. + +### 6.1 Profil-Matrix (Soll) + +| Profil | Apps | Account-Limits | Tenant-Limits | Capabilities | Governance (Objekt) | +|--------|------|----------------|---------------|--------------|---------------------| +| **P1 Personal** | Mitai | ✅ primär | ❌ | ✅ Account | minimal / privat | +| **P2 Mandant** | Shinkan | ⚠️ Lifecycle only | ✅ primär | ✅ Account + Tenant-Rollen | ✅ Access Layer | +| **P3 Minimal** | Miken, Ikigai (geplant) | optional | ❌ | optional | minimal | +| **P4 Hybrid** | (Reserve) | ✅ | ✅ | ✅ beide | ✅ | + +### 6.2 Registrierter Abweichungs-Katalog + +| ID | App | Abweichung vom Familien-Standard | Begründung | Review | +|----|-----|----------------------------------|------------|--------| +| **DEV-01** | Mitai | Kein `tenant`-Block in Entitlements | Persönliche Tracking-App; kein Verein | Beibehalten (P1) | +| **DEV-02** | Mitai | Kein Capability-Katalog (nur Features+Tier) | RBAC = admin/user ausreichend | Optional später `account.*` Capabilities | +| **DEV-03** | Mitai | Mehrere Nutzer-APIs statt einem Snapshot | Historisch gewachsen | Facade → Snapshot (mittelfristig) | +| **DEV-04** | Mitai | Legacy Session-Spalten (`ai_enabled`, …) parallel Features | Migrationsschuld | Bereinigen, nicht in neue Apps | +| **DEV-05** | Shinkan | Kein Account-Tier / `profiles.tier` | Verein zahlt, nicht Trainer | Beibehalten (P2) | +| **DEV-06** | Shinkan | Capabilities **und** Governance (zwei Achsen) | Trainer vs. Objekt-Rechte | Familien-Vorbild für P2/P4 | +| **DEV-07** | Shinkan | `check_feature_access` (Mitai-Legacy) explizit verboten | Falsches Subjekt | Beibehalten | +| **DEV-08** | Shinkan | Enforcement oft Probe-only | Rollout-Sicherheit | → Phase 4 bis Datum X | +| **DEV-09** | Shinkan | Inventar-Features live gezählt | Drift-Vermeidung | Abweichung OK; in P2 dokumentieren | +| **DEV-10** | Beide | Kein atomares Check+Increment | Race bei Parallel-Requests | Familien-Backlog; Workaround dokumentieren | +| **DEV-11** | Neue Apps | dürfen **nur** Account **oder** nur Tenant wählen | MVP | Eintrag hier anlegen vor Launch | + +**Neue Abweichung:** Zeile in §6.2 + ggf. ein Satz im App-`CLAUDE.md`. + +### 6.3 Dokumentierte Ausnahmen (Bypass) + +| Ausnahme | Apps | Regel | +|----------|------|-------| +| Plattform-Admin Audit | Shinkan | Quota-Bypass über Grants, nicht pauschal superadmin | +| Admin ohne Auto-Bypass | Mitai | Admins unterliegen Limits (Produktentscheid) | +| Öffentliche Routen ohne Auth | alle | Kein Entitlement-Check | + +--- + +## 7. Mapping: Familien-Begriff ↔ Implementierung + +### 7.1 Mitai (Profil P1) + +| Familien | Mitai-Implementierung | +|----------|----------------------| +| Account-Features | `features`, `tier_limits`, `user_feature_usage` | +| Account-Tier | `get_effective_tier()`, `access_grants` | +| Check | `check_feature_access(profile_id, feature_id)` | +| Increment | `increment_feature_usage(profile_id, …)` | +| UI | `UsageBadge`, Widget `allowed` | + +### 7.2 Shinkan (Profil P2) + +| Familien | Shinkan-Implementierung | +|----------|-------------------------| +| Tenant-Features | `club_plan_limits`, `club_feature_usage`, `club_features.py` | +| Tenant-Plan | `get_effective_club_plan(club_id)` | +| Capabilities | `check_capability`, `capabilities` + `rights_registry` | +| Snapshot | `build_me_entitlements()` → `GET /me/entitlements` | +| Governance | `TenantContext`, `club_tenancy` — **kein** Ersatz für Capabilities | + +### 7.3 Gemeinsame Muster (copy-ready) + +| Muster | Mitai | Shinkan | +|--------|-------|---------| +| Feature-Registry in DB | ✅ | ✅ (`app='shinkan'`) | +| Plan × Feature Matrix | `tier_limits` | `club_plan_limits` | +| Admin-Override | `user_feature_restrictions` | `club_feature_overrides` | +| Promo/Trial | `access_grants` | `club_access_grants` | +| 4-Phasen-Rollout | ✅ | ✅ | +| Registry-first neue IDs | ⚠️ teils hardcoded | ✅ `rights_registrations/` | + +--- + +## 8. Entscheidungsregeln für neue Produkte + +### 8.1 Pflicht (alle Apps mit Auth) + +- [ ] Session → `profile_id`; kein Client-Header als Autorität +- [ ] Entitlements-Auflösung **eine Funktion pro Ebene** (Capability, Feature) +- [ ] API-Enforcement vor UI-Gate +- [ ] 4-Phasen-Rollout dokumentiert +- [ ] Produkt-Profil (P1–P4) gewählt + Abweichungen in §6.2 + +### 8.2 Wenn Personal App (P1) + +- [ ] Primäres Subjekt = Account +- [ ] `tenant`-Block in API = `null` (DEV-01-Analog) +- [ ] Feature-Registry + Tier-Matrix + +### 8.3 Wenn Mandanten-App (P2) + +- [ ] Primäres Subjekt = Tenant +- [ ] `TenantContext` + Governance getrennt von Capabilities +- [ ] Capabilities registry-first +- [ ] Account nur Lifecycle (verified, member) — kein Tier nötig (DEV-05-Analog erlaubt) + +### 8.4 Wenn Minimal App (P3) + +- [ ] Explizit: „keine Limits“ oder nur Boolean-Features — in §6.2 eintragen +- [ ] Kein halbes Mitai-v9c kopieren + +--- + +## 9. Konvergenz-Roadmap (optional, nicht blockierend) + +| Schritt | Mitai | Shinkan | Familie | +|---------|-------|---------|---------| +| **Kurz** | Legacy Session-Flags entfernen | `CAPABILITY_ENFORCE` / `CLUB_FEATURE_ENFORCE` Prod | DEV-04, DEV-08 schließen | +| **Mittel** | `/me/entitlements` Account-Block | Snapshot um `tenant`-Typ metadata erweitern | Facade alte APIs | +| **Lang** | Optional `tenant_id` reservieren (null) | Optional Account-Tier für Cross-Sell | Shared package `jinkendo_entitlements` | +| **Vision** | SSO + zentraler Billing | Vereins-Abo Stripe | `CENTRAL_SUBSCRIPTION_SYSTEM` | + +**Wichtig:** Konvergenz ist **empfohlen**, nicht Pflicht — solange §6.2 aktuell bleibt. + +--- + +## 10. Anti-Patterns (familienweit verboten) + +1. Tier- oder Plan-Namen in React-Komponenten hardcoden +2. Limit-Logik nur im Frontend +3. Shinkan-Vereinslimits über Mitai `check_feature_access(profile_id)` +4. Capability-Check durch Governance ersetzen (oder umgekehrt) +5. Neue Feature-IDs nur in SQL-Migration ohne Registry +6. Enforcement Phase 4 „vergessen“ bei paid Features ohne dokumentierte Probe-Phase +7. Undokumentierte Produkt-Abweichung (nicht in §6.2) + +--- + +## 11. Offene Familien-Entscheidungen (Backlog) + +| ID | Frage | Optionen | Default wenn unentschieden | +|----|-------|----------|----------------------------| +| **FD-01** | Atomares check+increment | DB-Lock / Transaction / Queue | Status quo + Retry-Hinweis in Doku | +| **FD-02** | Shared Python-Modul | Monorepo-Paket vs. Copy+Sync | Copy+Sync mit gleicher API-Shape | +| **FD-03** | Capability-Namespace global | `jinkendo.*` vs. app-prefix | `{app}.{domain}.{action}` | +| **FD-04** | Mitai bekommt Capability-Layer? | ja/nein/später | nein (DEV-02) bis Bedarf | +| **FD-05** | Ein `features.app` für alle Apps | gemeinsame DB vs. pro Deploy | pro Deploy (heute) | + +--- + +## 12. Verwandte Dokumente + +| Dokument | Inhalt | +|----------|--------| +| [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) | Vollständiger Mitai ↔ Shinkan Abgleich | +| [design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) | Shinkan Ist-Prinzipien | +| [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md) | Governance (Ebene D) | +| Mitai Foundation #3 | Feature & Entitlement Ist Mitai | +| Shinkan `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | Vereins-Abo Detail | +| Shinkan `CAPABILITY_CATALOG.v1.md` | Capability-IDs | + +--- + +## 13. Changelog + +| Datum | Änderung | +|-------|----------| +| 2026-07-04 | Entwurf: Familien-Standard, Produkt-Profile P1–P4, Abweichungs-Katalog DEV-01–11 | diff --git a/docs/reference/design-principles/mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..2e18900 --- /dev/null +++ b/docs/reference/design-principles/mitai/AUTH_SESSION_DESIGN_PRINCIPLES.md @@ -0,0 +1,326 @@ +# Auth & Session – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Authentifizierung, Session-Management, rollenbasierte API-Zugriffe — kein Mandanten-/SSO-System, keine Zahlungs-Auth + +**Serie:** Designprinzipien für Produktfamilie · Dokument 5 von n +**Vorgänger:** [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Auth-Kern | `backend/auth.py` | +| Auth-Endpoints | `backend/routers/auth.py` | +| Profile | `backend/routers/profiles.py` | +| Frontend | `frontend/src/context/AuthContext.jsx`, `ProfileContext.jsx`, `utils/api.js` | +| DB | `profiles`, `sessions` | +| Architektur-Regeln | `.claude/rules/ARCHITECTURE.md`, `CLAUDE.md` § Auth | +| Vision (nicht implementiert) | `CENTRAL_SUBSCRIPTION_SYSTEM.md` (SSO/JWT) | + +--- + +## Modul + +**Auth & Session** + +Server-seitige, token-basierte Authentifizierung mit FastAPI-Dependencies — **Identität und Rolle**, getrennt von Feature-Entitlements und fachlicher Logik. + +--- + +## Fachliche Verantwortung + +Das Modul übernimmt: + +1. **Identität** — Wer ist eingeloggt? (`profiles` + Passwort/bcrypt) +2. **Session** — Opaque Token in `sessions`, Ablaufzeit, Logout +3. **API-Gate** — `require_auth`, `require_admin`, `require_auth_flexible` +4. **Passwort-Lifecycle** — Hash, Verify, Reset, Registrierung, E-Mail-Verifizierung +5. **Rollen** — `profiles.role`: `user` \| `admin` (grobbinsenartig) + +Es übernimmt **nicht**: + +- Feature-Limits / Tier (→ Feature & Entitlement System, gleiche `auth.py`-Datei aber logisch getrennt) +- Mandanten-Isolation / Org-Workspaces +- OAuth/SSO/JWT (nur Vision) +- Authorization auf Datensatzebene (Row-Level Security) + +### Was Mitai **ist** vs. **nicht ist** + +| Mitai | Produktfamilien-Muster | +|-------|------------------------| +| 1 Login = 1 Profil (E-Mail) | ✅ Account-Modell | +| Historisch Multi-Profil auf einer Instanz | ⚠️ Legacy (`/profiles`, `X-Profile-Id`) | +| Self-hosted Einzelinstanz | ✅ Kein Multi-Tenant-SaaS | +| Session-Token in DB | ✅ Server-side Session Store | +| Zentrale Jinkendo-Auth (Vision) | ❌ nicht gebaut | + +--- + +## Administrierte vs. code-definierte Konfiguration + +| Konfiguration | Speicherort | Administrierbar? | +|---------------|-------------|------------------| +| Nutzer-Stammdaten, Rolle | `profiles` | Admin (User-Verwaltung) / Self-Service | +| Session-Laufzeit | `profiles.session_days` (Default 30) | Profil/Admin | +| Passwort-Hash | `profiles.pin_hash` | Nutzer (change pin) | +| E-Mail-Verifizierung | `email_verified`, Token-Felder | System | +| Trial-Ende | `trial_ends_at` | System bei Registrierung | +| SMTP | Env (`SMTP_*`, `APP_URL`) | Deploy | +| Rate Limits Login/Register | Code (`5/min`, `3/hour`) | Code | + +**Hardcodiert:** bcrypt, Token-Länge (`secrets.token_urlsafe(32)`), Rollen-Enum (`user`/`admin`), Header-Name `X-Auth-Token`. + +--- + +## Session- und Auth-Flow + +``` +Login (email + password) + → verify_pin (bcrypt | legacy SHA256) + → optional bcrypt upgrade + → INSERT sessions (token, profile_id, expires_at) + → Client: localStorage bodytrack_token + +Request + → Header X-Auth-Token (api.js / AuthContext) + → get_session(token) JOIN profiles + → require_auth → session dict (profile_id, role, …) + +Logout + → DELETE sessions WHERE token=… + → Client: localStorage clear +``` + +**Sonderfall:** `require_auth_flexible` — Token via Header **oder** Query `ssetoken` (SSE, ``, Downloads). + +--- + +## Rollen + +| Rolle | Mechanismus | Typische Rechte | +|-------|-------------|-----------------| +| **user** | `profiles.role = 'user'` | Eigene Daten, Features nach Tier | +| **admin** | `require_admin` | Admin-Shell, Prompts, User, System | + +Kein feingranulares RBAC (keine Permission-Matrix). Admin ist Binär-Schalter. + +--- + +## Designprinzipien + +### 1. FastAPI-Dependencies als Auth-Gate + +| | | +|---|---| +| **Prinzip** | Jeder geschützte Endpoint nutzt `session: dict = Depends(require_auth)` als **separaten** Parameter — nie in `Header()` eingebettet. | +| **Begründung** | Verhindert ungeschützte Endpoints durch falsche Parameter-Signatur. | +| **Quelle** | `CLAUDE.md` § Kritische Regeln; `auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht linter-erzwungen; Legacy-Endpoints existieren. | + +### 2. Server-side opaque Sessions + +| | | +|---|---| +| **Prinzip** | Token ist zufällig, in DB gespeichert; Validierung über `sessions` + Ablauf — kein JWT mit Client-Claims. | +| **Begründung** | Revocation (Logout), kein Trust in Client-Payload; einfaches Modell für Self-Hosted. | +| **Quelle** | `sessions` Tabelle; `make_token()`, `get_session()` | +| **Tragfähigkeit** | **hoch** (Single-App, Self-Hosted) | +| **Einschränkung** | Skalierung multi-node braucht shared session store; SSO-Familie braucht anderes Modell. | + +### 3. profile_id aus Session, nicht aus Client + +| | | +|---|---| +| **Prinzip** | Autoritative Identität für neue Endpoints: `session['profile_id']` — Client darf Profil nicht wählen. | +| **Begründung** | Verhindert IDOR (Zugriff auf fremde Profile). | +| **Quelle** | `routers/goals.py`, `routers/prompts.py`; Architektur-Intent in `CLAUDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy `get_pid(x_profile_id)` akzeptiert `X-Profile-Id` **ohne** Session-Abgleich — siehe Nicht übernehmen. | + +### 4. bcrypt mit Legacy-Migration + +| | | +|---|---| +| **Prinzip** | Passwörter mit bcrypt; SHA256-Legacy beim Login erkannt und transparent auf bcrypt upgraded. | +| **Begründung** | Kein Big-Bang-Migration; sichere Hashes ohne Nutzer-Zwangs-Reset. | +| **Quelle** | `verify_pin()`, Login in `routers/auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Upgrade nur bei erfolgreichem Login. | + +### 5. Rate Limiting auf Auth-Endpoints + +| | | +|---|---| +| **Prinzip** | Login, Register, Forgot-Password, Resend-Verification mit `slowapi`-Limits (IP-basiert). | +| **Begründung** | Brute-Force- und Abuse-Schutz. | +| **Quelle** | `routers/auth.py` (`5/minute`, `3/hour`); `main.py` Limiter | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | IP-only; kein account-based lockout. | + +### 6. Keine E-Mail-Enumeration bei sensiblen Flows + +| | | +|---|---| +| **Prinzip** | Forgot-Password und Resend-Verification liefern generische Erfolgsmeldung, auch wenn E-Mail unbekannt. | +| **Begründung** | Privacy; erschwert Account-Scraping. | +| **Quelle** | `password_reset_request`, `resend_verification` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Register sagt „E-Mail bereits registriert“ (Enumeration möglich). | + +### 7. E-Mail-Verifizierung vor voller Nutzung + +| | | +|---|---| +| **Prinzip** | Self-Register setzt `email_verified=FALSE`; Verify-Endpoint aktiviert + Auto-Login-Session. | +| **Begründung** | Valide Kontaktadresse; Spam-Reduktion. | +| **Quelle** | `register`, `verify_email` in `routers/auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht überall im Backend erzwungen (Login ohne verified check?). | + +### 8. Flexible Auth für technische Clients + +| | | +|---|---| +| **Prinzip** | `require_auth_flexible`: gleiche Session-Validierung via Header oder `?ssetoken=` für SSE/Bilder. | +| **Begründung** | Browser-APIs ohne Custom Headers. | +| **Quelle** | `auth.py`; Prompt SSE `/execute-stream` | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Token in URL kann in Logs/Referrer leaken — kurze Sessions / HTTPS Pflicht. | + +### 9. Zentraler API-Client mit Token-Injektion + +| | | +|---|---| +| **Prinzip** | Frontend: `api.js` injiziert `X-Auth-Token` automatisch — kein scattered `fetch` ohne Auth. | +| **Begründung** | Konsistenz; eine Stelle für Token-Handling. | +| **Quelle** | `utils/api.js` → `hdrs()`; `getToken()` aus AuthContext | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne Komponenten umgehen noch `api.js` (SettingsPage, EmailSettings). | + +### 10. Auth getrennt von Authorization (Features) + +| | | +|---|---| +| **Prinzip** | `require_auth` = identifiziert; `check_feature_access` = berechtigt für Aktion — nacheinander im Router. | +| **Begründung** | Klare Schichten; Auth-Modul nicht mit Tier-Logik vermischen (auch wenn Datei `auth.py` beides enthält). | +| **Quelle** | `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md`; Router-Muster | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy Profil-Flags `ai_enabled`, `export_enabled` parallel zum Feature-System. | + +### 11. Admin-Gate im Frontend und Backend + +| | | +|---|---| +| **Prinzip** | Backend: `require_admin`; Frontend: `RequireAdmin` + `isAdmin` aus Session-Rolle. | +| **Begründung** | UX-Navigation + API-Sicherheit (Frontend allein reicht nicht). | +| **Quelle** | `RequireAdmin.jsx`; `require_admin()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne Routen (Workflow-Editor) ohne Frontend-Admin-Gate. | + +### 12. Session-Kontext im Frontend + +| | | +|---|---| +| **Prinzip** | `AuthProvider` hält `{ token, profile_id, role, profile }`; App setzt `setProfileId(session.profile_id)` für API. | +| **Begründung** | Single React-Tree für Login-State; Re-Validate via `/auth/me` beim Start. | +| **Quelle** | `AuthContext.jsx`; `App.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `ProfileContext` lädt alle Profile — Multi-Profil-UX Rest; Session-Profil ist Kanon. | + +--- + +## Nicht übernehmen + +1. **`get_pid(X-Profile-Id)` ohne Session-Bindung** — Client kann fremde `profile_id` senden; IDOR-Risiko. Kanon: immer `session['profile_id']` oder explizite Admin-Impersonation mit Audit. + +2. **Profile-CRUD nur mit `require_auth`** — `/profiles` listet alle Nutzer für jeden Authentifizierten (Kommentar „admin“, kein `require_admin`). Für Familien-Architektur: strikte Admin-Gates. + +3. **Dual-System Profil-Flags vs. Features** — `ai_enabled`, `export_enabled`, `ai_limit_day` in Session-Query neben v9c Feature-Registry. + +4. **localStorage-Key-Inkonsistenz** — `bodytrack_token` vs. `mitai-jinkendo_active_profile` (historischer App-Name). + +5. **Direktes `fetch` ohne `api.js`** — umgeht Token-/Error-Konvention. + +6. **Reset-Token in `sessions`-Tabelle** — `reset_{token}` mischt Session-Typen in einer Tabelle; OK für MVP, für Familie: getrennte Token-Typen/Tabellen. + +7. **Kein JWT/SSO trotz Produktfamilien-Vision** — `CENTRAL_SUBSCRIPTION_SYSTEM.md` beschreibt `auth.jinkendo.de` — Mitai-Implementierung ist **nicht** das Zielbild für Cross-App-SSO. + +8. **Multi-Profil-Haushalt ohne klares Modell** — Legacy Multi-Profile auf einer Instanz vs. 1 Account = 1 Profil; für neue Apps Modell explizit wählen. + +9. **Role als einziges RBAC** — reicht für Admin/User, nicht für feingranulare Permissions. + +10. **Session-Query mit veralteten Profil-Spalten** — `get_session` SELECT enthält Legacy-Felder statt nur Identität + Rolle. + +11. **Fehlende erzwungene E-Mail-Verified-Prüfung** — Registrierung setzt Flag, Login prüft es nicht offensichtlich. + +12. **Debug-Print in Auth-Modul** — `print("[AUTH.PY] Module loaded…")` in Produktionscode. + +--- + +## Abgrenzung zu anderen Serien-Dokumenten + +| Thema | Dokument | +|-------|----------| +| Tier, Limits, Quotas | [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) | +| Zentrale SSO/Abo-Vision | [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) | +| API-First / Router | `ARCHITECTURE.md` §1 | + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/ +├── auth.py # Session, require_*, Feature-Access (v9c) +└── routers/ + ├── auth.py # login, logout, register, verify, reset + └── profiles.py # CRUD, get_pid (Legacy) + +frontend/src/ +├── context/AuthContext.jsx +├── context/ProfileContext.jsx +├── layouts/RequireAdmin.jsx +└── utils/api.js # Token-Injektion + +DB: +├── profiles # Identität, Rolle, Hash, Tier, Trial +└── sessions # token → profile_id, expires_at +``` + +**Endpoints (Auswahl):** + +| Endpoint | Auth | +|----------|------| +| `POST /api/auth/login` | Public + Rate limit | +| `POST /api/auth/logout` | Token optional | +| `GET /api/auth/me` | require_auth | +| `POST /api/auth/register` | Public + Rate limit | +| `GET /api/auth/verify/{token}` | Public | + +--- + +## Verwandte Dokumentation + +- Feature-Entitlements: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Architektur-Regeln Auth: `CLAUDE.md`, `.claude/rules/ARCHITECTURE.md` +- GUI Admin-Guard: `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` +- SSO-Vision: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1 | Prompt Engine | ✅ | +| 2 | Data Layer | ✅ | +| 3 | Feature & Entitlement | ✅ | +| 4 | Registry-/Plugin-Muster | ✅ | +| 5 | Auth & Session | ✅ dieses Dokument | +| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` | +| 7 | Dashboard Widgets | ✅ | +| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` | +| 9 | Migration & Deploy | ✅ | diff --git a/docs/reference/design-principles/mitai/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..ae47122 --- /dev/null +++ b/docs/reference/design-principles/mitai/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md @@ -0,0 +1,363 @@ +# Dashboard Widgets – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Konfigurierbare Übersicht (Widget-Katalog, Layout, Entitlements, Frontend-Registry) — keine Chart-/Metrik-Berechnung + +**Serie:** Designprinzipien für Produktfamilie · Dokument 7 von n +**Vorgänger:** [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Katalog (SSoT) | `backend/widget_catalog.py` | +| Layout-Schema | `backend/dashboard_layout_schema.py` | +| Config-Validierung | `backend/dashboard_widget_config.py` | +| Entitlements | `backend/dashboard_widget_entitlements.py`, `widget_feature_requirements_db.py` | +| Produkt-Standard | `backend/system_dashboard_product_default.py` | +| HTTP | `backend/routers/app_dashboard.py` | +| Frontend-Registry | `frontend/src/widgetSystem/dashboardWidgetRegistry.jsx` | +| Registrierung | `frontend/src/widgetSystem/registerDashboardWidgets.js` | +| Layout-Editor | `frontend/src/pages/DashboardConfigurePage.jsx` | +| Fehler-Isolation | `frontend/src/widgetSystem/WidgetErrorBoundary.jsx` | +| Leitfaden | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` | +| Registry-Meta | [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) | + +--- + +## Modul + +**Dashboard Widgets** + +Erweiterbares System für **konfigurierbare Startübersicht**: Backend-Katalog definiert erlaubte Widget-IDs; Nutzer speichern Reihenfolge, Ein/Aus und optionale `config` pro Profil; Frontend rendert über eine lokale Komponenten-Registry. + +--- + +## Fachliche Verantwortung + +Das Modul übernimmt: + +1. **Widget-Katalog** — IDs, Titel, Beschreibung, optionale Feature-Anforderung (`requires_feature`). +2. **Layout-Persistenz** — `profiles.dashboard_layout` (JSON v1: `{ version, widgets[] }`). +3. **Validierung** — Erlaubte IDs, keine Duplikate, max. 32 Widgets, mindestens eines aktiv. +4. **Pro-Widget-Config** — Whitelist pro Widget-ID; Normalisierung beim Speichern. +5. **Standard-Layouts** — Code-Fallback (`DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS`), Admin-Override (`system_config`), Lab-Template (`DEFAULT_LAB_WIDGET_IDS`). +6. **Entitlements** — `allowed` im Katalog; Layout bereinigt bei fehlender Berechtigung. +7. **Frontend-Rendering** — Registry mappt Katalog-ID → React-Komponente + Props aus `layoutEntry.config`. +8. **Nutzer-Konfigurator** — „Übersicht anpassen“ (Sortieren, Toggle, Config-Editoren). + +Es übernimmt **nicht**: + +- Berechnung von KPIs, Charts, Scores (→ Data Layer + Chart-Endpoints, siehe [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md)) +- Tier-/Subscription-Logik in Widgets (→ Feature System, siehe [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)) +- Prompt-/KI-Ausführung (Widget zeigt nur UI; Pipeline läuft über eigene API) + +### Datenfluss (Happy Path) + +``` +WIDGET_CATALOG (Backend) + → GET /api/app/widgets/catalog (+ allowed via check_feature_access) + → GET /api/app/dashboard-layout + → coalesce_effective_layout (Profil oder Standard) + → merge_missing_catalog_widgets (neue IDs anhängen) + → apply_entitlements_to_layout_dict + → Frontend: ensureDashboardWidgetsRegistered() + → WidgetRenderer: enabled widgets → mapProps(layoutEntry.config) → Component + → PUT /api/app/dashboard-layout (Pydantic + Entitlements + speichern) +``` + +### Layout-Eintrag (Struktur) + +| Feld | Bedeutung | +|------|-----------| +| `id` | Muss in `WIDGET_CATALOG` existieren | +| `enabled` | Sichtbar auf der Übersicht | +| `config` | Optional; nur für whitelisted Widgets mit Inhalt erlaubt | + +--- + +## Administrierte vs. code-definierte Konfiguration + +| Konfiguration | Speicherort | Wer pflegt? | +|---------------|-------------|-------------| +| Widget-IDs, Metadaten, Default-Aktivierung | `widget_catalog.py` | Entwickler | +| Produkt-Standard-Layout (live) | `system_config.dashboard_product_default` | Admin | +| Produkt-Standard (Fallback) | `DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS` | Entwickler | +| Lab-/Editor-Standard | `DEFAULT_LAB_WIDGET_IDS` | Entwickler | +| Nutzer-Layout | `profiles.dashboard_layout` | Nutzer | +| Feature-Gate (Katalog) | `requires_feature` pro Eintrag | Entwickler | +| Feature-Gate (Override) | `widget_feature_requirements` + Marker | Admin | +| Config-Schema pro Widget | `dashboard_widget_config.py` | Entwickler | +| React-Komponente | `registerDashboardWidgets.js` | Entwickler | + +--- + +## Designprinzipien + +### 1. Backend-Katalog als Single Source of Truth für IDs + +| | | +|---|---| +| **Prinzip** | `WIDGET_CATALOG` ist die einzige autoritative Liste erlaubter Widget-IDs; `ALLOWED_WIDGET_IDS` wird daraus abgeleitet — nicht manuell duplizieren. | +| **Begründung** | Layout-Validator, API und Default-Layouts bleiben synchron; unbekannte IDs werden beim PUT abgewiesen. | +| **Quelle** | `widget_catalog.py`; Agent-Guide §4 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Registry ist zweite manuelle Bindung (kein Build-Time-Gate). | + +### 2. Dual Registry: Backend-Kanon + Frontend-Komponentenbindung + +| | | +|---|---| +| **Prinzip** | Jede Katalog-ID braucht einen Eintrag in `registerDashboardWidget({ id, Component, mapProps })`; idempotent via `ensureDashboardWidgetsRegistered()`. | +| **Begründung** | React-Komponenten können nicht im Python-Katalog leben; explizite Zuordnung hält Bundle tree-shakeable. | +| **Quelle** | `registerDashboardWidgets.js`, `dashboardWidgetRegistry.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Fehlende Registrierung → Laufzeit „Unbekanntes Widget“, kein CI-Fail. | + +### 3. Layout als versioniertes Profil-JSON + +| | | +|---|---| +| **Prinzip** | Nutzer-Layout in `profiles.dashboard_layout`; Schema `version: 1`, Liste `{ id, enabled, config? }`. | +| **Begründung** | Pro Profil anpassbar; Reset auf NULL → System-Standard. | +| **Quelle** | `DashboardLayoutPayload`, `app_dashboard.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nur v1; Schema-Evolution braucht Migrationspfad. | + +### 4. Validierung an der API-Grenze (Pydantic) + +| | | +|---|---| +| **Prinzip** | Jeder GET/PUT-Pfad normalisiert über `DashboardLayoutPayload`: Duplikat-IDs, unbekannte IDs, leeres Layout (kein enabled) → Fehler. | +| **Begründung** | Keine korrupten Layouts in der DB; Frontend kann auf gültige Struktur vertrauen. | +| **Quelle** | `dashboard_layout_schema.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Ungültiges gespeichertes Layout → Fallback auf Standard (`coalesce_effective_layout`). | + +### 5. Config nur für explizit whitelisted Widgets + +| | | +|---|---| +| **Prinzip** | `WIDGETS_ALLOWING_CONFIG`: Widgets **ohne** Eintrag dürfen nur leere `config` haben; sonst Validierungsfehler. | +| **Begründung** | Verhindert unkontrollierte JSON-Blobs und stille Ignorierung unbekannter Keys. | +| **Quelle** | `dashboard_widget_config.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Pro Widget heterogene Schemas (chart_days vs. KPI-Tiles vs. show_*-Booleans). | + +### 6. Strikte Config-Keys (Whitelist, Normalisierung) + +| | | +|---|---| +| **Prinzip** | Unbekannte Keys in `config` werden abgelehnt; bekannte Keys typgeprüft und normalisiert (z. B. `chart_days` 7–90, KPI max. 9 Kacheln). | +| **Begründung** | Vorhersagbares Verhalten; Editor und Backend stimmen überein. | +| **Quelle** | `_validate_chart_days_only`, `_validate_kpi_board_config`, History-Viz-Defaults | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Normalizer (`bodyChartDays.js`, `*VizConfig.js`) teils parallel — Abweichungsrisiko. | + +### 7. Config-Größenlimit + +| | | +|---|---| +| **Prinzip** | `MAX_WIDGET_CONFIG_JSON_BYTES` (3072) — keine großen Blobs in Layout-JSON. | +| **Begründung** | DB-Spalte und API-Payload bleiben schlank; Config = Präferenzen, nicht Datenspeicher. | +| **Quelle** | `dashboard_widget_config.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 8. Katalog-Erweiterung ohne Layout-Reset + +| | | +|---|---| +| **Prinzip** | `merge_missing_catalog_widgets` hängt neue Katalog-IDs ans bestehende Layout an (`enabled: false`). | +| **Begründung** | Nutzer müssen nach Deploy nicht resetten; „Übersicht anpassen“ zeigt neue Optionen. | +| **Quelle** | `dashboard_layout_schema.py`; Agent-Guide | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Reihenfolge neuer Widgets immer am Ende. | + +### 9. Mehrere Standard-Layouts (Produkt vs. Lab vs. Admin) + +| | | +|---|---| +| **Prinzip** | **Produkt:** `get_product_default_base_dict` (DB-Override oder `DEFAULT_PRODUCT_DASHBOARD_WIDGET_IDS`). **Lab:** `lab_default_layout_dict` für Editor/Reset. **Nutzer:** eigenes JSON oder NULL. | +| **Begründung** | Onboarding-Default getrennt von Entwickler-/Lab-Template; Admin kann Produkt-Standard ohne Deploy ändern. | +| **Quelle** | `system_dashboard_product_default.py`, `widget_catalog.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Feldname `lab_default_layout` historisch irreführend (Servertemplate, nicht nur Lab). | + +### 10. Entitlements zentral, Widgets konsumieren nur `allowed` + +| | | +|---|---| +| **Prinzip** | Sichtbarkeit über `check_feature_access` in `widget_id_allowed`; Katalog liefert `allowed` pro Zeile. Widgets/React duplizieren **keine** Tier-Logik. | +| **Begründung** | Eine Wahrheit für „darf angezeigt werden“; spätere Feature-Cluster ohne Widget-Refactor. | +| **Quelle** | `dashboard_widget_entitlements.py`; Agent-Guide §0 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Inhalts-Endpoints (Charts, KI) brauchen **eigenes** Feature-Gate (Defense in Depth). | + +### 11. Layout-Persistenz bereinigt nicht erlaubte Widgets + +| | | +|---|---| +| **Prinzip** | `apply_entitlements_to_layout_dict`: bei fehlender Berechtigung `enabled: false`; mindestens `welcome` bleibt aktiv. GET und PUT wenden an. | +| **Begründung** | Keine „gespeichert aber nie sichtbar“-Zombies; Downgrade/Tier-Wechsel degradieren gracefully. | +| **Quelle** | `dashboard_widget_entitlements.py`, `app_dashboard.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Policy ist deaktivieren, nicht entfernen — IDs bleiben im JSON. | + +### 12. DB-Override für Widget-Feature-Anforderungen + +| | | +|---|---| +| **Prinzip** | Katalog-`requires_feature` ist Default; Admin kann per `dashboard_widget_requirement_custom` + `widget_feature_requirements` überschreiben (AND-Semantik). | +| **Begründung** | Runtime-Anpassung ohne Code-Deploy; Marker-Zeile trennt Custom von Fallback. | +| **Quelle** | `widget_feature_requirements_db.py`, Migration 041 | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Zwei Quellen (Code + DB) — Dokumentation und Admin-UI nötig. | + +### 13. mapProps: Layout-Config → Komponenten-Props + +| | | +|---|---| +| **Prinzip** | Registry-Eintrag mappt `ctx.layoutEntry.config` auf typisierte Props (`chartDays`, `kpiConfig`, `bodyHistoryVizConfig`, …). | +| **Begründung** | Widget-Komponenten bleiben layout-agnostisch; Normalisierung an einer Stelle pro ID. | +| **Quelle** | `registerDashboardWidgets.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Teilweise Normalisierung in Widget statt in `mapProps` (inkonsistent, aber dokumentiert). | + +### 14. Refresh-Koordination über Context + +| | | +|---|---| +| **Prinzip** | `refreshTick` + `requestRefresh()` im Render-Context; Widgets laden Daten bei Tick-Änderung neu; Aktionen (z. B. Schnelleingabe) rufen `requestRefresh`. | +| **Begründung** | Kein globales State-Monster; gezielte Invalidierung nach Capture. | +| **Quelle** | `dashboardWidgetRegistry.jsx`, Widget-Implementierungen | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein feingranulares Cache pro Widget. | + +### 15. Fehler-Isolation pro Widget + +| | | +|---|---| +| **Prinzip** | `WidgetErrorBoundary` um jede Instanz — Render-Fehler crashen nicht die ganze Übersicht. | +| **Begründung** | Robuste PWA; ein defektes Chart blockiert nicht Gewicht-Eingabe. | +| **Quelle** | `WidgetErrorBoundary.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein automatisches Retry/Reporting. | + +### 16. Konfigurator filtert nach `allowed` + +| | | +|---|---| +| **Prinzip** | `DashboardConfigurePage` blendet Widgets mit `allowed === false` aus der bearbeitbaren Liste aus. | +| **Begründung** | Nutzer sehen keine Optionen, die sie nicht nutzen dürfen (Agent-Guide A2). | +| **Quelle** | `DashboardConfigurePage.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Bereits gespeicherte disabled Einträge können im JSON verbleiben. | + +### 17. Widgets konsumieren Data Layer, duplizieren keine Logik + +| | | +|---|---| +| **Prinzip** | Chart-/KPI-Widgets rufen Chart-Endpoints bzw. API-Fassaden auf; Berechnungen leben in `data_layer/`, nicht in Widget-JS. | +| **Begründung** | Gleiche Zahlen wie Verlauf, KI-Platzhalter und Export. | +| **Quelle** | Layer-2b `*_history_viz`-Widgets; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Widgets unter `dashboard-widgets-legacy/` teils ältere Fetch-Pfade. | + +### 18. Dedizierte Config-Editoren für komplexe Widgets + +| | | +|---|---| +| **Prinzip** | Einfache `chart_days`: Set `CHART_DAYS_WIDGET_IDS` im Layout-Editor; komplexe Config: eigene Editor-Komponenten (`KpiBoardConfigEditor`, `*VizConfigEditor`). | +| **Begründung** | UX skaliert mit Config-Komplexität; Backend-Schema und Editor bleiben parallel pflegbar. | +| **Quelle** | `widgetSystem/*ConfigEditor.jsx`, Agent-Guide §3.4 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Jedes neue komplexe Widget = Editor + Validator + Tests. | + +--- + +## Nicht übernehmen + +1. **Tier-Logik in React-Widgets** — nur `allowed` aus API; keine hardcodierten Plan-Namen. + +2. **`ALLOWED_WIDGET_IDS` manuell pflegen** — immer aus Katalog ableiten. + +3. **Config ohne Backend-Whitelist** — stille Ignorierung unbekannter Keys in Widgets. + +4. **Nur UI-Gating ohne API-Absicherung** — Chart-/KI-/Export-Endpoints weiterhin `check_feature_access` (403). + +5. **Frontend-Registry vergessen** — Katalog-Eintrag ohne `registerDashboardWidget` → Laufzeitfehler statt Build-Fail. + +6. **Große Daten in `config`** — Layout ist Präferenzspeicher, kein Blob-Store (>3072 Bytes). + +7. **Doppelte Widget-IDs im Layout** — Validator verbietet; Editor muss dasselbe erzwingen. + +8. **Neue Katalog-IDs ohne `merge_missing_catalog_widgets`-Pfad** — Nutzer-Layouts veralten unsichtbar. + +9. **Kompletter Katalog nur in DB** — schwer testbar; Code-Katalog + optionale Feature-Overrides ist das Muster. + +10. **Evidence-Pflicht à la Placeholder-Registry** — 22 Metadatenfelder pro Widget wären Overkill; Tiefe an Risiko anpassen ([REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md)). + +11. **Ein Default für alles** — Produkt-Onboarding, Lab-Template und Admin-Override haben unterschiedliche Zwecke; nicht vermischen. + +12. **Fehlender Cross-Check Backend ↔ Frontend IDs** — empfohener Test/Gate fehlt im Ist-Stand; nicht als „optional“ ignorieren. + +13. **Berechnungslogik im Widget** — KPIs/Scores gehören in Data Layer, nicht in `useEffect`-Mathe. + +14. **Entitlements beim Speichern ablehnen statt deaktivieren** — Mitai wählt deaktivieren; Policy bewusst festlegen und dokumentieren. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/ +├── widget_catalog.py # WIDGET_CATALOG, DEFAULT_*_IDS +├── dashboard_layout_schema.py # Pydantic, merge_missing, defaults +├── dashboard_widget_config.py # WIDGETS_ALLOWING_CONFIG, Validatoren +├── dashboard_widget_entitlements.py # allowed, layout cleanup +├── widget_feature_requirements_db.py # Admin-Override +├── system_dashboard_product_default.py +└── routers/app_dashboard.py + +frontend/src/ +├── widgetSystem/ +│ ├── dashboardWidgetRegistry.jsx +│ ├── registerDashboardWidgets.js +│ ├── layoutEditor.js +│ ├── bodyChartDays.js, *VizConfig.js +│ └── *ConfigEditor.jsx +├── components/dashboard-widgets/ # Produkt-Widgets +├── components/dashboard-widgets-legacy/ # ältere Kern-Widgets +└── pages/DashboardConfigurePage.jsx + +DB: +├── profiles.dashboard_layout +├── system_config.dashboard_product_default +├── dashboard_widget_requirement_custom +└── widget_feature_requirements +``` + +**Katalog-Umfang:** ~24 Widget-IDs (Stand `widget_catalog.py`); ~13 mit konfigurierbarer `config`. + +--- + +## Verwandte Dokumentation + +- Agent-Guide (normativ): [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) +- Registry-Meta: [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) +- Feature-Gates: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Datenberechnung: [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) +- Architektur §9: `.claude/rules/ARCHITECTURE.md` + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1–6 | … | ✅ | +| 7 | Dashboard Widgets | ✅ dieses Dokument | +| 8 | Navigation / IA | ✅ | +| 9 | Migration & Deploy | ✅ | diff --git a/docs/reference/design-principles/mitai/DATA_LAYER_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/DATA_LAYER_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..2278a89 --- /dev/null +++ b/docs/reference/design-principles/mitai/DATA_LAYER_DESIGN_PRINCIPLES.md @@ -0,0 +1,359 @@ +# Data Layer – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Multi-Layer Data Architecture (Phase 0c, Issue #53) — keine Mitai-Gesamtarchitektur, keine konkrete Gesundheits-/Ernährungsfachlogik als Produktinhalt + +**Serie:** Designprinzipien für Produktfamilie · Dokument 2 von n +**Vorgänger:** [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Metriken (Layer 1) | `backend/data_layer/*_metrics.py`, `scores.py`, `correlations.py` | +| Utilities | `backend/data_layer/utils.py` | +| Visualisierung (Layer 2b) | `*_chart_payloads.py`, `*_viz.py` | +| KI-Formatierung (Layer 2a-Hilfe) | `prompt_output_compact.py` | +| Persistenz-Orchestrierung | `activity_persistence_orchestrator.py`, `activity_session_metrics.py` | +| Konsumenten | `routers/charts.py`, `placeholder_resolver.py`, `routers/exportdata.py` | +| Leitfäden | `DATA_LAYER_EXTENSION_GUIDE.md`, `docs/issues/issue-53-phase-0c-multi-layer-architecture.md` | +| Architektur-Regel Import-Grenze | `.claude/rules/ARCHITECTURE.md` §8 | + +--- + +## Modul + +**Data Layer** (Phase 0c Multi-Layer Architecture, Issue #53) + +Zentrale Schicht für **Datenabruf, Berechnung und strukturierte Aufbereitung** — ohne UI-Formatierung, ohne Prompt-Texte, ohne Chart.js-spezifische Ausgabe in den Kern-Metrik-Modulen. + +--- + +## Fachliche Verantwortung + +Der Data Layer ist die **Single Source of Truth für alle abgeleiteten Messwerte und Metriken**. Er übernimmt: + +1. **Datenabruf** — Lesen aus PostgreSQL (profile-scoped), optional mit Quality-Filter. +2. **Berechnung** — Trends, Scores, Korrelationen, Aggregationen, Projektionen. +3. **Strukturierte Rückgabe** — Dicts/Listen mit numerischen Werten, Datumsfeldern, Metadaten (`confidence`, `data_points`). +4. **Konsumenten-Bereitstellung** — Charts (Layer 2b), KI-Platzhalter (Layer 2a via Resolver), Export, Router-Anreicherung. + +Er übernimmt **nicht**: + +- CSV-Parsing und Feld-Mapping (Import-Schicht) +- Prompt-Template-Auflösung (Prompt Engine) +- React-Rendering oder Frontend-Berechnungen +- Autorisierung / Feature-Limits (Auth-Schicht) + +### Schichtenmodell (Multi-Layer) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Layer 0: Persistenz (PostgreSQL) │ +│ weight_log, nutrition_log, activity_log, sleep_log, … │ +└──────────────────────────┬──────────────────────────────┘ + │ +┌──────────────────────────▼──────────────────────────────┐ +│ Layer 1: DATA LAYER (Metriken) │ +│ Strukturierte Daten · confidence · data_points │ +│ KEINE formatierten Strings · KEINE Chart.js-Objekte │ +└──────────────┬───────────────────────┬──────────────────┘ + │ │ + ▼ ▼ +┌──────────────────────────┐ ┌────────────────────────────┐ +│ Layer 2a: KI / Prompts │ │ Layer 2b: Visualisierung │ +│ placeholder_resolver │ │ *_chart_payloads, *_viz │ +│ prompt_output_compact │ │ routers/charts.py │ +└──────────────────────────┘ └────────────────────────────┘ +``` + +### Administrierte vs. code-definierte Konfiguration + +| Was | Wo | Administrierbar? | +|-----|-----|------------------| +| Berechnungslogik (Formeln, Fenster) | `data_layer/*.py` | ❌ Code + Review | +| Confidence-Schwellen | `data_layer/utils.py` | ❌ Code | +| Goal Mode / Focus Weights | DB (`profiles`, `user_focus_area_weights`) | ✅ Nutzer/Admin | +| Quality Filter (Profil) | DB (`profiles`) | ✅ Admin | +| Chart-Zeitfenster | Query-Parameter an API | ✅ Request | +| Referenzwerte (persönlich) | DB + `reference_values.py` | ✅ Nutzer | +| EAV Session Metrics | DB (`training_*_parameter`) | ✅ Admin | + +**Bewusst nicht hardcodiert in Routern:** Metrik-Berechnungen — Router delegieren an Data Layer. + +**Hardcodiert (Code):** Domänen-Module, Confidence-Regeln, Schwellen pro Metrik-Typ, TDEE-Fallback-Logik, Chart-Payload-Struktur. + +### Trennung: Metriken · Chart-Payloads · KI-Formatierung · Persistenz + +| Schicht | Module | Verantwortung | +|---------|--------|---------------| +| **Metriken** | `body_metrics.py`, `nutrition_metrics.py`, … | Reine Berechnung, strukturierte Dicts | +| **Chart-Payloads** | `nutrition_chart_payloads.py`, `correlation_chart_payloads.py`, … | Chart.js-kompatible `{ labels, datasets, metadata }` aus Layer-1-Daten | +| **Viz-Bundles** | `body_viz.py`, `fitness_viz.py`, … | Zusammengesetzte Dashboard-/History-Pakete für Frontend | +| **KI-Kompaktierung** | `prompt_output_compact.py` | Token-sparende Zahlen/JSON für Platzhalter | +| **Interpretation** | `*_interpretation.py`, `vital_signs_assessment.py` | Textliche Einordnung (WHO-Klassen etc.) — Grenze zu Layer 2a | +| **Persistenz-Orchestrator** | `activity_persistence_orchestrator.py` | Schreibpfade REST/CSV → DB + Nebenwirkungen (EAV, Eval) | + +### Konsumenten (wer ruft den Data Layer auf?) + +| Konsument | Muster | +|-----------|--------| +| `routers/charts.py` | Layer-1-Funktion + Chart-Payload-Builder | +| `placeholder_resolver.py` | Layer-1 → Formatierung/JSON für `{{placeholders}}` | +| `routers/exportdata.py` | `enrich_sessions_with_metrics`, `serialize_dates` | +| `routers/activity.py`, `csv_import.py` | `activity_persistence_orchestrator` (Schreiben) | +| `prompt_executor.execute_prompt_with_data` | ⚠️ teils Roh-SQL parallel zum Data Layer (Legacy) | + +### Rollen + +Der Data Layer hat **keine eigene Admin-UI**. Konfiguration erfolgt indirekt: + +- **Admin:** Training-Parameter, Attributprofile, Referenzwert-Typen, Quality-Filter +- **Nutzer:** Profildaten, Referenzwerte, Focus-Area-Gewichte (beeinflussen Scores) +- **Entwickler:** Neue Funktionen in `data_layer/` nach Extension Guide + +--- + +## Designprinzipien + +### 1. Single Source of Truth für Berechnungen + +| | | +|---|---| +| **Prinzip** | Jede Metrik wird **einmal** in `data_layer/` berechnet; Charts, KI und Export konsumieren dieselbe Funktion. | +| **Begründung** | Verhindert divergierende Zahlen zwischen Dashboard, Analyse und KI-Ausgabe. | +| **Quelle** | Issue #53 Executive Summary; `nutrition_chart_payloads.py` Kommentar „identisch zu GET /api/charts/energy-balance“ | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Pfade migriert (`insights._prepare_template_vars`, `execute_prompt_with_data` Roh-SQL). | + +### 2. Layer 1 liefert strukturierte Daten, keine formatierten Strings + +| | | +|---|---| +| **Prinzip** | Kern-Metrik-Funktionen geben Dicts mit `float`/`int`/`date` zurück — **keine** Strings mit Einheiten („86,1 kg“). | +| **Begründung** | Formatierung ist konsumentenspezifisch (DE-Locale, Chart-Achsen, KI-Token). | +| **Quelle** | `data_layer/__init__.py` Docstring: „NO FORMATTING. NO STRINGS WITH UNITS.“ | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `placeholder_resolver` und `*_interpretation` Module formatieren teils direkt — Grenze Layer 1/2a nicht überall scharf. | + +### 3. Pflicht-Metadaten: confidence + data_points + +| | | +|---|---| +| **Prinzip** | Jede Metrik-Funktion liefert mindestens `confidence` (`high`\|`medium`\|`low`\|`insufficient`) und `data_points`. | +| **Begründung** | UI/KI können Datenqualität kommunizieren; Debugging und Monitoring vereinfacht. | +| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` § Pflicht-Felder; `calculate_confidence()` in `utils.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht runtime-validiert; Disziplin per Code-Review. | + +### 4. Confidence nach Metrik-Typ und Zeitfenster + +| | | +|---|---| +| **Prinzip** | Schwellen unterscheiden `general`, `correlation`, `trend` und Fensterlänge (7d / 28d / 90d). | +| **Begründung** | Korrelationen brauchen mehr Paare; Trends messen Abdeckung (% der Tage). | +| **Quelle** | `data_layer/utils.py` → `calculate_confidence()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Schwellen global hardcodiert, nicht pro Metrik konfigurierbar. | + +### 5. Domänen-Module statt Monolith + +| | | +|---|---| +| **Prinzip** | Ein Python-Modul pro fachlichem Bereich (`body_metrics`, `nutrition_metrics`, …), max. ~500 Zeilen, dann Split. | +| **Begründung** | Wartbarkeit, klare Ownership, parallele Entwicklung. | +| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` § Modul-Struktur | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einige Module deutlich >500 Zeilen (Phase-0c-Wachstum). | + +### 6. Layer 2b: Chart-Payloads als Adapter + +| | | +|---|---| +| **Prinzip** | Chart.js-Strukturen leben in dedizierten `*_chart_payloads.py` / `*_viz.py`, nicht in Metrik-Modulen. | +| **Begründung** | Gleiche Metrik, verschiedene Visualisierungen; API-Endpoints bleiben dünn. | +| **Quelle** | `nutrition_chart_payloads.py`; `routers/charts.py` Imports | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Teilweise noch SQL-Duplikation in Payload-Buildern neben Layer-1-Aufruf. | + +### 7. Layer 2a-Hilfe: KI-spezifische Kompaktierung getrennt + +| | | +|---|---| +| **Prinzip** | Token-Reduktion für LLM-Kontext (`compact_float_for_prompt`, `compact_json_payload_for_prompts`) ist eigenes Modul, nicht in Metrik-Kern. | +| **Begründung** | KI hat andere Anforderungen als Charts (Präzision vs. Token-Kosten). | +| **Quelle** | `prompt_output_compact.py`; Tests in `tests/test_prompt_output_compact.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nur für KI-Pfad; Charts nutzen eigene Rundung. | + +### 8. Import-Grenze: Ingest vs. Interpretation + +| | | +|---|---| +| **Prinzip** | CSV-Import macht Mapping + Typkonvertierung + Duplikatlogik — **keine** fachliche Auswertung beim Insert. | +| **Begründung** | Semantik gehört in Layer 1+, sonst versteckte Business-Logik in Import-Adaptern. | +| **Quelle** | `ARCHITECTURE.md` §8; Issue #53 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Adapter (Apple-Schlaf-Aggregat, dedizierte Import-Endpoints) noch aktiv. | + +### 9. Persistenz-Orchestrator für Schreibpfade + +| | | +|---|---| +| **Prinzip** | Alle Schreibwege eines Domänenobjekts (REST, CSV, Legacy) laufen durch **einen** Orchestrator mit Nebenwirkungen (EAV, Evaluation). | +| **Begründung** | Konsistente Duplikat-Erkennung, Registry-Felder, keine divergierenden Insert-Logiken. | +| **Quelle** | `activity_persistence_orchestrator.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Bisher vor allem Aktivität; andere Domänen noch direkt in Routern. | + +### 10. Registry als Feld-Kanon (Activity) + +| | | +|---|---| +| **Prinzip** | Erlaubte persistierbare Felder für CSV/REST leiten sich aus `module_registry` ab, nicht aus Router-Hardcoding. | +| **Begründung** | Single Source of Truth für Import-Mappings und DB-Updates. | +| **Quelle** | `activity_data_canon.py`, `activity_persistence_orchestrator.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nur Activity vollständig; andere Module noch klassische Spalten-CRUD. | + +### 11. EAV-Anreicherung als Read-Layer + +| | | +|---|---| +| **Prinzip** | Session-Metriken (EAV) werden beim **Lesen** angereichert (`enrich_sessions_with_metrics`), nicht pro Consumer dupliziert. | +| **Begründung** | Ein Merge-Kanon für Liste, Detail, Export, Platzhalter. | +| **Quelle** | `activity_session_metrics.py`; `ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Domänenspezifisch (Training); Muster übertragbar. | + +### 12. Scores als composable Layer + +| | | +|---|---| +| **Prinzip** | Composite Scores (`scores.py`) kombinieren Domänen-Metriken mit nutzer-spezifischen Focus Weights — keine Score-Logik in Routern. | +| **Begründung** | Goal-Mode-/Focus-abhängige Gewichtung zentral, für KI und Dashboard gleich. | +| **Quelle** | `data_layer/scores.py`; Phase-0b-Fokus-System | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Eng an Mitai-Zielsystem gekoppelt; Muster „gewichtete Composite Scores“ ist generisch. | + +### 13. API-First: Router delegieren, rechnen nicht + +| | | +|---|---| +| **Prinzip** | `routers/charts.py` und ähnliche Endpoints rufen Data-Layer-Funktionen auf und mappen auf HTTP — keine Trend-Berechnung im Router. | +| **Begründung** | Testbarkeit; Frontend ohne Business-Logik. | +| **Quelle** | `ARCHITECTURE.md` §1.2 API-First | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `charts.py` ist groß (2246+ Zeilen) — viel Adapter-Code, aber Berechnung delegiert. | + +### 14. serialize_dates / safe_float als Querschnitt + +| | | +|---|---| +| **Prinzip** | JSON/API-Serialisierung (Dates, Decimal) zentral in `utils.py`, nicht pro Modul neu erfunden. | +| **Begründung** | PostgreSQL-Typen (DATE, DECIMAL) konsistent für API und Export. | +| **Quelle** | `data_layer/utils.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 15. Extension Guide als verbindlicher Entwicklungsvertrag + +| | | +|---|---| +| **Prinzip** | Neue Metriken folgen Template (Retrieve → Confidence → Early Return → Calculate → Return) und werden in `__init__.py` exportiert. | +| **Begründung** | Einheitliche Struktur für 97+ Funktionen und wachsende Codebase. | +| **Quelle** | `DATA_LAYER_EXTENSION_GUIDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Guide und Ist-Code divergieren teils (Modulgröße, `goals.py` noch nicht in `__init__`). | + +--- + +## Nicht übernehmen + +Muster, die sich nicht bewährt haben oder zu produktspezifisch sind: + +1. **Berechnungslogik in `placeholder_resolver.py`** — Phase-0b-Legacy; Resolver soll nur formatieren/aggregieren, nicht rechnen. + +2. **Paralleler Roh-SQL-Kontext in `prompt_executor.execute_prompt_with_data`** — lädt Modul-Rohdaten per SQL, obwohl Layer 1 existiert; zweite Wahrheit. + +3. **Legacy Insights-Pfad (`insights._prepare_template_vars`)** — eigene Variablen-Vorbereitung ohne Data Layer. + +4. **Import mit fachlicher Interpretation** — Apple-Schlaf-Aggregat und ähnliche Adapter verstecken Semantik im Ingest (Gitea #69). + +5. **Monolithische Router mit Inline-Berechnung** — vor Phase 0c; gelegentlich noch Reste in nicht migrierten Pfaden. + +6. **Interpretation vermischt mit Layer 1** — `*_interpretation.py` liefert teils fertige Texte; für Familien-Architektur klar als Layer 2a/2b markieren oder auslagern. + +7. **SQL-Duplikation in Chart-Payloads** — manche Payload-Builder führen eigene Queries statt ausschließlich Layer-1-Ergebnisse zu visualisieren. + +8. **Hardcodierte Confidence global** — funktioniert, aber nicht pro Metrik/Domäne konfigurierbar; Skalierung in Multi-Tenant-Produktfamilie prüfen. + +9. **Domänen-Module als Produktinhalt** — `body_metrics`, TDEE, WHR etc. sind Mitai-spezifisch; **Schichtenmodell** übernehmen, **Formeln** nicht blind kopieren. + +10. **Fehlende runtime-Validierung des Return-Schemas** — `confidence`/`data_points` per Konvention, nicht per TypedDict/Pydantic erzwungen. + +11. **Uneinheitliche Schreib-Orchestrierung** — nur Activity hat `persistence_orchestrator`; andere Domänen noch fragmentiert. + +12. **Riesige Einzeldateien** — einige Metrik-Module >>500 Zeilen widersprechen eigenem Extension Guide. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/data_layer/ +├── Kern-Metriken (Layer 1) +│ ├── body_metrics.py +│ ├── nutrition_metrics.py +│ ├── activity_metrics.py +│ ├── recovery_metrics.py +│ ├── health_metrics.py +│ ├── scores.py +│ └── correlations.py +├── Visualisierung (Layer 2b) +│ ├── *_chart_payloads.py (nutrition, recovery, correlation) +│ └── *_viz.py (body, nutrition, fitness, recovery, history_overview) +├── KI / Format (Layer 2a-Nähe) +│ ├── prompt_output_compact.py +│ └── *_interpretation.py +├── Persistenz / EAV +│ ├── activity_persistence_orchestrator.py +│ ├── activity_session_metrics.py +│ └── activity_data_canon.py +├── Querschnitt +│ ├── utils.py +│ ├── reference_values.py +│ └── nutrition_body_merge.py +└── __init__.py (Exports) +``` + +**Konsumenten-Endpoints (Auswahl):** 20+ Chart-Endpoints in `routers/charts.py` (E1–E5, A1–A8, R1–R5, C1–C4). + +--- + +## Verwandte Dokumentation + +- Issue #53 Abschluss: [issue-53-phase-0c-multi-layer-architecture.md](../../../../docs/issues/issue-53-phase-0c-multi-layer-architecture.md) +- Extension Guide: [DATA_LAYER_EXTENSION_GUIDE.md](../../technical/DATA_LAYER_EXTENSION_GUIDE.md) +- Fachliche Datenarchitektur: [DATA_ARCHITECTURE.md](../../functional/DATA_ARCHITECTURE.md) +- Import-Grenze: [ARCHITECTURE.md](../../../rules/ARCHITECTURE.md) §8 +- Platzhalter-Anbindung: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md) +- Prompt Engine (Konsument Layer 2a): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) +- Feature & Entitlement: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Datei (geplant) | +|---|-------|-----------------| +| 1 | Prompt Engine | ✅ `PROMPT_ENGINE_DESIGN_PRINCIPLES.md` | +| 2 | Data Layer | ✅ dieses Dokument | +| 3 | Feature & Entitlement System | ✅ `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` | +| 4 | Registry-/Plugin-Muster | ✅ `REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` | +| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` | +| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` | +| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` | +| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` | +| 9 | Migration & Deploy | ✅ `MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` | diff --git a/docs/reference/design-principles/mitai/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..f2f3fcd --- /dev/null +++ b/docs/reference/design-principles/mitai/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md @@ -0,0 +1,370 @@ +# Feature & Entitlement System – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Membership-, Tier- und Feature-Limit-System (v9c) — keine Mitai-Domänenlogik, kein zentrales SSO/Stripe (Vision) + +**Serie:** Designprinzipien für Produktfamilie · Dokument 3 von n +**Vorgänger:** [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Entitlement-Auflösung | `backend/auth.py` (`get_effective_tier`, `check_feature_access`, `increment_feature_usage`) | +| Monitoring | `backend/feature_logger.py` | +| Nutzer-API | `backend/routers/subscription.py`, `backend/routers/features.py` | +| Admin | `routers/tiers_mgmt.py`, `tier_limits.py`, `coupons.py`, `access_grants.py`, `user_restrictions.py` | +| Widget-Gating | `backend/dashboard_widget_entitlements.py`, `widget_feature_requirements_db.py` | +| Frontend | `UsageBadge.jsx`, Feature-Usage in Seiten (z. B. `Analysis.jsx`, `WeightPage`) | +| Doku | `MEMBERSHIP_SYSTEM.md`, `FEATURE_ENFORCEMENT.md`, `CENTRAL_SUBSCRIPTION_SYSTEM.md` (Vision) | + +--- + +## Modul + +**Feature & Entitlement System** (Membership v9c) + +Zentrale Schicht für **„Darf dieser Nutzer diese Funktion wie oft nutzen?“** — unabhängig von Auth (Identität) und unabhängig von fachlicher Business-Logik in Routern. + +--- + +## Fachliche Verantwortung + +Das System übernimmt: + +1. **Feature-Registry** — Deklarative Liste aller limitierbaren Produktfunktionen mit Metadaten. +2. **Tier-Auflösung** — Effektiver Tarif eines Profils (Basis-Tier + zeitliche Grants). +3. **Limit-Auflösung** — Pro Feature: Override → Tier-Limit → Feature-Default. +4. **Usage-Tracking** — Zähler für Count-Features mit optionalem Reset (daily/monthly/never). +5. **Enforcement** — HTTP 403 bei Überschreitung; Frontend-Vorschaum via Badges. +6. **Beobachtbarkeit** — Strukturiertes JSON-Logging aller Access-Checks. +7. **Promotionen** — Coupons → Access Grants (temporäre Tier-Elevation, Pause/Resume). + +Es übernimmt **nicht**: + +- Login, Session, Passwort (Auth-Modul) +- Zahlungsabwicklung / Stripe (geplant, `CENTRAL_SUBSCRIPTION_SYSTEM.md`) +- Mandanten-Isolation (Org/Workspace) — Entitlements sind **profile-scoped** +- Inhaltliche Berechtigung pro Datensatz (nur Feature-Gates) + +### Zwei Entscheidungsebenen + +| Ebene | Frage | Funktion | +|-------|--------|----------| +| **Tier** | Welcher Tarif gilt? | `get_effective_tier()` | +| **Feature** | Darf Feature X genutzt werden (wie oft)? | `check_feature_access()` | + +Tier beeinflusst Feature-Limits über `tier_limits`; User-Overrides können Limits unabhängig vom Tier setzen. + +### Administrierte Konfigurationen + +| Konfiguration | Speicherort | Admin-UI | +|---------------|-------------|----------| +| Feature-Definitionen | `features` | Admin Features | +| Tier-Stufen | `tiers` | Admin Tiers | +| Tier × Feature Matrix | `tier_limits` | Admin Tier Limits | +| User-Overrides | `user_feature_restrictions` | Admin User Restrictions | +| Coupons | `coupons`, `coupon_redemptions` | Admin Coupons | +| Temporäre Tier-Grants | `access_grants` | (via Coupon/Admin) | +| Widget → Feature Mapping | `widget_feature_requirements`, Katalog | Admin Widget Features | +| Usage-Zähler | `user_feature_usage` | (automatisch) | + +**Nicht hardcodiert:** Limits pro Tier, Feature-Metadaten, Coupon-Parameter, User-Overrides. + +**Hardcodiert (Code):** Feature-IDs in Routern (`'ai_calls'`, `'weight_entries'`, …), Reset-Berechnung, 4-Phasen-Muster, 11 initial registrierte Features. + +### Auflösungs-Hierarchien + +**Effektiver Tier** (`get_effective_tier`): + +1. Aktiver `access_grants`-Eintrag (`is_active`, `valid_from`/`valid_until`) +2. Fallback: `profiles.tier` + +**Feature-Limit** (`check_feature_access` → `_check_impl`): + +1. `user_feature_restrictions.limit_value` (höchste Priorität) +2. `tier_limits` für effektiven Tier +3. `features.default_limit` + +**Limit-Semantik:** + +| `limit_type` | Bedeutung | +|--------------|-----------| +| `count` | Zählbares Kontingent; `used < limit` | +| `boolean` | An/Aus; `limit == 1` erlaubt, `0` gesperrt | + +| `limit_value` | Bedeutung | +|---------------|-----------| +| `NULL` | Unbegrenzt | +| `0` | Deaktiviert | +| `> 0` | Kontingent oder Boolean „an“ | + +### Rollen + +| Rolle | Darf | +|-------|------| +| **Admin** | Features/Tiers/Limits/Coupons/Restrictions CRUD; alle Nutzer-Overrides | +| **Nutzer** | Eigene Subscription/Usage lesen (`/subscription/me`, `/features/usage`); keine Limit-Änderung | + +Enforcement gilt für alle authentifizierten Nutzer gleich — Admins haben keine automatische Bypass-Logik in `check_feature_access`. + +### Versionierung, Freigabe, Test + +| Mechanismus | Status | +|-------------|--------| +| 4-Phasen-Rollout (Monitor → UI → Enforce) | ✅ dokumentiert & angewendet | +| JSON-Log `feature-usage.log` | ✅ Phase 2 Monitoring | +| DB-Migration v9c für Schema | ✅ | +| Automatisierte Enforcement-Tests pro Router | ⚠️ teilweise (Widgets getestet) | +| Zentrale Policy „jeder Endpoint muss checken“ | ❌ nicht erzwungen | + +--- + +## Designprinzipien + +### 1. Feature-Registry statt hardcodierter Limits + +| | | +|---|---| +| **Prinzip** | Jedes limitierbare Produkt-Feature ist Zeile in `features` — neue Features ohne Schema-Migration für Limits. | +| **Begründung** | Admin-UI, Usage-API und Backend-Checks teilen dieselbe ID und Metadaten. | +| **Quelle** | `MEMBERSHIP_SYSTEM.md` § Feature-Registry; `routers/features.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Feature-IDs müssen trotzdem in Router-Code referenziert werden. | + +### 2. Eine Auflösungsfunktion für Entitlements + +| | | +|---|---| +| **Prinzip** | Alle Backend- und Widget-Checks rufen `check_feature_access(profile_id, feature_id)` auf. | +| **Begründung** | Keine duplizierte Tier/Limit-Logik in Routern, Widgets oder Frontend. | +| **Quelle** | `auth.py`; `dashboard_widget_entitlements.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Endpoints nutzen es (z. B. `/prompts/execute` fehlt). | + +### 3. Getrennte Tier- und Feature-Auflösung + +| | | +|---|---| +| **Prinzip** | `get_effective_tier()` für Tarif; `check_feature_access()` für konkretes Feature — Tier ist Input, nicht Output der Feature-Prüfung. | +| **Begründung** | Temporäre Grants heben Tier an; User-Override kann einzelnes Feature unabhängig anpassen. | +| **Quelle** | `auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `get_effective_tier` im Code einfacher als in `MEMBERSHIP_SYSTEM.md` (kein `tier_locked`, Trial nicht in Tier-Funktion). | + +### 4. Prioritäts-Kette für Limits + +| | | +|---|---| +| **Prinzip** | User-Override > Tier-Limit > Feature-Default — explizit und dokumentiert. | +| **Begründung** | Support/Beta-Fälle ohne Tier-Wechsel; vorhersehbares Verhalten. | +| **Quelle** | `_check_impl()` in `auth.py`; `MEMBERSHIP_SYSTEM.md` § Zugriffs-Hierarchie | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `user_feature_restrictions.enabled` im Schema, aber nicht in `_check_impl` ausgewertet. | + +### 5. Count vs. Boolean als zwei Feature-Klassen + +| | | +|---|---| +| **Prinzip** | Zählbare Aktionen (`count` + Usage) vs. Schalter-Features (`boolean`, kein Counter). | +| **Begründung** | Pipeline-An/Aus vs. monatliche KI-Calls — unterschiedliche UX und Backend-Logik. | +| **Quelle** | `features.limit_type`; `FEATURE_ENFORCEMENT.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Boolean-Features nutzen `limit_value` 0/1 — leicht mit Count zu verwechseln. | + +### 6. Reset-Perioden für Count-Features + +| | | +|---|---| +| **Prinzip** | `reset_period`: `never` \| `daily` \| `monthly` — Counter-Reset in `check_feature_access` bei abgelaufenem `reset_at`. | +| **Begründung** | Monats-Kontingente vs. Lifetime-Limits in einem Modell. | +| **Quelle** | `auth.py` → `_calculate_next_reset()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Reset beim Check, nicht per Cron — Edge Cases bei seltenem Zugriff. | + +### 7. Usage nur bei neuen Entitäten incrementieren + +| | | +|---|---| +| **Prinzip** | `increment_feature_usage()` nur nach **INSERT**, nicht nach UPDATE/Upsert-Deduplikat. | +| **Begründung** | Limits messen „neue Nutzung“, nicht Bearbeitung bestehender Daten. | +| **Quelle** | `FEATURE_ENFORCEMENT.md` § Wichtige Regeln | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Bulk-Import muss explizit zählen; Fehler anfällig. | + +### 8. Vier-Phasen-Rollout (Observe before Enforce) + +| | | +|---|---| +| **Prinzip** | Phase 1 Cleanup → 2 Logging → 3 Frontend-Badges → 4 HTTP 403. | +| **Begründung** | Limits einführen ohne blind Nutzer zu blockieren; Daten für Limit-Kalibrierung. | +| **Quelle** | `FEATURE_ENFORCEMENT.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Disziplin pro Feature; kein zentraler Feature-Flag pro Endpoint-Phase. | + +### 9. Strukturiertes Feature-Logging + +| | | +|---|---| +| **Prinzip** | Jeder Check: `log_feature_usage(profile_id, feature_id, access, action)` → JSON in `feature-usage.log`. | +| **Begründung** | Audit, Debugging, Kalibrierung — auch wenn noch nicht enforced. | +| **Quelle** | `feature_logger.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Log-Pfad `/app/logs` container-spezifisch. | + +### 10. Defense in Depth: API 403 + Frontend-Gate + +| | | +|---|---| +| **Prinzip** | Backend blockiert autoritativ; Frontend zeigt `UsageBadge`, deaktiviert Buttons, Tooltip bei Limit. | +| **Begründung** | UX (frühes Feedback) + Sicherheit (API nicht umgehbar via curl). | +| **Quelle** | `FEATURE_ENFORCEMENT.md`; `UsageBadge.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Gate optional pro Seite; nicht generisch erzwungen. | + +### 11. Nutzer-Usage-API ohne Code-Änderung bei neuen Features + +| | | +|---|---| +| **Prinzip** | `GET /features/usage` iteriert alle aktiven `features` und ruft `check_feature_access` pro Zeile. | +| **Begründung** | Neues DB-Feature erscheint automatisch in Quota-Übersicht. | +| **Quelle** | `routers/features.py` → `get_feature_usage()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend muss Feature-ID kennen, um Badge zu binden. | + +### 12. Access Grants für temporäre Tier-Elevation + +| | | +|---|---| +| **Prinzip** | Coupons/Admin erzeugen `access_grants`; effektiver Tier steigt zeitlich begrenzt. | +| **Begründung** | Promotions, Partner (Wellpass), Trials ohne permanente Tier-Änderung. | +| **Quelle** | `access_grants`; `routers/coupons.py` (Pause/Resume) | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Coupon-Stacking-Logik komplex; dokumentiert vs. Code prüfen bei Neuentwicklung. | + +### 13. Entitlements als Querschnitt für UI-Module (Widgets) + +| | | +|---|---| +| **Prinzip** | Dashboard-Widgets mappen auf `features.id`; Katalog liefert `allowed` pro Profil. | +| **Begründung** | Tier-Logik nicht in React-Widgets duplizieren (`DASHBOARD_WIDGETS_AGENT_GUIDE` §0). | +| **Quelle** | `dashboard_widget_entitlements.py`; `ARCHITECTURE.md` §9 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Widget-Sichtbarkeit ≠ API-Schutz — Chart-Endpoints brauchen eigenes Gating (A4). | + +### 14. Admin-konfigurierbare Tier × Feature Matrix + +| | | +|---|---| +| **Prinzip** | `tier_limits` trennt Tier-Definition von Limits; Tiers ohne hardcodierte Spalten pro Feature. | +| **Begründung** | Neue Tiers/Preise ohne Code-Deploy der Limit-Logik. | +| **Quelle** | `MEMBERSHIP_SYSTEM.md` § Tier-System; `tier_limits` Tabelle | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Tier-Namen in Seed-Daten (`free`, `premium`, …) — erweiterbar, aber Konvention. | + +### 15. NULL = unlimited, 0 = disabled + +| | | +|---|---| +| **Prinzip** | Einheitliche Semantik für Limit-Werte in allen Schichten. | +| **Begründung** | Vermeidet Sonderfälle „-1 means unlimited“; klare Admin-UI. | +| **Quelle** | `_check_impl()` in `auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | SQL NULL vs. Python None — konsistent, aber in UI erklärungsbedürftig. | + +--- + +## Registrierte Features (Referenz) + +| Feature ID | Typ | Reset | Typische Aktion | +|------------|-----|-------|-----------------| +| `weight_entries` | count | never | Gewicht anlegen | +| `circumference_entries` | count | never | Umfang anlegen | +| `caliper_entries` | count | never | Caliper anlegen | +| `activity_entries` | count | monthly | Training anlegen/import | +| `nutrition_entries` | count | monthly | Ernährung anlegen/import | +| `photos` | count | monthly | Foto hochladen | +| `ai_calls` | count | monthly | KI-Analyse | +| `ai_pipeline` | boolean | — | Pipeline-Analyse | +| `data_export` | count | monthly | Export/PDF | +| `data_import` | count | monthly | ZIP/Universal-Import | + +**Enforcement-Lücken (Ist):** `routers/prompts.py` (`/execute`, `/execute-stream`) ohne `check_feature_access` — Legacy `insights.py` hat Enforcement für `ai_calls`/`ai_pipeline`. + +--- + +## Nicht übernehmen + +1. **Dokumentations-Drift** — `MEMBERSHIP_SYSTEM.md` („Enforcement deaktiviert“) vs. `FEATURE_ENFORCEMENT.md` (Phase 4 komplett) vs. Ist-Code; bei Neuentwicklung einen Kanon festlegen. + +2. **Unvollständige Tier-Auflösung** — Doku beschreibt `tier_locked`, Trial-in-Tier; Code nutzt primär Grants + `profiles.tier`. Trial (`trial_ends_at`) eher UI-Banner als Tier-Engine. + +3. **Feature-IDs in Routern verstreut** — kein zentraler Endpoint-Registry-Eintrag „welcher Router prüft welches Feature“. + +4. **Check und Increment nicht atomar** — Race bei parallelen Requests möglich; kein DB-Level Locking. + +5. **Legacy Profil-Spalten parallel** — `ai_enabled`, `ai_limit_day`, `export_enabled` in Sessions-Query neben Feature-System. + +6. **Frontend ohne Backend-Gate** — reine UI-Deaktivierung ohne 403 ist unsicher (manche Seiten nur teilweise umgesetzt). + +7. **Boolean via limit_value 0/1** — funktioniert, aber für Familien-Architektur explizites `enabled`-Flag oder Capability-Tokens erwägen. + +8. **Unused Schema-Felder** — `user_feature_restrictions.enabled` nicht in Auflösung eingebunden. + +9. **App-lokales Abo ohne Zahlungsanbindung** — Stripe/SSO nur Vision (`CENTRAL_SUBSCRIPTION_SYSTEM.md`); nicht als fertiges Familien-Muster übernehmen. + +10. **Profile als Entitlement-Subject** — kein Org/Mandant; Multi-App-Familie braucht separates Identity/Subscription-Boundary. + +11. **Self-hosted Tier als Sonderfall** — `selfhosted` ist Deploy-Modell, kein generisches SaaS-Tier-Muster. + +12. **Increment-Schleifen bei Bulk** — `for _ in range(new_entries): increment_feature_usage()` — ineffizient; batch-Inkrement besser. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/ +├── auth.py # get_effective_tier, check_feature_access, increment_feature_usage +├── feature_logger.py # JSON-Logging +├── dashboard_widget_entitlements.py # Widget allowed + Layout-Sanitisierung +├── widget_feature_requirements_db.py +└── routers/ + ├── subscription.py # /me, /usage, /limits (Nutzer) + ├── features.py # Admin CRUD + /usage, /check-access + ├── tiers_mgmt.py, tier_limits.py + ├── coupons.py, access_grants.py + └── user_restrictions.py + +frontend/src/components/ +└── UsageBadge.jsx # Quota-Anzeige (Phase 3) +``` + +**DB (v9c):** `features`, `tiers`, `tier_limits`, `user_feature_restrictions`, `user_feature_usage`, `coupons`, `coupon_redemptions`, `access_grants`, `user_activity_log` + +--- + +## Verwandte Dokumentation + +- Membership-Detail: [MEMBERSHIP_SYSTEM.md](../../technical/MEMBERSHIP_SYSTEM.md) +- Enforcement-Howto: [FEATURE_ENFORCEMENT.md](../../architecture/FEATURE_ENFORCEMENT.md) +- Vision Produktfamilie: [CENTRAL_SUBSCRIPTION_SYSTEM.md](../../technical/CENTRAL_SUBSCRIPTION_SYSTEM.md) +- Widget-Gating: [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) §0 +- Prompt Engine (Enforcement-Lücke): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1 | Prompt Engine | ✅ | +| 2 | Data Layer | ✅ | +| 3 | Feature & Entitlement | ✅ dieses Dokument | +| 4 | Registry-/Plugin-Muster | ✅ | +| 5 | Auth & Session | ✅ | +| 6 | Universal Import | ✅ | +| 7 | Dashboard Widgets | ✅ | +| 8 | Navigation / IA | ✅ | +| 9 | Migration & Deploy | ✅ `MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md` | diff --git a/docs/reference/design-principles/mitai/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..ce10396 --- /dev/null +++ b/docs/reference/design-principles/mitai/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md @@ -0,0 +1,369 @@ +# Migration & Deploy – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** DB-Migrationen, Container-Startup, CI/CD-Deploy — keine Anwendungsdomäne + +**Serie:** Designprinzipien für Produktfamilie · Dokument 9 von n (Abschluss) +**Vorgänger:** [NAVIGATION_IA_DESIGN_PRINCIPLES.md](./NAVIGATION_IA_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| DB-Init & Migrationen | `backend/db_init.py`, `backend/startup.sh` | +| Migrationen | `backend/migrations/XXX_*.sql` | +| Basis-Schema (Greenfield) | `backend/schema.sql` | +| Tracking | Tabelle `schema_migrations` | +| Compose Prod/Dev | `docker-compose.yml`, `docker-compose.dev-env.yml` | +| CI/CD | `.gitea/workflows/deploy-dev.yml`, `deploy-prod.yml`, `test.yml` | +| Versionierung | `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) | +| Doku (operativ) | `MIGRATIONS.md` | +| Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` §2, §7 | + +--- + +## Modul + +**Migration & Deploy** + +Automatische **PostgreSQL-Schema-Evolution** beim Container-Start plus **Git-getriebene Deploy-Pipeline** (develop → Dev, main → Prod) auf selbst-gehosteter Infrastruktur (Docker auf Raspberry Pi). + +--- + +## Fachliche Verantwortung + +Das Modul übernimmt: + +1. **Schema-Migrationen** — Nummerierte SQL-Dateien, idempotent wo möglich, getrackt in `schema_migrations`. +2. **Startup-Orchestrierung** — Postgres ready → Schema/Migrationen → optional SQLite-Import → Uvicorn. +3. **Umgebungstrennung** — Dev (`3099`/`8099`) vs. Prod (`3002`/`8002`), getrennte DBs/Volumes. +4. **Deploy-Automatisierung** — Push auf Branch → Runner → `git reset --hard` → `docker compose build --no-cache` → Health-Check. +5. **Post-Deploy-Tests** — Pytest/Lint/Frontend-Build gegen **deployed** Container auf dem Runner. +6. **Versions-Metadaten** — App-/Modul-Version und dokumentierte `DB_SCHEMA_VERSION`. + +Es übernimmt **nicht**: + +- Fachliche Datenberechnungen (→ Data Layer) +- Automatisches Downgrade/Rollback von Schema +- Blue-Green oder Multi-Region-Deploy + +### Deploy-Pipeline (Happy Path) + +``` +Entwickler: commit → push develop + → Gitea Runner: deploy-dev.yml + → cd /home/lars/docker/bodytrack-dev + → git fetch + reset --hard origin/develop + → docker compose -f docker-compose.dev-env.yml build --no-cache && up -d + → backend startup.sh → db_init.py (Migrationen) + → curl localhost:8099/api/auth/status + → test.yml (push + nach Deploy): pytest im Container, py_compile, npm run build + +Prod: PR develop → main → deploy-prod.yml (Port 8002, bodytrack/) +``` + +--- + +## Administrierte vs. code-definierte Konfiguration + +| Konfiguration | Speicherort | Wer pflegt? | +|---------------|-------------|-------------| +| Migration-SQL | `backend/migrations/` | Entwickler | +| Welche Migrationen angewendet | `schema_migrations` (DB) | Automatisch | +| Greenfield-Basis | `schema.sql` | Entwickler (selten) | +| Compose/Ports/Env | `docker-compose*.yml`, `.env` auf Server | Betrieb | +| Deploy-Workflow | `.gitea/workflows/*.yml` | Entwickler | +| App-Version / Changelog | `backend/version.py` | Entwickler (pro Release) | +| Prod-Geheimnisse | Server-`.env`, nicht im Repo | Betrieb | + +--- + +## Designprinzipien + +### 1. Migrationen beim Container-Start ( nicht manuell in Prod) + +| | | +|---|---| +| **Prinzip** | `startup.sh` ruft `db_init.py` auf **bevor** Uvicorn startet; pending Migrationen werden automatisch angewendet. | +| **Begründung** | Kein vergessenes Schema-Update; Deploy und DB-Stand bleiben gekoppelt. | +| **Quelle** | `startup.sh`, `db_init.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Fehlgeschlagene Migration blockiert API-Start (`sys.exit(1)`). | + +### 2. Nummeriertes Datei-Pattern als Gate + +| | | +|---|---| +| **Prinzip** | Nur `\d{3}_*.sql` wird ausgeführt (z. B. `054_activity_session_metrics_eav.sql`); alles andere wird ignoriert. | +| **Begründung** | Sortierbare Reihenfolge; Ad-hoc-Skripte (`check_features.sql`, `v9c_*.sql`) verunreinigen nicht den Lauf. | +| **Quelle** | `run_migrations()` Regex | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Dateien ohne Nummer liegen noch im Ordner (historischer Ballast). | + +### 3. Tracking-Tabelle als Single Source of „applied“ + +| | | +|---|---| +| **Prinzip** | `schema_migrations(filename)` — jede erfolgreiche Datei genau einmal eingetragen; Pending = Dateien minus Applied. | +| **Begründung** | Idempotenter Startup; wiederholter Container-Start wendet nichts doppelt an. | +| **Quelle** | `ensure_migration_table`, `apply_migration` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein checksum — geänderte Datei nach Apply wird nicht erneut ausgeführt. | + +### 4. Alphabetische Reihenfolge = Migrations-Reihenfolge + +| | | +|---|---| +| **Prinzip** | `sorted(glob)` — dreistellige Präfixe (`001`, `054`, `061`) definieren die Apply-Order. | +| **Begründung** | Einfach, git-freundlich, keine separate Migrations-Registry. | +| **Quelle** | `run_migrations()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nummern-Kollisionen oder nachträgliches Einfügen erfordern Disziplin (immer nächste freie Nummer). | + +### 5. Fail-Fast bei Migrationsfehler + +| | | +|---|---| +| **Prinzip** | Schlägt eine Migration fehl → kein Commit in Tracking (bei Exception vor INSERT), Prozess exit 1, Container unhealthy. | +| **Begründung** | API läuft nicht mit halb angewendetem Schema. | +| **Quelle** | `apply_migration`, `main` in `db_init.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Manueller Recovery-Prozess nötig (siehe MIGRATIONS.md Rollback). | + +### 6. Greenfield: schema.sql, Bestand: nur Migrationen + +| | | +|---|---| +| **Prinzip** | Existiert `profiles` nicht → einmalig `schema.sql` laden; danach nur noch nummerierte Migrationen. | +| **Begründung** | Frische Instanz schnell bootstrapped; langlebige DBs evolvieren incremental. | +| **Quelle** | `check_table_exists`, `load_schema` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `schema.sql` kann hinter Migrationen zurückfallen wenn nicht gepflegt. | + +### 7. Idempotente DDL bevorzugen + +| | | +|---|---| +| **Prinzip** | `ADD COLUMN IF NOT EXISTS`, `CREATE INDEX IF NOT EXISTS`, defensive UPDATEs — Migration soll mehrfach ausführbar sein ohne Schaden. | +| **Begründung** | Recovery nach partiellem Apply; manuelles Re-Run sicherer. | +| **Quelle** | `MIGRATIONS.md` Best Practices | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Änderungen sind idempotent (DROP, irreversible Datenmigration). | + +### 8. Kein psql-Meta in Migrationsdateien + +| | | +|---|---| +| **Prinzip** | Nur SQL — kein `\echo`, `\i`, `\connect`; Ausführung via psycopg2, nicht interaktiv. | +| **Begründung** | Parser/Runner versteht nur SQL-Statements. | +| **Quelle** | `MIGRATIONS.md`, `apply_migration` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 9. Schema-Änderung = nummerierte Migration (nie ad-hoc in Prod) + +| | | +|---|---| +| **Prinzip** | Neue Tabellen/Spalten **nur** via `backend/migrations/XXX_*.sql`; nicht direkt in laufender Prod-DB editieren. | +| **Begründung** | Reproduzierbarkeit Dev→Prod; Review im Git-Diff. | +| **Quelle** | ARCHITECTURE.md, CLAUDE.md | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Agent-Regel — technisch nicht erzwungen. | + +### 10. DB_SCHEMA_VERSION als dokumentierter Marker + +| | | +|---|---| +| **Prinzip** | `backend/version.py` → `DB_SCHEMA_VERSION` bei Schema-Änderung manuell bumpen (Format z. B. `YYYYMMDD` + Suffix). | +| **Begründung** | API `/api/version` und Changelog zeigen Schema-Stand unabhängig von App-Minor. | +| **Quelle** | ARCHITECTURE.md §2.6 | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Nicht automatisch aus `schema_migrations` abgeleitet — Drift möglich. | + +### 11. Branch → Umgebung (develop / main) + +| | | +|---|---| +| **Prinzip** | `develop` → Dev-Deploy automatisch; `main` → Prod-Deploy automatisch; Prod nur nach expliziter Freigabe/Merge. | +| **Begründung** | Klare Promotion; Dev als Integrationsumgebung. | +| **Quelle** | Workflows, CLAUDE.md Deployment | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein Staging-Branch zwischen Dev und Prod. | + +### 12. Deploy-Arbeitskopie = exakt Remote-Branch + +| | | +|---|---| +| **Prinzip** | Runner: `git fetch` + `git reset --hard origin/` — keine `pull`-Merge-Konflikte, kein schmutziger `package-lock` auf dem Pi. | +| **Begründung** | Reproduzierbarer Deploy-Baum; Fix aus GUI-IA-Abnahme 2026-04-05. | +| **Quelle** | `deploy-prod.yml`, `deploy-dev.yml` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Lokale Hotfixes auf dem Server werden beim Deploy überschrieben. | + +### 13. Immutabler Build pro Deploy (`--no-cache`) + +| | | +|---|---| +| **Prinzip** | `docker compose build --no-cache` bei jedem Deploy — frisches Image aus Dockerfile + Repo-Stand. | +| **Begründung** | Keine veralteten Layer; Migrationen und Code garantiert im Image. | +| **Quelle** | Deploy-Workflows | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Langsamere Deploys; kein Registry-basiertes Image-Promotion. | + +### 14. Health-Check nach Deploy + +| | | +|---|---| +| **Prinzip** | Nach `up -d`: kurz warten, dann `curl -sf …/api/auth/status` (8099 Dev / 8002 Prod). | +| **Begründung** | Minimale Smoke-Verification dass API antwortet (inkl. DB-Init durchlaufen). | +| **Quelle** | Deploy-Workflows | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Prüft nicht fachliche Endpoints oder Migration-Inhalt. | + +### 15. Persistente Volumes für Daten und Fotos + +| | | +|---|---| +| **Prinzip** | Postgres-Daten, `/app/data`, `/app/photos` in benannten/external Volumes — überleben Container-Rebuild. | +| **Begründung** | Deploy = neues Image, nicht Datenverlust. | +| **Quelle** | `docker-compose*.yml` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Volume-Backup/Restore ist Betriebsaufgabe außerhalb Repo. | + +### 16. Postgres Healthcheck vor Backend-Start + +| | | +|---|---| +| **Prinzip** | `depends_on: condition: service_healthy` — Backend startet erst wenn DB `pg_isready`. | +| **Begründung** | `wait_for_postgres` in db_init ist zweite Absicherung; reduziert Race beim ersten Start. | +| **Quelle** | Compose-Files | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 17. Tests gegen deployed Stack (Self-Hosted Runner) + +| | | +|---|---| +| **Prinzip** | `test.yml` führt pytest **im laufenden Backend-Container** auf dem Pi aus, nicht in isolierter GitHub-Cloud. | +| **Begründung** | Tests laufen gegen echte Dev/Prod-Compose-Umgebung des Projekts. | +| **Quelle** | `.gitea/workflows/test.yml` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Prod-Deploy triggert Tests auf Prod-Pfad — Risiko wenn Tests schreibend; `-m 'not slow'` begrenzt Laufzeit. | + +### 18. Feste Ports pro Umgebung + +| | | +|---|---| +| **Prinzip** | Dev `3099/8099`, Prod `3002/8002` — nicht ändern (Reverse Proxy/Fritz!Box hängen daran). | +| **Begründung** | Externe URLs (`dev.mitai.jinkendo.de`, `mitai.jinkendo.de`) stabil. | +| **Quelle** | CLAUDE.md, Compose | +| **Tragfähigkeit** | **hoch** (betriebsspezifisch) | +| **Einschränkung** | Andere Projekte brauchen eigene Port-Matrix. | + +### 19. Prod-Schutz: Deploy nur über Git + +| | | +|---|---| +| **Prinzip** | Keine direkten Prod-Container-/DB-Schreibzugriffe für Automation; Prod-Änderung = Merge `main` → Workflow. | +| **Begründung** | Audit-Trail, Review, keine Drift. | +| **Quelle** | ARCHITECTURE.md §7.1, `/deploy` Command | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Menschlicher SSH-Zugriff bleibt möglich — Prozess, nicht Technik. | + +### 20. Versions-Bump als Release-Disziplin + +| | | +|---|---| +| **Prinzip** | Jede lieferbare Änderung: `APP_VERSION`, betroffene `MODULE_VERSIONS`, `CHANGELOG` in `version.py`; bei Schema auch `DB_SCHEMA_VERSION`. | +| **Begründung** | `/api/version`, Support, Korrelation Deploy ↔ Code. | +| **Quelle** | ARCHITECTURE.md §2.5, `deploy.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-`version.js` in Spec erwähnt, im Repo teils nicht vorhanden — Dual-Bump unvollständig. | + +--- + +## Nicht übernehmen + +1. **Unnummerierte Migrationsdateien** — `v9c_*.sql`, `check_*.sql` werden nicht auto-applied; nicht als Vorbild. + +2. **Migration-Datei nach Apply ändern** — Tracking verhindert Re-Run; neue Nummer statt Edit. + +3. **Automatischer Downgrade** — nicht implementiert; Rollback manuell + Tracking-Eintrag löschen. + +4. **Direktes Schema in Prod** — immer Git-Migration + Deploy. + +5. **Breaking DROP ohne Koordination** — App-Code und Migration in einem Release. + +6. **psql-Metacommands in `.sql`** — bricht Python-Runner. + +7. **`git pull` auf Deploy-Server** — Merge-Schmutz; `reset --hard` ist das Muster. + +8. **Prod-Deploy ohne Dev-Validierung** — develop-First ist implizite Policy. + +9. **Schema-Drift ohne `DB_SCHEMA_VERSION`-Bump** — dokumentarische Lücke. + +10. **Cached Docker-Build als Default** — Mitai wählt Reproduzierbarkeit über Geschwindigkeit. + +11. **Migrationen außerhalb Container-Start vergessen** — manuelles psql in Prod als Normalfall. + +12. **Hardcoded Seed-Daten in Migration** — produktive User/Secrets nicht in SQL. + +13. **Port-Änderung „nebenbei“** — Infrastruktur-Kopplung. + +14. **Tests nur lokal, nie auf Runner-Stack** — Mitai testet bewusst post-deploy im Pi-Container (Trade-off verstehen). + +15. **Transaktionssteuerung in SQL-Datei** — Runner committet pro Datei; komplexe multi-step Rollbacks nicht eingebaut. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/ +├── db_init.py # wait, schema, run_migrations, sqlite import +├── startup.sh # db_init → uvicorn +├── schema.sql # Greenfield +├── migrations/ # 001–061+ nummeriert (+ Legacy ohne Nummer) +└── version.py # APP_VERSION, DB_SCHEMA_VERSION, MODULE_VERSIONS + +docker-compose.yml # Prod: 3002/8002 +docker-compose.dev-env.yml # Dev: 3099/8099 + +.gitea/workflows/ +├── deploy-dev.yml # push develop +├── deploy-prod.yml # push main +└── test.yml # pytest, lint, npm build on Pi + +Server (Pi): +/home/lars/docker/bodytrack-dev/ # develop +/home/lars/docker/bodytrack/ # main +``` + +**Migrationen (Stand):** 60+ nummerierte Dateien (`001` … `061`); höchste Nummer im Repo prüfen vor neuer Migration. + +--- + +## Verwandte Dokumentation + +- Operativ: [MIGRATIONS.md](../../technical/MIGRATIONS.md) +- Architektur: `.claude/rules/ARCHITECTURE.md` §2 (Versionierung), §7 (Prod-Schutz) +- Deploy-Command: `.claude/commands/deploy.md`, `merge-to-prod.md` +- Import/Migration-Grenze: [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](./UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) +- Auth auf Prod: [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) + +--- + +## Serie – Übersicht (abgeschlossen) + +| # | Modul | Status | +|---|-------|--------| +| 1 | Prompt Engine | ✅ `PROMPT_ENGINE_DESIGN_PRINCIPLES.md` | +| 2 | Data Layer | ✅ `DATA_LAYER_DESIGN_PRINCIPLES.md` | +| 3 | Feature & Entitlement | ✅ `FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md` | +| 4 | Registry / Plugin (Meta) | ✅ `REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md` | +| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` | +| 6 | Universal Import | ✅ `UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md` | +| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` | +| 8 | Navigation / IA | ✅ `NAVIGATION_IA_DESIGN_PRINCIPLES.md` | +| 9 | Migration & Deploy | ✅ dieses Dokument | diff --git a/docs/reference/design-principles/mitai/NAVIGATION_IA_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/NAVIGATION_IA_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..6771a52 --- /dev/null +++ b/docs/reference/design-principles/mitai/NAVIGATION_IA_DESIGN_PRINCIPLES.md @@ -0,0 +1,346 @@ +# Navigation & Informationsarchitektur – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** App-Navigation, Bereichs-Shells, Admin-IA, Responsive Shell — keine Seiteninhalte oder Domänenlogik + +**Serie:** Designprinzipien für Produktfamilie · Dokument 8 von n +**Vorgänger:** [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Hauptnavigation | `frontend/src/config/appNav.js` | +| Erfassung | `frontend/src/config/captureNav.js`, `layouts/CaptureShell.jsx` | +| Einstellungen | `frontend/src/config/settingsNav.js`, `layouts/SettingsShell.jsx` | +| Admin | `frontend/src/config/adminNav.js`, `layouts/AdminShell.jsx`, `RequireAdmin.jsx` | +| KI-Analyse (Kategorien) | `frontend/src/config/analysisCategories.js`, `pages/Analysis.jsx` | +| Routing | `frontend/src/App.jsx` | +| Desktop-Sidebar | `frontend/src/components/DesktopSidebar.jsx` | +| Responsive CSS | `frontend/src/app.css` (`--nav-h`, `.bottom-nav`, `.analysis-split`, `.desktop-sidebar`) | +| Abnahme-Doku | `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` | +| Responsive-Spec | `.claude/docs/functional/RESPONSIVE_UI.md` | + +--- + +## Modul + +**Navigation & Informationsarchitektur (IA)** + +Schichtenmodell für die PWA: **eine primäre Hauptnavigation** (6–7 Bereiche), darunter **Bereichs-Shells** mit eigener Sub-Navigation, getrennte **Admin-Realm**, **Auth-Gates** und **ein Breakpoint** für Mobile vs. Desktop. + +--- + +## Fachliche Verantwortung + +Das Modul übernimmt: + +1. **Hauptnav-SSoT** — Reihenfolge, Labels, Icons, Admin-Sichtbarkeit (`getMainNavItems`). +2. **Routing-Struktur** — Welche URL gehört zu welchem Bereich (Übersicht, Erfassen, Verlauf, Ziele, Analyse, Einstellungen, Admin). +3. **Sub-Navigation pro Bereich** — Capture-Hub, Settings-Tabs, Admin-Gruppen, Analyse-Kategorien. +4. **Layout-Muster** — Bottom-Nav (mobil), Sidebar (Desktop), `analysis-split` für tiefe Bereiche. +5. **Zugriffskontrolle (UI)** — `RequireAdmin`, Admin-Link nur bei `role === 'admin'`. +6. **Active-State** — Nested Routes (Erfassung unter `/capture`, Admin unter `/admin/*`). +7. **PWA-Tauglichkeit** — Safe Area, Scrollbare Bottom-Nav, Content-Padding. + +Es übernimmt **nicht**: + +- Backend-Autorisierung (→ [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md)) +- Feature-Entitlements in der Nav (Tier-Gates an Endpoints/Widgets, nicht an jedem NavLink) +- Inhaltliche Tab-Logik innerhalb von Verlauf/Analyse (Seiten concern) + +### IA-Modell (Nutzerperspektive) + +| Ebene | Mental Model | Beispiel-Routen | +|-------|--------------|-----------------| +| **Primär** | Wo bin ich in der App? | `/`, `/capture`, `/history`, `/goals`, `/analysis`, `/settings` | +| **Sekundär (Shell)** | Was mache ich in diesem Bereich? | `/weight`, `/admin/g/features`, `/settings/dashboard-layout` | +| **Tertiär (Seite)** | Tabs/Filter innerhalb einer Maske | Verlauf-Tabs, Analyse-Kategorien | + +### Strategisch vs. taktisch (Ziele) + +| Ebene | Ort | Zweck | +|-------|-----|-------| +| **Strategisch** | `/goals` (Hauptnav) | Ziele definieren, Prioritäten, Focus Areas | +| **Taktisch** | `/custom-goals` (Erfassung) | Tägliche Ist-Werte für eigene Ziele | +| **Auswertung** | `/history` | Trends, Charts, Vergleiche | + +--- + +## Administrierte vs. code-definierte Konfiguration + +| Konfiguration | Speicherort | Wer pflegt? | +|---------------|-------------|-------------| +| Hauptnav-Reihenfolge & Labels | `appNav.js` | Entwickler | +| Erfassungs-Kacheln & Shell-Nav | `captureNav.js` | Entwickler | +| Admin-Gruppen & Hub-Karten | `adminNav.js` | Entwickler | +| Settings-Subnav | `settingsNav.js` | Entwickler | +| Analyse-Kategorie-Reihenfolge | `analysisCategories.js` | Entwickler | +| KI-Prompt-Kategorien (Runtime) | DB `ai_prompts.category` | Admin (Prompts) | +| React-Routes | `App.jsx` | Entwickler (muss zu Nav-Configs passen) | + +--- + +## Designprinzipien + +### 1. Eine Quelle für die Hauptnavigation + +| | | +|---|---| +| **Prinzip** | `getMainNavItems(isAdmin)` liefert dieselbe Item-Liste für **Bottom-Nav** und **Desktop-Sidebar** — keine parallelen Hardcodings. | +| **Begründung** | Reihenfolge und Labels bleiben synchron; Admin-Conditional an einer Stelle. | +| **Quelle** | `appNav.js`, `App.jsx`, `DesktopSidebar.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Active-State-Logik ist in zwei Dateien dupliziert (`navItemActive` / `sidebarLinkActive`). | + +### 2. Feste primäre IA-Reihenfolge (Produkt-Story) + +| | | +|---|---| +| **Prinzip** | Übersicht → Erfassen → Verlauf → **Ziele** → Analyse → Einstellungen → [Admin] — spiegelt Nutzerfluss: sehen → eingeben → auswerten → steuern → interpretieren → konfigurieren. | +| **Begründung** | Ziele als eigener Hauptpunkt (nicht unter Analyse versteckt); klare Trennung Capture vs. History vs. Analysis. | +| **Quelle** | `GUI_IA_ADMIN_NAV_2026-04-05.md`; `appNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Produkt-spezifisch; andere Apps können andere Reihenfolge brauchen. | + +### 3. Config-Dateien pro Bereich (Nav-as-Data) + +| | | +|---|---| +| **Prinzip** | Sub-Navigation lebt in dedizierten Config-Modulen (`captureNav`, `adminNav`, `settingsNav`, `analysisCategories`) — Shell-Komponenten iterieren nur. | +| **Begründung** | Neue Erfassungsmaske = Eintrag in Config + Route; kein Nav-HTML in jeder Page. | +| **Quelle** | `captureNav.js` Kommentar „Pfade müssen mit Routes übereinstimmen“ | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein Build-Time-Check Config ↔ Routes. | + +### 4. Bereichs-Shells für tiefe Navigation + +| | | +|---|---| +| **Prinzip** | Capture, Settings und Admin nutzen **Shell-Layouts** mit ``; Nutzer wechselt Sub-Bereiche ohne Hauptnav zu verlassen. | +| **Begründung** | Erfassung hat 12+ Masken — wären als Hauptnav-Einträge unbrauchbar. | +| **Quelle** | `CaptureShell`, `SettingsShell`, `AdminShell` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Verlauf und Analyse haben eigene Tab-Muster (kein gemeinsames Shell-Config). | + +### 5. Wiederverwendbares `analysis-split`-Layout + +| | | +|---|---| +| **Prinzip** | Admin, Settings und KI-Analyse teilen CSS-Muster: mobil horizontale Chips, Desktop linke Spalte + `__main` für Inhalt. | +| **Begründung** | Ein visuelles Muster für „Kategorie links, Arbeit rechts“; weniger UI-Drift. | +| **Quelle** | `AdminShell.jsx`, `SettingsShell.jsx`, `Analysis.jsx`, `app.css` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Capture nutzt eigenes `capture-shell` (Emoji-Icons, Hub-Kacheln). | + +### 6. Admin: Gruppen in der Shell, Seiten über Hub + +| | | +|---|---| +| **Prinzip** | Shell-Nav zeigt nur **Admin-Gruppen** (+ Übersicht); konkrete Seiten als Karten auf `/admin/g/:groupId`. | +| **Begründung** | Skaliert bei wachsender Admin-Oberfläche; keine 20er-Sidebar. | +| **Quelle** | `adminNav.js` (`ADMIN_GROUPS`, `getAdminShellNavEntries`) | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Ein Klick mehr als flache Nav; bewusster Trade-off. | + +### 7. Admin als eigener Realm + +| | | +|---|---| +| **Prinzip** | `/admin/*` hinter `RequireAdmin`; kein Admin-Block mehr in Einstellungen; Profil-Anlage nur Admin → Benutzerverwaltung. | +| **Begründung** | Trennung Nutzer- vs. Betreiber-Kontext; weniger Verwechslung. | +| **Quelle** | `RequireAdmin.jsx`, `GUI_IA_ADMIN_NAV_2026-04-05.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI-Guard ersetzt nicht Backend-`require_admin` auf APIs. | + +### 8. Route-Guard mit Nutzer-Feedback + +| | | +|---|---| +| **Prinzip** | Nicht-Admin auf `/admin` → Redirect `/` mit `state.adminDenied`; Dashboard zeigt Hinweis. | +| **Begründung** | Stilles Scheitern vermeiden; klare Erwartung. | +| **Quelle** | `RequireAdmin.jsx`, `Dashboard.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 9. Nested Active-State für Section-Prefixes + +| | | +|---|---| +| **Prinzip** | Custom Active-Logik: `/capture` aktiv bei allen Erfassungs-Pfaden; `/admin` bei gesamten Admin-Baum; `/goals` mit `end: true` (exakt). | +| **Begründung** | React-Router `end` allein reicht für Section-Gruppen nicht. | +| **Quelle** | `navItemActive`, `adminShellEntryIsActive` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Neue Section-Prefixes brauchen explizite Regel. | + +### 10. Erfassungs-Hub + direkte Deep-Links + +| | | +|---|---| +| **Prinzip** | `/capture` = Kachel-Hub; jede Maske auch direkt erreichbar (`/weight`, …); Shell-Nav immer sichtbar. | +| **Begründung** | Onboarding über Hub; Power-User/Dashboard-Links springen direkt. | +| **Quelle** | `CaptureHub`, `CAPTURE_HUB_TILES` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Hub und Shell-Nav listen dieselben Ziele (Doppelpflege). | + +### 11. Einstellungen: nur aktives Profil + +| | | +|---|---| +| **Prinzip** | Settings = Self-Service für **aktives** Profil (Name, E-Mail, Avatar, Quality-Filter); keine Profil-Liste für Endnutzer. | +| **Begründung** | Multi-Profil-Verwaltung ist Admin-Aufgabe; reduziert Komplexität. | +| **Quelle** | `SettingsPage.jsx`, IA-Doku | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Session-bound Profile-Id-Schwäche bleibt Backend-Thema. | + +### 12. Settings-Subnav für Layout & Export + +| | | +|---|---| +| **Prinzip** | Konfiguration schwerer Features (Dashboard-Layout, PDF-Berichte, Referenzwerte) als eigene Settings-Routen unter Shell — nicht in „Allgemein“ verstecken. | +| **Begründung** | Entspricht [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) (Nutzer-Konfigurator). | +| **Quelle** | `settingsNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Admin-Dashboard-Default liegt unter `/admin/...` (getrennte Rolle). | + +### 13. KI-Analyse: Ergebnis im Hauptspalt + +| | | +|---|---| +| **Prinzip** | Neue Analyse-Ergebnisse rendern in `analysis-split__main`, nicht in der Kategorie-Nav — Nav bleibt wählbar. | +| **Begründung** | Lange Ergebnisse verdrängen sonst die Prompt-Auswahl (Mobile). | +| **Quelle** | `GUI_IA_ADMIN_NAV_2026-04-05.md`, `Analysis.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 14. Ein Breakpoint Mobile / Desktop (1024px) + +| | | +|---|---| +| **Prinzip** | `< 1024px`: Bottom-Nav + Mobile-Header; `≥ 1024px`: Desktop-Sidebar, Bottom-Nav ausgeblendet, breiterer Content. **Kein** separates Tablet-Layout. | +| **Begründung** | Einfache Spec, PWA-first; iPad im Portrait = Mobile-Verhalten. | +| **Quelle** | `RESPONSIVE_UI.md`, `app.css` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Phones und kleine Tablets identisch behandelt. | + +### 15. PWA Safe Area für Bottom-Navigation + +| | | +|---|---| +| **Prinzip** | `--nav-h`, `--nav-pad-top`, `env(safe-area-inset-bottom)` auf `.bottom-nav`; Content-`padding-bottom` inkl. Nav-Höhe; horizontal scrollbare Nav bei vielen Items. | +| **Begründung** | iPhone Home-Indicator und Notch — kein Clipping, kein verdeckter Content. | +| **Quelle** | `app.css`, IA-Doku | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Safe Area nur auf Nav/Content-Padding, nicht global überall. | + +### 16. Auth-Routen außerhalb der App-Shell + +| | | +|---|---| +| **Prinzip** | Login, Register, Verify, Reset-Password rendern **ohne** Bottom-Nav/Sidebar — minimale Vollbild-Cards. | +| **Begründung** | Keine Navigation ohne Session; klarer Fokus. | +| **Quelle** | `App.jsx` (early returns vor `AppShell`) | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Public Routes nicht zentral in einer Route-Config. | + +### 17. Rollen-sichtbare Nav-Einträge (UI only) + +| | | +|---|---| +| **Prinzip** | Admin-Link erscheint nur wenn `isAdmin`; Backend schützt APIs separat. | +| **Begründung** | Progressive disclosure; normale Nutzer sehen keinen toten Link. | +| **Quelle** | `getMainNavItems(isAdmin)` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Security nicht durch Ausblenden ersetzt. | + +### 18. Deep-Link-State für Verlauf + +| | | +|---|---| +| **Prinzip** | Nav zu `/history` setzt optional `state: { tab: 'overview' }` — konsistenter Einstieg von Hauptnav. | +| **Begründung** | Verlauf merkt sich Tabs; Hauptnav soll nicht zufälligen alten Tab öffnen. | +| **Quelle** | `App.jsx`, `DesktopSidebar.jsx`, `History.jsx` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nur für History implementiert, nicht app-weit. | + +--- + +## Nicht übernehmen + +1. **Hauptnav an mehreren Stellen hardcoden** — immer `appNav.js`. + +2. **Admin-Funktionen in Einstellungen** — eigener `/admin`-Bereich. + +3. **Alle Erfassungsmasken in die Bottom-Nav** — Shell + Hub skaliert. + +4. **Alle Admin-Seiten in der Shell-Sidebar** — Hub-Gruppen-Muster beibehalten. + +5. **Nav-Config ohne Route-Pflege** — jeder neue Pfad: Config + `App.jsx` + ggf. Active-State. + +6. **UI-Admin-Guard ohne Backend-Guard** — `RequireAdmin` ist UX, APIs brauchen `require_admin`. + +7. **Zwei Tablet-/Desktop-Breakpoints** — Mitai: ein Cut bei 1024px. + +8. **Safe Area ignorieren** — PWA auf iOS bricht sonst an Bottom-Nav. + +9. **Profil-Liste für Endnutzer in Settings** — Multi-Profil = Admin. + +10. **Feature-Tier-Logik in Nav-Komponenten** — Entitlements an Widgets/APIs ([FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md)). + +11. **Inkonsistente Layout-Muster pro Bereich** — wo `analysis-split` passt, nicht neues Ad-hoc-Layout erfinden. + +12. **Orphan-Routes ohne Nav-Ergänzung** — z. B. `/subscription`, `/workflow-editor/:id` existieren außerhalb Haupt-IA; bewusst dokumentieren, nicht unkontrolliert multiply. + +13. **Active-State nur per Router-Default** — Section-Prefixes (`/capture/*`, `/admin/*`) brauchen explizite Regeln. + +14. **Analyse-Ergebnisse in der Nav-Spalte** — verdrängt Prompt-Auswahl auf Mobile. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +frontend/src/config/ +├── appNav.js # Hauptnav (6 + Admin) +├── captureNav.js # Erfassungs-Hub + Shell +├── settingsNav.js # Settings-Subnav +├── adminNav.js # ADMIN_GROUPS, Shell-Entries +└── analysisCategories.js # KI-Analyse-Gruppen + +frontend/src/layouts/ +├── CaptureShell.jsx +├── SettingsShell.jsx +├── AdminShell.jsx +└── RequireAdmin.jsx + +frontend/src/components/ +└── DesktopSidebar.jsx + +frontend/src/App.jsx # Routes + Bottom-Nav + Auth-Gates +frontend/src/app.css # Shell, split, safe-area, 1024px breakpoint +``` + +**Hauptnav (7 Einträge mit Admin):** Übersicht · Erfassen · Verlauf · Ziele · Analyse · Einstellungen · Admin + +**Admin-Gruppen (8):** users · features · subscription · training · goals · prompts · system + +--- + +## Verwandte Dokumentation + +- Abnahme-Stand: [GUI_IA_ADMIN_NAV_2026-04-05.md](../../../../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md) +- Responsive-Spec: [RESPONSIVE_UI.md](../../functional/RESPONSIVE_UI.md) +- Auth/Session: [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) +- Dashboard-Konfigurator: [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](./DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) +- Gitea #30 (Responsive UI, teilweise erledigt) + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1–7 | … | ✅ | +| 8 | Navigation / IA | ✅ dieses Dokument | +| 9 | Migration & Deploy | ✅ | diff --git a/docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..7ce5360 --- /dev/null +++ b/docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md @@ -0,0 +1,305 @@ +# Prompt Engine – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Prompt Engine“ (Unified Prompt System, Issue #28) — keine Mitai-Gesamtarchitektur, keine Domänenlogik (Gesundheit, Ernährung, Messwerte) + +**Serie:** Designprinzipien für Produktfamilie · Dokument 1 von n + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Executor | `backend/prompt_executor.py`, `backend/workflow_executor.py` | +| Platzhalter | `backend/placeholder_resolver.py`, `backend/placeholder_registry.py`, `backend/placeholder_registrations/` | +| API | `backend/routers/prompts.py`, `backend/routers/workflows.py` | +| Admin-UI | `frontend/src/pages/AdminPromptsPage.jsx`, `UnifiedPromptModal.jsx`, `WorkflowEditorPage.jsx` | +| Fachliche Spec | `.claude/docs/functional/AI_PROMPTS.md` | +| Platzhalter-Governance | `.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`, `docs/PLACEHOLDER_GOVERNANCE.md` | + +--- + +## Modul + +**Prompt Engine** (Unified Prompt System, Issue #28) + +Backend-Kern: `prompt_executor.py`, `placeholder_resolver.py`, `placeholder_registry` / `placeholder_registrations/`, `workflow_executor.py` +API: `routers/prompts.py` +Admin-UI: `AdminPromptsPage`, `UnifiedPromptModal`, `WorkflowEditorPage` + +--- + +## Fachliche Verantwortung + +Die Prompt Engine ist die **zentrale Ausführungs- und Konfigurationsschicht für KI-Analysen**. Sie übernimmt: + +1. **Prompt-Orchestrierung** — Laden aktiver Prompt-Definitionen aus der DB und Ausführung als `base` (Einzelprompt), `pipeline` (mehrstufig) oder `workflow` (Graph). +2. **Kontextaufbereitung** — Befüllen von Platzhaltern mit profilbezogenen Daten (Resolver → Data Layer). +3. **LLM-Aufruf** — Einheitlicher OpenRouter-Call über injizierte Callback-Funktion. +4. **Ergebnisbehandlung** — JSON-Validierung, strukturierte Container (Fragenergänzungen), Debug-Metadaten, optionales Speichern in `ai_insights`. +5. **Admin-Konfiguration** — CRUD für Prompts/Workflows, Import/Export, Vorschau und Test ohne Produktions-Ausführung. + +### Administrierte Konfigurationen + +| Konfiguration | Speicherort | Inhalt | +|---------------|-------------|--------| +| Prompt-Metadaten | `ai_prompts` | `name`, `slug`, `category`, `active`, `sort_order`, `display_name` | +| Templates | `ai_prompts` | `template` | +| Pipeline-Stages | `ai_prompts.stages` (JSONB) | Stages mit `inline` / `reference` | +| Workflow-Graphen | `ai_prompts.graph_data` | Knoten, Kanten, Metadaten | +| Output-Regeln | `ai_prompts` | `output_format`, `output_schema` | +| Fragenergänzungen | `ai_prompts.question_augmentations` | Optionale Standard-Fragen (Hybridmodell: Knoten > Prompt) | +| System-Reset | `ai_prompts` | `is_system_default`, `default_template` | +| Legacy-Pipeline-Configs | `pipeline_configs` | Module, Zeiträume, Stage-Slugs (parallel zum Unified System) | +| Workflow-Fragenkatalog | `workflow_question_catalog` | Fragetypen, Templates, Normalisierung | + +### Bewusst nicht hardcodiert + +- Prompt-Texte, Pipeline-Zusammensetzung, Workflow-Topologie +- Kategorie, Sichtbarkeit (`active`), Sortierung +- Output-Format und Schema pro Prompt +- Referenz vs. Inline in Pipeline-Stages + +### Hardcodiert (Code / Env) + +- Platzhalter-Definitionen und Resolver (`PLACEHOLDER_MAP`, Registry) +- LLM-Modell (`OPENROUTER_MODEL`) +- Default-Module und -Zeiträume in `/prompts/execute` +- Domänen-Kategorien im Frontend (`analysisCategories.js`) +- Meta-Prompts für Generate/Optimize (Admin-Tooling) + +### Trennung: Template · Platzhalter · Kontext · Workflow + +| Schicht | Ort | Rolle | +|---------|-----|-------| +| **Templates** | `ai_prompts.template`, `stages`, Knoten-Templates im Graph | Was an die KI geht | +| **Platzhalter** | `placeholder_resolver` + Registry | Semantische API-Keys `{{key}}`, Resolver-Funktionen | +| **Kontextdaten** | `execute_prompt_with_data` + Resolver → `data_layer/` | Werte für Platzhalter | +| **Workflows** | `graph_data` + `workflow_executor` | Ausführungsgraph, Verzweigung, Join, Aggregation | + +### Durchsetzung: keine Sonderlogik außerhalb der Engine + +**Konzeptionell:** Ein Executor (`execute_prompt` → `execute_prompt_with_data`) als Single Entry Point. + +**Praktisch unvollständig:** Legacy-Pfade in `insights.py` (`/insights/run`, `/insights/pipeline`) mit eigener Template-Logik (`_prepare_template_vars`, `_render_template`) und direkten LLM-Calls; `History.jsx` nutzt noch `runInsight`. Kein technischer Guard (Lint/Policy), nur Konvention. + +### Rollen und Berechtigungen + +| Rolle | Darf | +|-------|------| +| **Admin** (`require_admin`) | Prompts/Workflows/Pipeline-Configs CRUD, Import/Export, Reset-to-default, Generate/Optimize, Platzhalter-Metadaten-ZIP | +| **Nutzer** (`require_auth`) | Aktive Prompts listen (ohne Pipeline-Slugs), ausführen (`/prompts/execute`), Preview, Platzhalter-Katalog, eigene Werte exportieren | + +Workflow-Editor-Route (`/workflow-editor/:id`) ist nicht hinter `RequireAdmin`; Schreib-APIs sind admin-geschützt. + +### Versionierung, Freigabe, Test + +| Mechanismus | Status | +|-------------|--------| +| Prompt-Versionsverlauf in DB | ❌ Overwrite | +| Reset-to-default für System-Prompts | ✅ `is_system_default` + `default_template` | +| JSON Import/Export (Dev→Prod) | ✅ `/export-all`, `/import` | +| Admin-Test mit Debug | ✅ `debug=true`, UnifiedPromptModal | +| Preview ohne LLM | ✅ `POST /preview` | +| Platzhalter-Deprecation-Prozess | 📄 dokumentiert, nicht runtime-erzwungen | +| Formales Freigabe-Workflow | ❌ | +| Executor-E2E-Tests | ⚠️ punktuell (Modifier, Output-Compact) | + +--- + +## Designprinzipien + +### 1. Single Executor für Prompt-Ausführung + +| | | +|---|---| +| **Prinzip** | Alle KI-Analysen laufen über `execute_prompt` / `execute_prompt_with_data`. | +| **Begründung** | Einheitliche Platzhalter-Auflösung, Debug, JSON-Validierung, Speicher-Metadaten. | +| **Quelle** | `backend/prompt_executor.py`; `POST /api/prompts/execute`; `Analysis.jsx` → `executeUnifiedPromptStream` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy `insights.py` und teils `History.jsx` umgehen den Executor noch. | + +### 2. Konfigurierbare Prompt-Bibliothek statt fest verdrahteter Texte + +| | | +|---|---| +| **Prinzip** | Prompt-Inhalte und Workflows liegen in `ai_prompts`, nicht im Anwendungscode. | +| **Begründung** | Admins können Analysen anpassen, duplizieren, deaktivieren, ohne Deploy. | +| **Quelle** | Migration 020; `UnifiedPromptCreate`/`Update` in `models.py`; `AdminPromptsPage.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Parallel existieren noch `pipeline_configs` und hardcodierte Default-Module/Zeiträume. | + +### 3. Drei Prompt-Typen mit klarer Verantwortung + +| | | +|---|---| +| **Prinzip** | `base` = wiederverwendbarer Baustein; `pipeline` = sequenzielle Stages; `workflow` = Graph mit Verzweigung. | +| **Begründung** | Komposition ohne Copy-Paste; Reference-Prompts in Pipelines (`source: 'reference'`). | +| **Quelle** | `execute_prompt()` Typ-Verzweigung; `StagePromptCreate` in `models.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Pipeline-Stages laufen sequentiell, obwohl konzeptionell „parallel“; `workflow_definitions` und `ai_prompts.graph_data` doppelt. | + +### 4. Platzhalter als API-Verträge (Registry) + +| | | +|---|---| +| **Prinzip** | Platzhalter sind registrierte, dokumentierte Verträge — keine freien Prompt-Hilfsvariablen. | +| **Begründung** | Konsistenz für Injektion, GUI-Picker, Export, Validierung. | +| **Quelle** | `PLACEHOLDER_REGISTRY_FRAMEWORK.md`; `docs/PLACEHOLDER_GOVERNANCE.md`; `import placeholder_registrations` in `main.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Duplikat `PLACEHOLDER_MAP` in `placeholder_resolver.py` neben Registry; Metadaten teils noch Legacy. | + +### 5. Trennung Template (Was) vs. Resolver (Daten) + +| | | +|---|---| +| **Prinzip** | Templates enthalten nur `{{keys}}`; Berechnung liegt in Resolver/Data Layer. | +| **Begründung** | Prompt-Autoren ändern Text, nicht Berechnungslogik. | +| **Quelle** | `resolve_placeholders()` in `prompt_executor.py`; Registry-Felder `resolver_function`, `data_layer_function` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `execute_prompt_with_data` lädt zusätzlich Roh-SQL pro Modul — zweite Kontext-Schicht. | + +### 6. Layer-1-Daten vs. Layer-2a-Prompt-Injektion + +| | | +|---|---| +| **Prinzip** | Berechnungen in `data_layer/`; Prompt Engine konsumiert nur formatierte Werte. | +| **Begründung** | Single Source of Truth für Charts, Platzhalter, KI. | +| **Quelle** | Phase-0c-Architektur; Registry-Felder `data_layer_module` / `layer_1_decision` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy `_prepare_template_vars` in `insights.py` umgeht Data Layer. | + +### 7. Transparenz durch Debug- und Preview-Modus + +| | | +|---|---| +| **Prinzip** | Aufgelöste/unaufgelöste Platzhalter, Final-Prompt und Stage-Outputs sind inspizierbar; Preview ohne LLM. | +| **Begründung** | Admin kann Prompts testen und Wertetabelle/Expertenmodus speisen. | +| **Quelle** | `debug`-Parameter; `/preview`; `UnifiedPromptModal` Test-Button; `ai_insights.metadata` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Debug-Daten in Responses können groß/sensibel sein; kein separates Staging. | + +### 8. Wiederverwendbare Base-Prompts via Reference + +| | | +|---|---| +| **Prinzip** | Pipeline-Stages referenzieren Slugs statt Templates zu duplizieren. | +| **Begründung** | Ein Baustein, mehrere Workflows; zentral wartbar. | +| **Quelle** | `source == 'reference'` in `execute_pipeline_prompt()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine Referenz-Versionierung; Änderung am Base-Prompt wirkt sofort auf alle Referenzen. | + +### 9. Strukturierte LLM-Ausgaben per Output-Format + +| | | +|---|---| +| **Prinzip** | Pro Prompt/Prompt-Def: `output_format: text\|json`, optional `output_schema`; Pipeline-Outputs als Stage-Keys im Kontext. | +| **Begründung** | Maschinenlesbare Zwischenergebnisse für Multi-Stage und Wertetabelle. | +| **Quelle** | `validate_json_output()`; Stage `output_key` in Pipeline | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | JSON-Schema-Validierung ist TODO (`jsonschema`); Markdown-Unwrap als Heuristik. | + +### 10. Admin-only Konfiguration, User-only Ausführung + +| | | +|---|---| +| **Prinzip** | Schreibende Prompt-/Workflow-Operationen nur mit `require_admin`. | +| **Begründung** | Produktions-Prompts sind Systemkonfiguration, nicht Nutzerdaten. | +| **Quelle** | `require_admin` in `routers/prompts.py`; `RequireAdmin` für `/admin/prompts` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `/workflow-editor/:id` ohne Frontend-Admin-Gate; `/prompts/execute` ohne `check_feature_access` (Legacy-Pfad in `insights.py` hat Enforcement). | + +### 11. Import/Export als Umgebungs-Sync + +| | | +|---|---| +| **Prinzip** | Prompt-Sätze als JSON exportierbar/importierbar (Dev→Prod). | +| **Begründung** | Konfiguration versionierbar in Git, nicht in der App-DB. | +| **Quelle** | `GET /export-all`, `POST /import` in `routers/prompts.py`; Admin-UI Buttons | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Kein Diff, keine Merge-Strategie, kein Rollback; Overwrite-Flag manuell. | + +### 12. Workflow-Erweiterung: Graph + Fragenergänzungen + Signale + +| | | +|---|---| +| **Prinzip** | Workflows als Knoten/Kanten-Graph; optionale Fragen am Knoten; Normalisierung/Logic/Join als Engine-Schicht. | +| **Begründung** | Bedingte, verzweigte Analysen jenseits linearer Pipelines. | +| **Quelle** | `workflow_executor.py`; Migration 034; `question_augmenter.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Hohe Komplexität; zwei Speicherorte (`graph_data` vs. `workflow_definitions`); Jinja2 im Workflow-Pfad zusätzlich zu `{{}}`-Resolver. | + +### 13. Platzhalter-Modifier für KI-Kontext + +| | | +|---|---| +| **Prinzip** | `{{key\|d}}` (Wert + Beschreibung), `{{key\|x}}` (Erklärung ohne Zahl) über Katalog-Metadaten. | +| **Begründung** | Prompts können Kontext für das Modell reichhaltiger machen ohne Template-Duplikate. | +| **Quelle** | `resolve_placeholders()` Modifier-Logik; `get_placeholder_catalog()` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Modifier-Syntax ad hoc; Katalog-Pflicht für sinnvolle `\|x`-Nutzung. | + +### 14. System-Prompt-Reset statt DB-Versionierung + +| | | +|---|---| +| **Prinzip** | Shipped Prompts mit `is_system_default` + `default_template`; Admin-Reset auf Original. | +| **Begründung** | Schutz vor irreversiblen Fehlkonfigurationen ohne vollständiges Versionsmodell. | +| **Quelle** | Migration 019; `POST /{prompt_id}/reset-to-default` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nur ein Default-Snapshot; keine Historie benutzerdefinierter Änderungen. | + +### 15. Governance für Platzhalter-Änderungen + +| | | +|---|---| +| **Prinzip** | Breaking Changes nur über Deprecation + Replacement; semantische Verträge dokumentiert. | +| **Begründung** | Prompts in Produktion brechen nicht still. | +| **Quelle** | `docs/PLACEHOLDER_GOVERNANCE.md` §4 | +| **Tragfähigkeit** | **mittel** (prozessual) | +| **Einschränkung** | Prozess in Doku, nicht im Runtime erzwungen; Checkliste verweist noch auf Legacy-Dateien. | + +--- + +## Nicht übernehmen + +Muster, die sich nicht bewährt haben oder zu produktspezifisch sind — bei Neuentwicklung vermeiden: + +1. **Parallele Ausführungspfade** — Legacy `insights.py` (`/insights/run`, `/insights/pipeline`) mit eigener Template-Engine und LLM-Calls neben `prompt_executor`; Frontend-Split (`Analysis` vs. `History`). + +2. **Doppelte Metadaten für Platzhalter** — `PLACEHOLDER_MAP`, Registry, `placeholder_metadata_complete.py` und Katalog-Fallbacks parallel; erzeugt Sync-Risiko (114 Keys müssen deckungsgleich bleiben). + +3. **Zwei Pipeline-Modelle gleichzeitig** — `pipeline_configs` (3 fixe Stages) und Unified `type=pipeline` in `ai_prompts`; Migration 020 migriert, Tabelle bleibt aktiv. + +4. **Zwei Workflow-Speicher** — `workflow_definitions.graph` und `ai_prompts.graph_data`; unklare Single Source of Truth. + +5. **Roh-SQL-Kontextladung im Executor** — `execute_prompt_with_data` lädt Modul-Rohdaten per SQL, obwohl Resolver/Data Layer existieren; Domänenwissen in der Engine. + +6. **Hardcodierte Execute-Defaults** — Module/Zeiträume in `/execute` fest verdrahtet statt aus Prompt-/Pipeline-Konfiguration. + +7. **Fehlende Feature-Enforcement-Konsistenz** — `check_feature_access` auf Legacy-Insights, nicht auf `/prompts/execute`. + +8. **„Parallel“ als sequentiell implementiert** — Pipeline-Stages kommentiert als parallel, Code sequentiell; irreführendes Modell. + +9. **Unvollständige Output-Validierung** — JSON-Parse + Markdown-Unwrap, Schema-Check auskommentiert/TODO. + +10. **Workflow-Editor ohne klares Admin-Gate in Routing** — Schreib-API geschützt, UI-Route für alle Authentifizierten erreichbar. + +11. **Domänen-spezifische Hardcodings in der Engine** — Kategorien (`körper`, `ernährung`, …), deutsche Meta-Prompts für Generate/Optimize, Fitness-Kontext in Prompt-Generator. + +12. **Kein integriertes Prompt-Versions- und Freigabemodell** — Overwrite + JSON-Export ersetzt keine Revision/Review/Publish-Pipeline. + +13. **Issue #51 (Seitenzuordnung) nicht umgesetzt** — Prompt-Verfügbarkeit kontextuell nicht konfigurierbar; alles über zentrale Analyse-Seite. + +14. **Globales LLM-Modell per Env** — `workflow_executor` übergibt Modell pro Call, `call_openrouter` ignoriert es; keine prompt-spezifische Modellwahl trotz API-Shape. + +--- + +## Verwandte Dokumentation + +- Fachliche Spec: [AI_PROMPTS.md](../../functional/AI_PROMPTS.md) +- Platzhalter-Registry: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md) +- Platzhalter-Governance: [PLACEHOLDER_GOVERNANCE.md](../../../../docs/PLACEHOLDER_GOVERNANCE.md) +- Issue #28 (Unified Prompt System): abgeschlossen, siehe `CLAUDE.md` +- Issue #51 (Prompt-Seitenzuordnung): [issue-51-prompt-page-assignment.md](../../../../docs/issues/issue-51-prompt-page-assignment.md) +- **Serie (abgeschlossen):** [Index](./README.md) · [Jinkendo Foundation](../README.md) · Dokumente #1–#9 diff --git a/docs/reference/design-principles/mitai/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..b4fb650 --- /dev/null +++ b/docs/reference/design-principles/mitai/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md @@ -0,0 +1,315 @@ +# Registry- & Plugin-Muster – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Querschnittsmuster für erweiterbare Registries — drei Implementierungen in Mitai (Platzhalter, Dashboard-Widgets, CSV-Import) + +**Serie:** Designprinzipien für Produktfamilie · Dokument 4 von n +**Vorgänger:** [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) + +**Die drei Registries:** + +| Registry | Backend-Kanon | Frontend-/Runtime-Registry | Leitfaden | +|----------|---------------|----------------------------|-----------| +| **Platzhalter** | `placeholder_registry.py` + `placeholder_registrations/` | `PLACEHOLDER_MAP` in `placeholder_resolver.py` | `PLACEHOLDER_REGISTRY_FRAMEWORK.md` | +| **Dashboard-Widgets** | `widget_catalog.py` | `registerDashboardWidgets.js` → `dashboardWidgetRegistry.jsx` | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` | +| **CSV-Import-Module** | `csv_parser/module_registry.py` | — (Executor + Admin-UI konsumieren API) | `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` | + +--- + +## Modul + +**Registry- & Plugin-Muster** (Meta-Schicht) + +Wiederkehrendes Architekturmuster: **Zentral registrierte, ID-basierte Erweiterungspunkte** mit Metadaten, Validierung und getrennten Konsumenten (GUI, API, Batch). Kein einzelnes Runtime-Modul — ein **Familien-Designpattern**, das in Mitai dreimal konkret umgesetzt ist. + +--- + +## Fachliche Verantwortung + +Registries übernehmen: + +1. **Autoritative ID-Liste** — Was existiert, was ist erlaubt, in welcher Reihenfolge (optional). +2. **Metadaten für Mensch & Maschine** — Titel, Beschreibung, Typen, Abhängigkeiten, semantische Verträge. +3. **Validierung** — Unbekannte IDs werden abgelehnt; Konfigurationen gegen Kanon geprüft. +4. **Entkopplung** — Implementierung (Resolver, React-Komponente, Import-Executor) registriert sich an den Kanon, nicht umgekehrt. +5. **Erweiterbarkeit ohne Schema-Explosion** — Neue Einträge über Code-Registrierung (+ ggf. DB-Overrides), nicht über neue DB-Spalten pro Feature. + +Registries übernehmen **nicht**: + +- Fachliche Berechnung (→ Data Layer) +- Entitlement-Auflösung (→ Feature System; Widgets *referenzieren* Features) +- Auth / Mandanten + +### Gemeinsames Strukturschema + +``` +┌─────────────────────────────────────────────────────────┐ +│ REGISTRY (Kanon) │ +│ ID + Metadaten + optionale Policies/Abhängigkeiten │ +└────────────────────┬────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + Implementierung Validierung Konsumenten + (Resolver/ (Schema/ (GUI-Picker, + Component/ Tests) API, Executor) + Executor) +``` + +### Vergleich der drei Implementierungen + +| Aspekt | Platzhalter | Dashboard-Widgets | CSV-Import | +|--------|-------------|-------------------|------------| +| **Primär-ID** | `key` (snake_case) | `id` (snake_case) | Modul-Name (`nutrition`, `activity`, …) | +| **Kanon-Speicher** | Python Singleton + Cluster-Module | Python-Liste `WIDGET_CATALOG` | Python-Dict `MODULE_DEFINITIONS` | +| **Metadaten-Tiefe** | Sehr hoch (22+ Felder, Evidence) | Mittel (title, description, requires_feature) | Hoch (fields, types, duplicate_key, aggregates) | +| **Runtime-Registry** | `register_placeholder()` beim Import | `registerDashboardWidget()` idempotent | Keine — Executor liest Dict | +| **Frontend-Spiegel** | PlaceholderPicker, Admin-Prompt-Modal | `ensureDashboardWidgetsRegistered()` | Admin CSV Template Editor | +| **Validierung** | `metadata.validate()`, Governance-Docs | Pydantic Layout + `validate_widget_entry_config` | `validate_field_mappings`, `validate_csv_template` | +| **Tests** | `test_placeholder_metadata.py`, … | `test_widget_catalog.py` | `test_template_validator.py`, … | +| **DB-Override** | Nein (nur Code) | Ja (`widget_feature_requirements`, Layout pro Profil) | Ja (Vorlagen, Nutzer-Mappings) | +| **Entitlements** | Indirekt (Data/Features) | `requires_feature` → `check_feature_access` | Feature-Limits beim Import | + +--- + +## Designprinzipien (übergreifend) + +### 1. Single Source of Truth für erlaubte IDs + +| | | +|---|---| +| **Prinzip** | Jede erweiterbare Einheit hat **eine** autoritative ID-Liste; Router und UI duplizieren keine Feld-/Widget-/Platzhalter-Listen. | +| **Begründung** | Verhindert „funktioniert in der UI, scheitert in der API“ und umgekehrt. | +| **Quelle** | `module_registry.py` Kommentar; `widget_catalog.py`; `PLACEHOLDER_REGISTRY_FRAMEWORK.md` §2.3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Platzhalter: paralleles `PLACEHOLDER_MAP` neben Registry. | + +### 2. ID-Stabilität als Vertrag + +| | | +|---|---| +| **Prinzip** | IDs/Keys sind stabile API-Verträge; Umbenennung = neuer Key + Deprecation, nicht stilles Rename. | +| **Begründung** | Prompts, Layouts und CSV-Vorlagen referenzieren IDs persistent. | +| **Quelle** | `docs/PLACEHOLDER_GOVERNANCE.md` §4.2–4.3; Widget-Layout in `profiles.dashboard_layout` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht überall runtime-erzwungen (Platzhalter-Governance prozessual). | + +### 3. Metadaten getrennt von Implementierung + +| | | +|---|---| +| **Prinzip** | Registry speichert **Was** (ID, Beschreibung, Typ, Policies); Implementierung lebt in separaten Modulen. | +| **Begründung** | GUI, Export, Validierung und Docs können Metadaten nutzen ohne Resolver/Component zu laden. | +| **Quelle** | `PlaceholderMetadata` Dataclass; `WidgetCatalogEntry`; `MODULE_DEFINITIONS.fields` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Platzhalter bindet `_resolver_func` optional an Metadata-Objekt. | + +### 4. Zwei-Phasen-Registrierung (Backend-Kanon + Runtime-Binding) + +| | | +|---|---| +| **Prinzip** | Phase A: Kanon definiert IDs und Metadaten. Phase B: Implementierung registriert sich (Platzhalter-Cluster-Import, `registerDashboardWidget`, Executor nutzt Modul-Def). | +| **Begründung** | Backend bleibt autoritativ; Frontend/plugins können nachziehen, Tests können Lücken finden. | +| **Quelle** | `import placeholder_registrations` in `main.py`; `ensureDashboardWidgetsRegistered()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Fehlende Frontend-Registrierung zeigt „Unbekanntes Widget“ — kein Build-Time-Fail. | + +### 5. Auto-Registration via Package-Import + +| | | +|---|---| +| **Prinzip** | Side-Effect-Import eines Packages triggert vollständige Registrierung (`placeholder_registrations/__init__.py`). | +| **Begründung** | Keine vergessene manuelle Registrierungsliste in `main.py` pro Eintrag. | +| **Quelle** | `placeholder_registrations/__init__.py`; `main.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Import-Reihenfolge und zirkuläre Imports beachten. | + +### 6. Validierung am Registry-Rand + +| | | +|---|---| +| **Prinzip** | Unbekannte Keys/Widget-IDs/Feld-Mappings werden an Registry-Grenzen abgewiesen, nicht erst in der Business-Logik. | +| **Begründung** | Frühes, klares Fehlerbild für Admins und Entwickler. | +| **Quelle** | `DashboardWidgetEntry` + `ALLOWED_WIDGET_IDS`; `validate_field_mappings()`; `get_unknown_placeholders()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Prompt-Templates können unbekannte Platzhalter erst zur Laufzeit offenbaren. | + +### 7. Konsumenten-Agnostik + +| | | +|---|---| +| **Prinzip** | Dieselbe Registry bedient mehrere Konsumenten (KI, Charts, Export, Admin-Browser) ohne duplizierte Metadaten. | +| **Begründung** | DRY für Beschreibungen, Kategorien, Beispielwerte. | +| **Quelle** | `PLACEHOLDER_REGISTRY_FRAMEWORK.md` §2.2; `get_placeholder_catalog()` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Export-Pfade mergen noch „Registry + Legacy“. | + +### 8. Erweiterungs-Checkliste statt Ad-hoc + +| | | +|---|---| +| **Prinzip** | Jedes Registry hat dokumentierte Schritte A→G (Katalog-Eintrag, Validierung, Tests, Version-Bump). | +| **Begründung** | Agenten und Menschen erweitern konsistent; Review an Checkliste. | +| **Quelle** | `DASHBOARD_WIDGETS_AGENT_GUIDE.md` §2; `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` §2; `PLACEHOLDER_DEVELOPMENT_GUIDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Drei separate Guides — kein unified „Registry Agent Guide“. | + +### 9. Tests auf Katalog-Konsistenz + +| | | +|---|---| +| **Prinzip** | Automatisierte Tests prüfen Eindeutigkeit, Reihenfolge, Payload-Shape, ID-Abgleich Kanon ↔ abgeleitete Sets. | +| **Begründung** | Regression wenn Katalog wächst but Registry/Layout nicht mitzieht. | +| **Quelle** | `test_widget_catalog.py`; Placeholder-Metadata-Tests | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein Cross-Registry-Test „Frontend widget IDs == backend catalog“. | + +### 10. Optionale Entitlement-Referenz im Kanon + +| | | +|---|---| +| **Prinzip** | Registry-Einträge **referenzieren** Feature-IDs (`requires_feature`), lösen Entitlements aber nicht selbst auf. | +| **Begründung** | Tier-Logik bleibt in `check_feature_access`; Katalog bleibt deklarativ. | +| **Quelle** | `WidgetCatalogEntry.requires_feature`; `dashboard_widget_entitlements.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Platzhalter haben kein direktes `requires_feature` — Gating nur indirekt. | + +--- + +## Designprinzipien (spezifisch pro Registry) + +### Platzhalter-Registry + +| # | Prinzip | Tragfähigkeit | Schwäche | +|---|---------|---------------|----------| +| P1 | **Semantischer Vertrag** (`semantic_contract`) pro Key — Platzhalter sind API, nicht Prompt-Hilfe | hoch | Viele Legacy-Keys mit schwachem Vertrag | +| P2 | **Evidence-Tagging** — Herkunft jedes Metadatenfelds nachvollziehbar | mittel | Pflegeaufwand | +| P3 | **Cluster-Module** — Registrierung nach Domäne (`nutrition_part_a`, `body_metrics`, …) | hoch | 114 Keys Sync mit `PLACEHOLDER_MAP` | +| P4 | **Data-Layer-Referenz** in Metadata (`data_layer_function`) — Bindung an Layer 1 | hoch | Nicht alle Keys vollständig verknüpft | +| P5 | **Singleton** `get_registry()` — globaler Kanon | hoch | Test-Isolation braucht Disziplin | + +### Dashboard-Widget-Registry + +| # | Prinzip | Tragfähigkeit | Schwäche | +|---|---------|---------------|----------| +| W1 | **Backend-Katalog = ALLOWED_WIDGET_IDS** — Layout-Schema leitet ab | hoch | Frontend-Registry manuell parallel | +| W2 | **`merge_missing_catalog_widgets`** — neue Katalog-IDs erscheinen im Layout ohne User-Reset | hoch | — | +| W3 | **Strikte `config`-Validierung** nur für whitelisted Widgets (`WIDGETS_ALLOWING_CONFIG`) | hoch | Config-Schemas pro Widget heterogen | +| W4 | **Graceful Degradation** — unregistrierte ID → Fehler-Karte, nicht Crash | mittel | Maskiert Deploy-Fehler | +| W5 | **WidgetErrorBoundary** pro Instanz | hoch | — | + +### CSV-Modul-Registry + +| # | Prinzip | Tragfähigkeit | Schwäche | +|---|---------|---------------|----------| +| C1 | **`MODULE_DEFINITIONS` = einzige Feldliste** — Router duplizieren nicht | hoch | Activity erweitert dynamisch um `training_parameters` | +| C2 | **Deklarative Duplikat-Strategie** (`duplicate_key`, `update`/`skip`) | hoch | Modul-spezifische Executor-Sonderfälle | +| C3 | **`import_row_processing`** — Aggregation in Registry, nicht im Router | hoch | Legacy-Defaults in Modul-Def | +| C4 | **`validate_field_mappings`** vor Persistenz | hoch | Nutzer-Mappings teils ohne volle Validator-Parität (#71) | +| C5 | **Persistenz-Orchestrator** liest Registry-Felder (`activity_persistence_orchestrator`) | hoch | Nur Activity voll ausgebaut | + +--- + +## Anti-Pattern: Doppel-Registry + +Mitai zeigt an **Platzhaltern** das Risiko explizit: + +``` +placeholder_registrations/ ──register──► PlaceholderRegistry (Metadata) + │ ▲ + └── resolver in code ──► PLACEHOLDER_MAP (Runtime, 114 Keys) +``` + +**Regel für Produktfamilie:** Runtime-Auflösung soll Metadata-Registry **lesen**, nicht spiegeln. + +Widgets sind näher am Ideal: Backend `WIDGET_CATALOG` ist Kanon; Frontend muss IDs manuell in `registerDashboardWidgets.js` binden — akzeptabel, aber testbar machen (Cross-Check). + +--- + +## Nicht übernehmen + +1. **Parallele Runtime-Maps** — `PLACEHOLDER_MAP` + Registry; eine Quelle für Keys und Resolver-Pfad. + +2. **Frontend-Registry ohne Build-/Test-Gate** — fehlende `registerDashboardWidget`-Einträge erst zur Laufzeit sichtbar. + +3. **Metadaten-Duplikation in Export-Code** — hardcodierte Beschreibungen außerhalb der Registry. + +4. **Registry-Einträge ohne Validierungs-Tests** — besonders bei 100+ Platzhaltern. + +5. **Import-Feldlisten in Routern** — alles über `module_registry` (Mitai-Zielbild, noch nicht überall). + +6. **Evidence-/Metadata-Pflicht für einfache Plugins** — 22 Felder für Widgets wären Overkill; **Metadaten-Tiefe an Risiko anpassen**. + +7. **Dynamische Registry aus DB ohne Versionierung** — Widget-Feature-Overrides OK; kompletter Kanon nur in DB wäre schwer testbar. + +8. **Registry ohne Deprecation-Pfad** — Breaking Key-Changes still (Platzhalter-Governance existiert, durchsetzen). + +9. **Entitlements in Registry auflösen** — Widgets richtig: referenzieren; nicht Tier-Logik im Katalog. + +10. **Schlaf-Modul leeres `fields: {}`** — Sondermodus (`import_mode`) statt sauberem Registry-Eintrag; technische Schuld. + +--- + +## Entscheidungsmatrix: Welche Registry-Tiefe? + +| Wenn … | dann Metadaten-Tiefe … | Beispiel | +|--------|------------------------|----------| +| Externe Verträge / KI / Compliance | Hoch (Vertrag, Evidence, Missing-Policy) | Platzhalter | +| UI-Plugin mit optionaler Config | Mittel (ID, title, config schema, feature ref) | Widgets | +| Daten-Ingest / Schema-Mapping | Hoch (Typen, keys, constraints) | CSV-Module | +| Internes Hilfsmodul | Minimal (ID + Handler-Ref) | — | + +--- + +## Modul-Inventar (Querschnitt) + +``` +backend/ +├── placeholder_registry.py +├── placeholder_registrations/ # Auto-import Cluster +├── placeholder_resolver.py # PLACEHOLDER_MAP (Legacy-Spiegel) +├── placeholder_registry_export.py +├── widget_catalog.py +├── dashboard_layout_schema.py +├── dashboard_widget_config.py +├── dashboard_widget_entitlements.py +├── widget_feature_requirements_db.py +└── csv_parser/ + └── module_registry.py + +frontend/src/ +├── widgetSystem/ +│ ├── registerDashboardWidgets.js +│ └── dashboardWidgetRegistry.jsx +└── components/workflow/panels/PlaceholderPicker.jsx +``` + +--- + +## Verwandte Dokumentation + +- Platzhalter: [PLACEHOLDER_REGISTRY_FRAMEWORK.md](../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md), [PLACEHOLDER_GOVERNANCE.md](../../../../docs/PLACEHOLDER_GOVERNANCE.md) +- Widgets: [DASHBOARD_WIDGETS_AGENT_GUIDE.md](../../technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md) +- Import: [UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md](../../technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md) +- Entitlements (Widget-Gating): [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Data Layer (Platzhalter-Berechnung): [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) +- Prompt Engine (Platzhalter-Konsument): [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](./PROMPT_ENGINE_DESIGN_PRINCIPLES.md) + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1 | Prompt Engine | ✅ | +| 2 | Data Layer | ✅ | +| 3 | Feature & Entitlement | ✅ | +| 4 | Registry-/Plugin-Muster | ✅ dieses Dokument | +| 5 | Auth & Session | ✅ `AUTH_SESSION_DESIGN_PRINCIPLES.md` | +| 6 | Universal Import | ✅ | +| 7 | Dashboard Widgets | ✅ | +| 8 | Navigation / IA | ✅ | +| 9 | Migration & Deploy | ✅ | + +*Hinweis:* Dokumente 6 und 7 vertiefen Einzel-Registries; dieses Meta-Dokument ist die übergreifende Extraktion. diff --git a/docs/reference/design-principles/mitai/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/mitai/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..5687e71 --- /dev/null +++ b/docs/reference/design-principles/mitai/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md @@ -0,0 +1,325 @@ +# Universal CSV Import – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Universal CSV Import (Issue #21) — Ingest/Mapping/Persistenz, keine Auswertungslogik + +**Serie:** Designprinzipien für Produktfamilie · Dokument 6 von n +**Vorgänger:** [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Modul-Kanon | `backend/csv_parser/module_registry.py` | +| Ausführung | `backend/csv_parser/executor.py` | +| Parsing/Typen | `core.py`, `type_converter.py`, `field_units.py` | +| Aggregation | `import_row_processing.py` | +| Validierung | `template_validator.py` | +| Fehler-Hints | `import_errors.py` | +| Mapping-Vorschläge | `mapping_suggest.py` | +| Nutzer-API | `backend/routers/csv_import.py` | +| Admin-Vorlagen | `backend/routers/admin_csv_templates.py` | +| Persistenz-Orchestrator | `data_layer/activity_persistence_orchestrator.py` | +| Leitfaden | `UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` | +| Import-Grenze | `.claude/rules/ARCHITECTURE.md` §8 | + +--- + +## Modul + +**Universal CSV Import** + +Konfigurierbare Pipeline: **CSV-Datei → Feld-Mapping → Typkonvertierung → (optional Aggregation) → DB-Upsert** — mit Vorlagen, Audit-Log und row-level Fehlertoleranz. + +--- + +## Fachliche Verantwortung + +Das Modul übernimmt: + +1. **Modul-Registry** — Welche Zieltabellen/Felder importierbar sind (Typen, Duplikat-Keys, Strategien). +2. **Vorlagen (Mappings)** — System-Templates (Admin) + Nutzer-Kopien (`csv_field_mappings`). +3. **Analyse** — Delimiter-Erkennung, Spalten-Signatur, Mapping-Vorschläge, Diagnose einzelner Zeilen. +4. **Ausführung** — Upsert pro Modul, `source=csv`, Statistik, `affected_ids`. +5. **Fehlertransparenz** — Row-level Errors mit `code`/`hint`; kein Silent-Fail der ganzen Transaktion. +6. **Audit** — `csv_import_log` mit Status, Counts, betroffenen IDs. +7. **Limits** — Dateigröße/Zeilen aus `system_config`; Feature-Entitlements pro Modul. + +Es übernimmt **nicht**: + +- Fachliche Metriken / Scores (→ Data Layer, siehe [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md)) +- Prompt-/KI-Logik +- Vollständiger Ersatz aller Legacy-Import-Endpoints (noch parallel) + +### Pipeline (Happy Path) + +``` +Upload CSV + → decode_raw_bytes + resolve_effective_csv_delimiter + → Vorlage laden (csv_field_mappings) + → validate (optional Admin) / feature check + → run_universal_csv_import(cur, …) // eine Transaktion + → build_row_after_mapping (type_converter) + → aggregate_mapped_rows (import_row_processing) + → UPSERT / activity_persistence_orchestrator + → csv_import_log UPDATE + increment_feature_usage +``` + +### Unterstützte Module (Registry) + +| Modul | Zieltabelle | Besonderheit | +|-------|-------------|--------------| +| `nutrition` | `nutrition_log` | Tages-Aggregation | +| `weight` | `weight_log` | Duplikat: profile + date | +| `activity` | `activity_log` | SAVEPOINT pro Zeile; EAV via Orchestrator | +| `vitals_baseline` | `vitals_baseline` | Tages-Aggregation | +| `blood_pressure` | `blood_pressure_log` | Composite `measured_at` | +| `sleep` | `sleep_log` | Legacy-Adapter `import_mode: apple_sleep_aggregate` | + +--- + +## Administrierte vs. code-definierte Konfiguration + +| Konfiguration | Speicherort | Wer pflegt? | +|---------------|-------------|-------------| +| Zielfelder, Typen, Duplikat-Keys | `MODULE_DEFINITIONS` | Entwickler (Code) | +| System-Vorlagen | `csv_field_mappings` (`is_system=true`) | Admin (+ Migration Seeds) | +| Nutzer-Mappings | `csv_field_mappings` (`profile_id`) | Nutzer (Kopie/Anpassung) | +| `field_mappings`, `type_conversions`, `import_row_processing` | JSONB in Vorlage | Admin/Nutzer | +| Import-Limits | `system_config.csv_import` | Admin | +| Delimiter-Sniffing-Heuristik | `core.py` | Code | +| Header-Aliases (Vorschläge) | `mapping_suggest.py` | Code | + +**Bewusst nicht in Routern hardcodiert:** Feldlisten, Duplikat-Logik — nur Registry + Executor. + +--- + +## Designprinzipien + +### 1. Module Registry als Single Source of Truth + +| | | +|---|---| +| **Prinzip** | Alle erlaubten Zielfelder, Typen und Duplikat-Keys leben in `MODULE_DEFINITIONS` — Router duplizieren nicht. | +| **Begründung** | Admin-UI, Validator, Executor und `/api/csv/modules` bleiben synchron. | +| **Quelle** | `module_registry.py`; Agent-Guide §1 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Activity erweitert Felder dynamisch aus `training_parameters` (DB). | + +### 2. Ingest vs. Interpretation (Import-Grenze) + +| | | +|---|---| +| **Prinzip** | Import: Mapping + Typ/Einheit + Duplikat/Upsert. **Keine** fachliche Auswertung beim Insert. | +| **Begründung** | Semantik gehört in Data Layer; Import bleibt austauschbar und testbar. | +| **Quelle** | `ARCHITECTURE.md` §8; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `sleep_apple_import.py` ist Legacy-Adapter mit quellenspezifischer Logik. | + +### 3. Vorlagen trennen Struktur von Datei + +| | | +|---|---| +| **Prinzip** | `csv_field_mappings` speichert Modul, Delimiter, Header-Flag, Mappings, Conversions, Row-Processing — unabhängig vom Upload. | +| **Begründung** | Wiederverwendung (Apple Health, Omron, …); Nutzer wählt Vorlage statt jedes Mal neu zu mappen. | +| **Quelle** | Migration 042; Admin + User APIs | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nutzer-Kopien nicht immer durch `validate_csv_template` (#71). | + +### 4. Effektives Trennzeichen aus Datei, nicht blind aus Vorlage + +| | | +|---|---| +| **Prinzip** | `resolve_effective_csv_delimiter` — DE-Export (`;`) vs. EN-Vorlage (`,`) wird aus Header-Feldanzahl erkannt. | +| **Begründung** | Regionale CSV-Exporte brechen sonst das gesamte Mapping (eine Spalte). | +| **Quelle** | `core.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Heuristik, kein 100%-Garant für exotische Formate. | + +### 5. Typ- und Einheiten-Konvertierung deklarativ + +| | | +|---|---| +| **Prinzip** | `type_conversions` + `source_unit` in Vorlage; Logik in `type_converter` / `field_units`. | +| **Begründung** | kJ→kcal, Datumsformate, Dezimal-Komma ohne Code pro Quelle. | +| **Quelle** | `type_converter.py`, `field_units.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Falsche `source_unit` → DB-Overflow; `enrich_row_error` hilft nachträglich. | + +### 6. Zeilen-Aggregation vor Upsert + +| | | +|---|---| +| **Prinzip** | `import_row_processing` (group_by + aggregates) fasst mehrere CSV-Zeilen pro logischem Tag/Datensatz zusammen. | +| **Begründung** | Ernährung/Vitals: viele Rohzeilen → ein Tageseintrag. | +| **Quelle** | `import_row_processing.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Modul-Default als Legacy-Fallback wenn Vorlage leer; Admin „Format prüfen“ kann Processing auslassen. | + +### 7. Ein Cursor, eine Transaktion, SAVEPOINT pro Zeile + +| | | +|---|---| +| **Prinzip** | `run_universal_csv_import(cur, …)` nutzt **bestehenden** Cursor; bei Row-Fehlern SAVEPOINT + ROLLBACK TO, nicht ganze Xact abbrechen. | +| **Begründung** | PostgreSQL „transaction aborted“; partielle Imports mit Fehlerliste. | +| **Quelle** | `executor.py` (activity, vitals); `csv_import.py` SAVEPOINT `csv_import_exec` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Module gleich implementiert; Disziplin pro Modul. | + +### 8. Kein verschachteltes get_db im Importpfad + +| | | +|---|---| +| **Prinzip** | FK-Auflösung (z. B. Trainingstyp) und Activity-Persistenz mit **demselben** `cur` wie der Import. | +| **Begründung** | Pool-Deadlocks, konsistente Transaktion. | +| **Quelle** | `_resolve_training_type_for_activity`; Agent-Guide §2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Lazy-Import aus Router in Executor (Kopplung). | + +### 9. Strukturierte Fehler mit Hints + +| | | +|---|---| +| **Prinzip** | `enrich_row_error()` mappt DB-/Parse-Fehler auf `code` + menschenlesbaren `hint`. | +| **Begründung** | Nutzer/Admin können Vorlagen korrigieren ohne PostgreSQL-Kenntnis. | +| **Quelle** | `import_errors.py`; Import-Response `error_details` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Heuristische String-Matches, nicht vollständig. | + +### 10. Vorlagen-Validierung vor Persistenz (Admin) + +| | | +|---|---| +| **Prinzip** | `validate_csv_template` → `{ valid, errors[], warnings[] }`; Admin Create/Update → HTTP 422 bei Fehlern. | +| **Begründung** | Fehler früh, nicht erst beim Nutzer-Import. | +| **Quelle** | `template_validator.py`; `admin_csv_templates.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Dry-Run / User-Mappings Lücken (#71). | + +### 11. System- vs. User-Mappings (Permissions) + +| | | +|---|---| +| **Prinzip** | `is_system=true`: nur Admin editierbar; Nutzer kopiert und passt eigene Zeile an. | +| **Begründung** | Shipped Templates schützen; Individualisierung erlauben. | +| **Quelle** | `permissions.py`; DB CHECK + Unique Indexes | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 12. Import-Audit und Rollback-Vorbereitung + +| | | +|---|---| +| **Prinzip** | Jeder Lauf schreibt `csv_import_log` mit Counts, `error_details`, `affected_ids` (PKs pro Tabelle). | +| **Begründung** | Nachvollziehbarkeit, spätere Bereinigung/Rollback, Erfolgsrate pro Vorlage. | +| **Quelle** | Migration 042; `csv_import_execute` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Automatischer Rollback-Button nicht überall umgesetzt. | + +### 13. Feature-Entitlements an Import gebunden + +| | | +|---|---| +| **Prinzip** | `data_import` global + modulspezifisch (`nutrition_entries`, …); Increment nur für **neue** Zeilen. | +| **Begründung** | Konsistent mit Membership-System. | +| **Quelle** | `csv_import.py` `_check_module_feature_access`, `increment_feature_usage` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Bulk-Increment-Schleife ineffizient (wie Feature-Doc). | + +### 14. Mapping-Vorschläge (Heuristik, nicht Autorität) + +| | | +|---|---| +| **Prinzip** | `mapping_suggest.py` schlägt Spalten-Zuordnung aus Header-Aliases vor — Admin bestätigt. | +| **Begründung** | Schneller Editor-Start; Kanon bleibt menschlich/administrativ freigegeben. | +| **Quelle** | `_MODULE_HEADER_ALIASES` | +| **Tragfähigkeit** | **mittel–hoch** | +| **Einschränkung** | Domänenspezifische Aliases hardcodiert (DE/EN). | + +### 15. Persistenz-Orchestrator für komplexe Domänen + +| | | +|---|---| +| **Prinzip** | Activity: nach Registry-Mapping → `activity_persistence_orchestrator` (Upsert + EAV + Eval-Hook). | +| **Begründung** | Gleiche Schreiblogik wie REST-API; kein divergierender CSV-Pfad. | +| **Quelle** | `activity_persistence_orchestrator.py`; [DATA_LAYER_DESIGN_PRINCIPLES.md](./DATA_LAYER_DESIGN_PRINCIPLES.md) §9 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nur Activity vollständig; andere Module direkt im Executor. | + +--- + +## Nicht übernehmen + +1. **Parallele Legacy-Import-Endpoints** — `/api/nutrition/import-csv`, `/api/activity/import-csv` neben Universal-Pfad; neue Quellen nur über Universal + Vorlage (ARCHITECTURE §8.2). + +2. **Quellenspezifische Aggregat-Logik im Import** — `sleep_apple_import` als Dauerlösung; Ziel: mapping-nah + Layer 1 (Gitea #69). + +3. **Feldlisten in Routern** — jede neue Spalte nur via `module_registry` + Migration. + +4. **Verschachtelte DB-Connections im Executor** — Pool-Risiko; immer Caller-`cur` durchreichen. + +5. **Transaktion ohne SAVEPOINT bei Multi-Row-Import** — ein Fehler killt gesamten Import + opaque „transaction aborted“. + +6. **Blindes Vorlagen-Delimiter** — regionaler Export bricht Mapping. + +7. **Nutzer-Mappings ohne Validierung** — #71; Copy-from-System muss durch Validator. + +8. **Dry-Run ohne `import_row_processing`** — Admin „Format prüfen“ unvollständig vs. echter Import. + +9. **`source`-CHECK in DB vergessen** — Import setzt `csv`, Constraint muss Migration sein. + +10. **NUMERIC-Overflow durch falsche Einheit** — Schema + `source_unit` + Migrationbreite gemeinsam planen. + +11. **Interpretation/Auswertung beim Import** — Scores, TDEE, Training-Quality nicht in `executor.py`. + +12. **Executor-Monolith ohne Modul-Split** — `executor.py` wächst pro Modul; langfristig Executor-Strategie pro Registry-Key. + +--- + +## Modul-Inventar (Ist-Stand) + +``` +backend/csv_parser/ +├── module_registry.py # MODULE_DEFINITIONS +├── executor.py # run_universal_csv_import +├── core.py # decode, delimiter, limits +├── type_converter.py +├── field_units.py +├── import_row_processing.py +├── template_validator.py +├── import_errors.py +├── mapping_suggest.py +├── permissions.py +└── sleep_apple_import.py # Legacy-Adapter + +backend/routers/ +├── csv_import.py # Nutzer: modules, analyze, import, mappings +└── admin_csv_templates.py # Admin: CRUD + validate + +DB: +├── csv_field_mappings +└── csv_import_log +``` + +--- + +## Verwandte Dokumentation + +- Agent-Guide (normativ): [UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md](../../technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md) +- Registry-Meta: [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](./REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) +- Import-Grenze: [ARCHITECTURE.md](../../../rules/ARCHITECTURE.md) §8 +- Feature-Limits: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](./FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Gitea #71: Dry-Run, User-Mapping-Validierung + +--- + +## Geplante Folgedokumente (Serie) + +| # | Modul | Status | +|---|-------|--------| +| 1–5 | … | ✅ | +| 6 | Universal Import | ✅ dieses Dokument | +| 7 | Dashboard Widgets | ✅ `DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md` | +| 8 | Navigation / IA | ✅ | +| 9 | Migration & Deploy | ✅ | diff --git a/docs/reference/design-principles/shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..17161a7 --- /dev/null +++ b/docs/reference/design-principles/shinkan/ACCESS_LAYER_DESIGN_PRINCIPLES.md @@ -0,0 +1,170 @@ +# Access Layer & Tenant – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Access Layer & Tenant Governance“ — keine Shinkan-Gesamtarchitektur, keine Kampfsport-Domänenlogik + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 1 von 15 +**Mitai-Vergleich:** kein direktes Gegenstück (Mitai ist profil-zentriert, kein Vereins-Mandant) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| TenantContext | `backend/tenant_context.py` | +| Governance-Helfer | `backend/club_tenancy.py` | +| Listenfilter SQL | `library_content_visibility_sql()` in `tenant_context.py` | +| Endpoint-Audit | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` | +| Cursor-Regel | `.cursor/rules/access-layer.mdc` | +| Heuristik-Check | `backend/scripts/check_access_layer_hints.py` | +| Normative Spec | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | + +--- + +## Modul + +**Access Layer & Tenant Governance** + +Zentraler Querschnitt für Mandanten-Kontext (`club_id`), Sichtbarkeit (`private`/`club`/`official`) und einheitliche Les-/Schreibregeln für Bibliotheksartefakte (Übungen, Medien, Rahmenprogramme, Vorlagen, Progressionsgraphen). + +--- + +## Fachliche Verantwortung + +1. **TenantContext pro HTTP-Request** — Auflösung aus Session + Header `X-Active-Club-Id` + Profilfeld `active_club_id`. +2. **Datenisolierung** — `club_id` als Grenze für vereinsgeteilte Inhalte; Cross-Verein ausgeschlossen. +3. **Einheitliche Sichtbarkeits-Semantik** — gleiche Enums und Prüflogik über alle Bibliotheksmodule. +4. **Governance-Transitionen** — Regeln beim Wechsel `private` → `club` → `official` und beim Löschen. +5. **Listenfilter** — SQL-Baustein statt „SELECT *“ in jedem Router. + +### Administrierte Konfigurationen + +| Konfiguration | Speicherort | Inhalt | +|---------------|-------------|--------| +| Aktiver Verein | `profiles.active_club_id` | Persistierter UI-Kontext | +| Request-Override | Header `X-Active-Club-Id` | Client-seitiger Mandantenwechsel | +| Sichtbarkeit | Spalte `visibility` je Objekt | `private`, `club`, `official` | +| Vereinszuordnung | Spalte `club_id` | Pflicht bei `club`-Inhalten | + +### Bewusst nicht hardcodiert + +- Welche Objekte welchen Verein haben (Daten) +- Individuelle Freigabeentscheidungen (Workflow) + +### Hardcodiert (Code) + +- Enum-Werte und Leseregeln in `club_tenancy.py` / `library_content_visibility_sql` +- Plattform-Admin-Ausnahmen (`is_platform_admin`, `is_superadmin`) +- Rollencodes für Schreib-/Löschregeln (`club_admin`, `trainer`, …) + +--- + +## Designprinzipien + +### 1. Ein Mandant pro Request (`TenantContext`) + +| | | +|---|---| +| **Prinzip** | `Depends(get_tenant_context)` liefert `profile_id`, `global_role`, `effective_club_id`, Mitgliedschaften — einmal pro Request. | +| **Begründung** | Kein verteiltes „Rates“ aus Headers; konsistente Filter in allen Routern. | +| **Quelle** | `tenant_context.py`; `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` §2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Endpoints migriert; Audit-Tabelle zeigt Restbestand mit nur `require_auth`. | + +### 2. `club_id` als Datenisolierungsgrenze + +| | | +|---|---| +| **Prinzip** | Vereinsgeteilte Inhalte sind nur für aktive Mitglieder des Objekt-`club_id` lesbar — nie Cross-Verein. | +| **Begründung** | Mandantenfähigkeit für Vereinsplattform; Compliance bei geteilten Trainingsinhalten. | +| **Quelle** | `library_content_visibility_sql()`; Tests `test_access_layer*.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `division`-Verschärfung noch nicht durchgängig; reserviert im Plan. | + +### 3. Einheitliche Visibility-Semantik + +| | | +|---|---| +| **Prinzip** | `private` \| `club` \| `official` mit gleicher Bedeutung für Übungen, Medien, Rahmen, Module, Graphen. | +| **Begründung** | Nutzer und Trainer verstehen ein Freigabemodell; UI-Feld „Freigabelevel“ durchgängig. | +| **Quelle** | `club_tenancy.py`; `FACHLICHE_NUTZERFUNKTIONEN.md` §4.7 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `community`-Stufe nur dokumentiert, nicht implementiert. | + +### 4. Zentraler SQL-Filter für Bibliothekslisten + +| | | +|---|---| +| **Prinzip** | Listen nutzen `library_content_visibility_sql(alias, profile_id, role, effective_club_id)` — nicht handgeschriebene WHERE-Kopien. | +| **Begründung** | Drift-Vermeidung; ein Fix gilt für alle Kataloge. | +| **Quelle** | `tenant_context.py`; Router `exercises.py`, `training_framework_programs.py`, … | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne Legacy-Queries können noch abweichen. | + +### 5. Governance-Transitionen explizit prüfen + +| | | +|---|---| +| **Prinzip** | Wechsel von `visibility`/`club_id` über `assert_library_content_governance_transition` + `assert_valid_governance_visibility`. | +| **Begründung** | „Privat → Verein teilen“ und „official herabstufen“ sind sicherheitsrelevante Aktionen. | +| **Quelle** | `club_tenancy.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht jedes Modul ruft Transition-Helper bei PATCH auf. | + +### 6. Löschregeln nach Visibility-Stufe + +| | | +|---|---| +| **Prinzip** | `assert_library_content_deletable`: privat → Ersteller/Vereinsadmin-Kontext; club → Vereinsadmin; official → Plattform-Admin. | +| **Begründung** | Schutz vor versehentlichem Löschen fremder oder offizieller Inhalte. | +| **Quelle** | `club_tenancy.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Medien-Lifecycle hat zusätzliche Stufen (Papierkorb) — Schnittmenge beachten. | + +### 7. Aktiver Verein: Header + Profil synchron + +| | | +|---|---| +| **Prinzip** | Frontend sendet `X-Active-Club-Id`; Backend validiert gegen Mitgliedschaft; Profil speichert `active_club_id`. | +| **Begründung** | Einheitlicher Mandanten-Kontext über API und UI. | +| **Quelle** | `frontend/src/api/client.js` (`mergeActiveClubHeader`); `profiles` Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Onboarding-Nutzer ohne Verein: eingeschränkter Nav-Modus. | + +### 8. Plattform-Admin als Audit-Pfad, nicht als Bypass + +| | | +|---|---| +| **Prinzip** | Plattform-Admins sehen fremde `club`-Inhalte nur mit expliziter Regel (Mitgliedschaft oder Audit-Ausnahme in SQL). | +| **Begründung** | Superuser-Zugriff ohne Mandanten-Leak in normalen Trainer-Flows. | +| **Quelle** | `library_content_visibility_sql` — `club_ok_plat`-Zweig | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Feinheiten zwischen `admin` und `superadmin` (z. B. `official`, Legal Hold) separat geregelt. | + +### 9. Endpoint-Audit als lebendes Inventar + +| | | +|---|---| +| **Prinzip** | Jeder sicherheitsrelevante Endpoint-Eintrag in `ACCESS_LAYER_ENDPOINT_AUDIT.md`; PR-Checkliste verlangt Update. | +| **Begründung** | Sichtbarkeit des Migrationsstands; kein stilles Ausweichen auf `require_auth` allein. | +| **Quelle** | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`; `check_access_layer_hints.py` | +| **Tragfähigkeit** | **hoch** (prozessual) | +| **Einschränkung** | CI-Strict-Modus optional, nicht überall aktiv. | + +--- + +## Nicht übernehmen + +1. **Endpoints nur mit `require_auth`** bei tenant-sensitiven Daten — führt zu IDOR und fehlenden Listenfiltern. +2. **Visibility-Logik pro Router duplizieren** — historische Drift zwischen Übungen und Planung. +3. **`division` vor stabiler Vereins-Isolation** — Reihenfolge im Plan: erst Stufe C, dann D. +4. **Community-Freigabe ohne additive Felder** — würde `club`-Isolation brechen. +5. **Client-seitige Mandantenfilter ohne Server-Enforcement** — UI-Hiding reicht nicht. + +--- + +## Verwandte Dokumentation + +- [ACCESS_LAYER_AND_GOVERNANCE_PLAN.md](../../../.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md) +- [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../../../.claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..3a05cbd --- /dev/null +++ b/docs/reference/design-principles/shinkan/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md @@ -0,0 +1,140 @@ +# AI Prompt Runtime – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „AI Prompt Runtime“ — Shinkan-KI-Schicht, keine Planungs-Gesamtarchitektur + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 5 von 15 +**Mitai-Vergleich:** [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/PROMPT_ENGINE_DESIGN_PRINCIPLES.md) (#1) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Laufzeit | `backend/ai_prompt_runtime.py`, `backend/prompt_resolver.py` | +| Domänen-Orchestrierung | `backend/exercise_ai.py`, `backend/planning_exercise_*.py` | +| OpenRouter | `backend/openrouter_chat.py` | +| Admin | `backend/routers/ai_prompts_admin.py` | +| Zielbild | `.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md` | +| Job-Kontext | `backend/ai_prompt_job.py`, `backend/ai_prompt_context.py` | + +--- + +## Modul + +**AI Prompt Runtime** + +Schmale Ausführungsschicht für admin-konfigurierbare Prompts in `ai_prompts`: Laden, Mustache-Rendering, Kontext-Arten, OpenRouter-Aufruf. **Kein** vollständiges Unified Prompt System wie Mitai (keine Workflows/Pipelines in Produktion). + +--- + +## Fachliche Verantwortung + +1. **Prompt-Laden aus DB** — `load_ai_prompt_row`, `load_and_render_ai_prompt` +2. **Platzhalter-Ersetzung** — Mustache `{{key}}` via `prompt_resolver.py` +3. **Kontext-Arten** — `AiPromptContextKind` trennt Übungs-KI vs. Planungs-KI +4. **Domänen-Builder** — `exercise_ai`, Planungs-Pipelines bauen Variablen-Maps +5. **Admin CRUD + Preview** — ohne LLM in Preview-Pfaden wo vorgesehen + +--- + +## Designprinzipien + +### 1. Eine Laufzeit-Fassade für DB-Prompts + +| | | +|---|---| +| **Prinzip** | Produktive Aufrufe laden Slugs über `ai_prompt_runtime` — nicht Roh-SQL auf `ai_prompts` in Routern. | +| **Begründung** | Einheitliches inactive-Handling, Modell-Feld, Render-Metadaten. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.1; `ai_prompt_runtime.py` | +| **Tragfähigkeit** | **hoch** (Zielrichtung) | +| **Einschränkung** | Planungs-KI hat noch verteilte Orchestratoren; kein einzelner `execute_prompt` wie Mitai. | + +### 2. Konfigurierbare Bibliothek in `ai_prompts` + +| | | +|---|---| +| **Prinzip** | Template-Texte in DB; Admins ändern ohne Deploy (`ai_prompts_admin`). | +| **Begründung** | Gleiches Familien-Muster wie Mitai Prompt-Bibliothek. | +| **Quelle** | Migration 069+; Admin-UI | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine Pipeline/Workflow-Typen; Slugs hardcoded in `context_kind_for_slug`. | + +### 3. Kontext-Namespaces statt globaler Platzhalter-Soup + +| | | +|---|---| +| **Prinzip** | `AiPromptContextKind` (z. B. `exercise_form_ai`, `planning_exercise_search`) begrenzt erlaubte Builder. | +| **Begründung** | Planungs-Kontext wächst ohne Kollision mit Übungs-Keys. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Noch keine zentrale Platzhalter-Registry wie Mitai — Mustache ad hoc pro Builder. | + +### 4. Trennung Template vs. Domänen-Kontext + +| | | +|---|---| +| **Prinzip** | UI/Router liefern Pydantic-DTOs → Builder erzeugen `variables`-Map → `render_mustache_template`. | +| **Begründung** | Prompt-Autoren ändern Text, nicht Python in Routern. | +| **Quelle** | `prompt_resolver.py`; `ExerciseFormAiPromptContext` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Planungs-Kontexte noch nicht vollständig über DTOs. | + +### 5. Transport (OpenRouter) getrennt von Semantik + +| | | +|---|---| +| **Prinzip** | `openrouter_chat.py` für HTTP; Validierung/JSON-Parsing in Domänen-Schicht. | +| **Begründung** | Modellwechsel ohne Router-Anpassung. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Modell teils global Env, teils Spalte `openrouter_model` — Konvergenz offen. | + +### 6. Reset-to-default für System-Prompts + +| | | +|---|---| +| **Prinzip** | `default_template` + Admin-Reset — kein vollständiges Versionsmodell. | +| **Begründung** | Familien-Muster aus Mitai; Schutz vor Fehlkonfiguration. | +| **Quelle** | Migration 069 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Keine Historie benutzerdefinierter Änderungen. | + +### 7. Admin-only Schreiben, authentifiziertes Ausführen + +| | | +|---|---| +| **Prinzip** | Prompt-CRUD nur Admin; Ausführung mit Capability + Feature-Kontingent. | +| **Begründung** | Systemkonfiguration vs. Nutzung; Kostenkontrolle. | +| **Quelle** | `ai_prompts_admin.py`; `exercises.ai.suggest` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Enforcement teils noch Probe-Phase. | + +### 8. Skill-Retrieval orthogonal zu Prompts + +| | | +|---|---| +| **Prinzip** | `ai_skill_retrieval_profiles` steuert Katalog für `{{skills_catalog}}` — unabhängig vom Prompt-Text. | +| **Begründung** | Anweisung vs. Kontextfenster trennbar konfigurierbar. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §3.3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +--- + +## Nicht übernehmen (Shinkan-Ist / Mitai-Vermeidung) + +1. **Direkte OpenRouter-Calls in Routern** ohne Laufzeit-Schicht — historische Schuld, abbauen. +2. **Hardcodierte Prompt-Strings in Produktion** — nur Fallback/Dev. +3. **Mitai-Workflow-Graph vorreifen** — Shinkan braucht erst Planungs-Kontext-Reife. +4. **Globale Platzhalter-Map ohne Namespace** — Mitai-Lektion `PLACEHOLDER_MAP`-Duplikat. +5. **Fehlende JSON-Schema-Validierung** bei `output_format=json` — Mitai-TODO übernehmen vermeiden. + +--- + +## Verwandte Dokumentation + +- [AI_PROMPT_TARGET_ARCHITECTURE.md](../../../.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md) +- [AI_PROMPT_SYSTEM_SPEC.md](../../../.claude/docs/technical/AI_PROMPT_SYSTEM_SPEC.md) +- [PLANNING_PROGRESSION_GRAPH_KI.md](../../architecture/PLANNING_PROGRESSION_GRAPH_KI.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..ca3b3ad --- /dev/null +++ b/docs/reference/design-principles/shinkan/AUTH_SESSION_DESIGN_PRINCIPLES.md @@ -0,0 +1,114 @@ +# Auth & Session – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Auth & Session“ — gemeinsame Mitai-Basis in Shinkan + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 4 von 15 +**Mitai-Vergleich:** [AUTH_SESSION_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/AUTH_SESSION_DESIGN_PRINCIPLES.md) (#5) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Auth-Kern | `backend/auth.py` | +| Router | `backend/routers/auth.py`, `profiles.py` | +| Frontend | `frontend/src/context/AuthContext.jsx`, `frontend/src/api/client.js` | +| Account-Lifecycle | `backend/account_lifecycle.py` | + +--- + +## Modul + +**Auth & Session** + +Token-basierte Server-Sessions (`sessions`-Tabelle), bcrypt-Passwörter, FastAPI-Dependencies `require_auth` / `require_admin`. Geteilter Code mit Mitai (App-Familie). + +--- + +## Fachliche Verantwortung + +1. **Login/Logout/Session-Lebensdauer** +2. **Passwort-Hashing** (bcrypt, Legacy-SHA256-Upgrade) +3. **Auth-Dependencies** für Router +4. **Account-States** (Verifizierung, Onboarding-Gates) + +--- + +## Designprinzipien + +### 1. Server-Side Sessions mit Token + +| | | +|---|---| +| **Prinzip** | `X-Auth-Token` Header → Lookup in `sessions` mit Ablaufzeit. | +| **Begründung** | Widerrufbar; kein JWT-Drift zwischen Apps. | +| **Quelle** | `auth.py` `get_session`, `require_auth` | +| **Tragfähigkeit** | **hoch** (Familien-Standard) | +| **Einschränkung** | Kein Refresh-Token-Rotation-Modell. | + +### 2. `require_auth` als separater Depends-Parameter + +| | | +|---|---| +| **Prinzip** | `session: dict = Depends(require_auth)` — nie in Header-Default eingebettet. | +| **Begründung** | Bekannter FastAPI-Footgun führt zu ungeschützten Endpoints. | +| **Quelle** | `CLAUDE.md` Kritische Regeln | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Code-Review/Lint erzwingt das nicht automatisch. | + +### 3. Profile-ID immer aus Session + +| | | +|---|---| +| **Prinzip** | `profile_id` aus `session['profile_id']`, nie aus Client-Header als Autorität. | +| **Begründung** | IDOR-Vermeidung. | +| **Quelle** | Architektur-Regeln; Shinkan ergänzt `TenantContext.profile_id` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Mitai-Dokument nennt Profile-Header-Schwäche — in Shinkan prüfen ob analog. | + +### 4. bcrypt für alle Passwort-Operationen + +| | | +|---|---| +| **Prinzip** | `hash_pin` / `verify_pin` mit bcrypt; SHA256 nur Legacy-Verify + Upgrade. | +| **Begründung** | Familien-konsistente Kryptografie. | +| **Quelle** | `auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Portal-Rollen vs. Vereinsrollen trennen + +| | | +|---|---| +| **Prinzip** | `profiles.role` (admin/superadmin/user) ≠ `club_member_roles` im Verein. | +| **Begründung** | Shinkan-Mandantenmodell; Plattform-Admin ≠ Vereins-Trainer. | +| **Quelle** | `club_tenancy.py`; `TenantContext.global_role` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI muss beide Ebenen korrekt anzeigen. | + +### 6. Account-Lifecycle als Capability-Voraussetzung + +| | | +|---|---| +| **Prinzip** | `min_account_state` auf Capabilities (z. B. verifiziertes Mitglied). | +| **Begründung** | Gates vor sensiblen Aktionen ohne Sonderchecks in Routern. | +| **Quelle** | `account_lifecycle.py`; `capabilities.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alle Flows nutzen Lifecycle einheitlich. | + +--- + +## Nicht übernehmen + +1. **Auth-Parameter in Header-Defaults vermischen** — dokumentierter Anti-Pattern. +2. **Client-gesteuerte `profile_id`** für Autorisierung. +3. **Shinkan-spezifische Mandantenlogik in `auth.py`** — gehört in `tenant_context.py`. + +--- + +## Verwandte Dokumentation + +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- Mitai: [AUTH_SESSION_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/AUTH_SESSION_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..0ea9728 --- /dev/null +++ b/docs/reference/design-principles/shinkan/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md @@ -0,0 +1,125 @@ +# Capability & Club Features – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Capabilities & Vereins-Feature-Kontingente“ — nicht Billing/Stripe + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 2 von 15 +**Mitai-Vergleich:** [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) (#3) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Capabilities | `backend/capabilities.py` | +| Vereins-Features | `backend/club_features.py` | +| Entitlements-API | `backend/entitlements.py`, `backend/routers/me_entitlements.py` | +| Quota-Bypass | `backend/club_quota_bypass.py` | +| Spez | `CAPABILITY_CATALOG.v1.md`, `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | + +--- + +## Modul + +**Capability & Club Feature Entitlements** + +Zwei Schichten: **Capabilities** (darf Nutzer X im Verein Y?) und **Club Features** (Kontingente/Limits pro Verein, Subjekt `club_id`). Zusammenführung in `GET /api/me/entitlements`. + +--- + +## Fachliche Verantwortung + +1. **Capability-Checks** — `check_capability`, `probe_capability`, `require_capability` mit Env `CAPABILITY_ENFORCE`. +2. **Vereins-Kontingente** — `probe_club_feature_access`, `consume_club_feature_with_usage` mit Env `CLUB_FEATURE_ENFORCE`. +3. **Entitlements-Snapshot** — Frontend erhält `capabilities` + `features` + Plan für UI-Gating ohne Tier-Logik in Widgets. +4. **Account-Lifecycle** — `min_account_state` blockiert Capabilities vor Verifizierung. + +### Unterschied zu Mitai + +| Aspekt | Mitai | Shinkan | +|--------|-------|---------| +| Limit-Subjekt | Profil / Subscription | **Verein** (`club_id`) | +| Rollen | Tier + Features | Vereinsrollen + Portal-Rolle | +| Legacy | `check_feature_access` (001) | Explizit **nicht** für Shinkan-Limits nutzen | + +--- + +## Designprinzipien + +### 1. Eine Entitlements-API für das Frontend + +| | | +|---|---| +| **Prinzip** | `GET /api/me/entitlements?club_id=` liefert Capabilities-Map + Feature-Kontingente + Plan. | +| **Begründung** | Keine Tier-Logik in React-Komponenten; ein Roundtrip pro Mandantenwechsel. | +| **Quelle** | `entitlements.py`, `me_entitlements.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle UI-Stellen nutzen Entitlements konsequent. | + +### 2. 4-Phasen-Rollout (Probe → Enforce) + +| | | +|---|---| +| **Prinzip** | Phase 2: JSON-Log ohne Block; Phase 3+: `CAPABILITY_ENFORCE=1` / `CLUB_FEATURE_ENFORCE=1` → HTTP 403. | +| **Begründung** | Sicheres Einführen ohne Produktions-Crash; Audit vor Hard-Block. | +| **Quelle** | `capabilities.py`, `club_features.py`; Mitai-Vorbild in Spec | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Env-Flags müssen pro Umgebung bewusst gesetzt werden. | + +### 3. Capabilities verknüpft mit Features + +| | | +|---|---| +| **Prinzip** | Capability kann `linked_feature_id` haben — Kontingent-Check vor Ausführung. | +| **Begründung** | Recht und Limit bleiben getrennt modelliert aber gemeinsam enforcebar. | +| **Quelle** | `capabilities`-Tabelle; `check_capability` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht jede Capability hat linked Feature. | + +### 4. Enforcement an der API, nicht in der UI + +| | | +|---|---| +| **Prinzip** | Router rufen `require_capability` / `probe_club_feature_access` — UI blendet nur vor. | +| **Begründung** | API ist Source of Truth; UI-Gating allein ist umgehbar. | +| **Quelle** | `exercise_ai.py`, Planungs-KI-Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Teilweise noch Probe-only in Prod. | + +### 5. Bestands-Features als Live-Zählung + +| | | +|---|---| +| **Prinzip** | Inventar-Features (`exercises`, `training_groups`, …) zählen live in DB, nicht nur `club_feature_usage`. | +| **Begründung** | Keine Drift zwischen tatsächlichem Bestand und Usage-Tabelle. | +| **Quelle** | `_INVENTORY_FEATURES` in `club_features.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Performance bei großen Vereinen — ggf. Cache nötig. | + +### 6. Quota-Bypass für Plattform-Rollen + +| | | +|---|---| +| **Prinzip** | Konfigurierbare Bypass-Capabilities (`domain=quota_bypass`) für Support/Admin ohne harte Limits. | +| **Begründung** | Betrieb und Demos ohne Plan-Upgrade. | +| **Quelle** | `club_quota_bypass.py`; `entitlements.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Missbrauchsrisiko — nur dokumentierte Grants. | + +--- + +## Nicht übernehmen + +1. **Mitai `auth.check_feature_access` für Shinkan-Vereinslimits** — profil-zentriert, falscher Subjekt-Scope. +2. **Tier-Logik in Frontend-Widgets** — gehört in Entitlements-Response. +3. **Enforcement ohne Probe-Phase** — bricht bestehende Vereine ohne Vorwarnung. +4. **Capabilities ohne DB-Sync aus Registry** — siehe Rights-Registry-Dokument. + +--- + +## Verwandte Dokumentation + +- [CAPABILITY_CATALOG.v1.md](../../../.claude/docs/technical/CAPABILITY_CATALOG.v1.md) +- [CLUB_MEMBERSHIP_AND_FEATURES.v1.md](../../../.claude/docs/technical/CLUB_MEMBERSHIP_AND_FEATURES.v1.md) +- [RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md](./RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/CONTENT_REPORTS_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/CONTENT_REPORTS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..0441592 --- /dev/null +++ b/docs/reference/design-principles/shinkan/CONTENT_REPORTS_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Content Reports (P-13) – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Content Reports“ — Meldeverfahren, Posteingang, Legal Hold + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 10 von 15 +**Mitai-Vergleich:** — (Compliance-spezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/content_reports.py` | +| Legal Hold | `backend/media_legal_hold.py` | +| Inbox-Integration | `GET /api/me/inbox/content-reports` | +| Frontend | `InboxPage.jsx` | +| Migration | 052, 053 | + +--- + +## Modul + +**Content Reports & Compliance (P-13)** + +Meldeverfahren für problematische Inhalte (Medien, Übungen), Admin-Posteingang mit Statusworkflow, Priorisierung sensibler Gründe, Anbindung Legal Hold und Medien-Audit. + +--- + +## Designprinzipien + +### 1. Eine Tabelle, ein Workflow — keine separate Admin-Queue + +| | | +|---|---| +| **Prinzip** | `content_reports` + bestehende Inbox-UI für Änderungsanfragen und Meldungen. | +| **Begründung** | Admin-Arbeit an einem Ort; weniger Navigations-Fragmentierung. | +| **Quelle** | `content_reports.py` Docstring | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Zwei Vorgangstypen in einer UI — klare Typ-Kennzeichnung nötig. | + +### 2. Melden optional ohne Auth (eingeschränkt) + +| | | +|---|---| +| **Prinzip** | Anonym/offline Meldung für `official`-Medien erlaubt; sonst Auth empfohlen. | +| **Begründung** | DSA-Anforderungen; öffentliche Plattform-Inhalte meldbar. | +| **Quelle** | Router-Berechtigungen | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Missbrauchsschutz (Rate Limit) prüfen. | + +### 3. Priorität bei sensiblen Gründen + +| | | +|---|---| +| **Prinzip** | `minors`, `illegal_content`, `youth_protection` → HIGH_PRIORITY automatisch. | +| **Begründung** | SLA und Admin-Aufmerksamkeit fachlich korrekt. | +| **Quelle** | `HIGH_PRIORITY_REASONS` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Rollengetrennte Sicht (Plattform vs. Verein) + +| | | +|---|---| +| **Prinzip** | Plattform-Admin: alle; Club-Admin: nur Vereinsmedien-Meldungen. | +| **Begründung** | Mandanten-Grenze auch im Compliance-Kontext. | +| **Quelle** | Router-Listenfilter | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Legal Hold nur Superadmin aus Meldung + +| | | +|---|---| +| **Prinzip** | Mapping `report_reason` → `legal_hold reason_code`; `set_legal_hold` superadmin-geschützt. | +| **Begründung** | Hochrisiko-Aktion; Anschluss P-11. | +| **Quelle** | `_REASON_TO_HOLD_CODE`; `media_legal_hold.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Audit-Spur bei Medien-Meldungen + +| | | +|---|---| +| **Prinzip** | `media_asset_audit_log` Event `content_report_filed` bei media_asset-Bezug. | +| **Begründung** | Lifecycle-Entscheidungen nachvollziehbar. | +| **Quelle** | Migration 053; `write_audit_log_entry` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 7. E-Mail best-effort, kein Hard-Fail + +| | | +|---|---| +| **Prinzip** | Bestätigung an Melder + Admin-Benachrichtigung; SMTP-Fehler blockieren Speichern nicht. | +| **Begründung** | Meldung geht nicht verloren wenn Mail down. | +| **Quelle** | Router-Implementierung | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Admins müssen Posteingang auch ohne Mail prüfen. | + +--- + +## Nicht übernehmen + +1. **Separate Compliance-Queue-App** — Inbox-Reuse ist bewusst. +2. **Legal Hold durch Club-Admin** — Superadmin-only. +3. **Meldungen ohne Statusworkflow/Archiv** — Wiedereröffnen muss möglich sein. +4. **Fehlende Verknüpfung Medien-Lifecycle** — Hold muss Purge blockieren. + +--- + +## Verwandte Dokumentation + +- [MEDIA_ASSETS_DESIGN_PRINCIPLES.md](./MEDIA_ASSETS_DESIGN_PRINCIPLES.md) +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/DASHBOARD_KPI_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/DASHBOARD_KPI_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..cbe47ff --- /dev/null +++ b/docs/reference/design-principles/shinkan/DASHBOARD_KPI_DESIGN_PRINCIPLES.md @@ -0,0 +1,105 @@ +# Dashboard KPIs – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Dashboard KPI Aggregation“ — vereinfacht vs. Mitai Widgets + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 14 von 15 +**Mitai-Vergleich:** [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) (#7, vereinfacht) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/dashboard.py` — `GET /api/dashboard/kpis` | +| Frontend | Dashboard/Übersicht-Page | +| Refaktor-Kontext | `docs/architecture/SCHULDEN_UND_REMEDIATION.md` A3, B1 | + +--- + +## Modul + +**Dashboard KPI Aggregation** + +Ein Backend-Roundtrip liefert Übungs-KPIs, YTD-Einheiten, Trainings-Home (nächste Termine, Vermerke, offene Rückschau) — Ersatz für mehrere parallele Client-Listen-Calls. + +--- + +## Designprinzipien + +### 1. Aggregierter Endpoint statt Chatty Client + +| | | +|---|---| +| **Prinzip** | `GET /dashboard/kpis` ruft intern `list_exercises_like_get` + `list_training_units` mit gleichen Filtern wie zuvor im UI. | +| **Begründung** | Weniger Latenz; eine TenantContext-Auflösung; Refaktor Phase 1 Dashboard. | +| **Quelle** | `dashboard.py`; SCHULDEN A3/B1 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Noch keine konfigurierbaren Widgets wie Mitai. | + +### 2. Gleiche Filtersemantik wie Einzel-Endpoints + +| | | +|---|---| +| **Prinzip** | KPI-Zählungen nutzen dieselben Helfer wie `/exercises` und `/training-units` — keine zweite Query-Logik. | +| **Begründung** | Zahlen auf Dashboard = Zahlen in Fachmodulen. | +| **Quelle** | Import aus `exercises`, `training_planning` Routern | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Interne Funktionsaufrufe statt HTTP — Kopplung an Router-Helfer. | + +### 3. TenantContext für Mandanten-KPIs + +| | | +|---|---| +| **Prinzip** | `Depends(get_tenant_context)` — assigned_to_me, created_by_me respektieren Verein/Rolle. | +| **Begründung** | Keine globalen KPIs für Trainer fremder Vereine. | +| **Quelle** | `get_dashboard_kpis` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Festes Dashboard-Layout (kein Widget-Katalog) + +| | | +|---|---| +| **Prinzip** | Shinkan-Übersicht zeigt definierte Kacheln — nicht nutzerkonfigurierbares Layout-JSON. | +| **Begründung** | MVP-Fokus Trainer-Verein; weniger Komplexität als Mitai. | +| **Quelle** | Produktentscheidung vs. Mitai #7 | +| **Tragfähigkeit** | **mittel** (bewusste Vereinfachung) | +| **Einschränkung** | Erweiterung braucht Backend+Frontend-Change, nicht Admin-Config. | + +### 5. Profil nicht redundant laden + +| | | +|---|---| +| **Prinzip** | Dashboard soll Auth-Profil nutzen — kein zweites `/profiles/me` nach Login+Reload (E2E Test 8). | +| **Begründung** | Architekturschuld A3 explizit adressiert. | +| **Quelle** | `tests/dev-smoke-test.spec.js`; Roadmap | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Umsetzung muss mitziehen. | + +### 6. Slice-Logik für Trainings-Home im Backend + +| | | +|---|---| +| **Prinzip** | `_slice_training_home_notes` filtert Einheiten mit Vermerken — max. N Stück serverseitig. | +| **Begründung** | Kleiner Payload; klare Semantik. | +| **Quelle** | `dashboard.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Grenzwert hardcoded — ggf. Query-Param später. | + +--- + +## Nicht übernehmen + +1. **Drei parallele fast gleiche `listTrainingUnits`-Calls** im Client — behoben durch KPI-Endpoint. +2. **Mitai Widget-Dual-Registry vorreifen** ohne Produktbedarf — Over-Engineering für Shinkan MVP. +3. **KPI-Berechnung im Frontend** aus Volllisten — skaliert nicht. +4. **Dashboard ohne Tenant-Filter** — Mandanten-Leak. + +--- + +## Verwandte Dokumentation + +- [docs/architecture/SCHULDEN_UND_REMEDIATION.md](../../architecture/SCHULDEN_UND_REMEDIATION.md) +- Mitai: [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/DESIGN_PRINCIPLES_INDEX.md b/docs/reference/design-principles/shinkan/DESIGN_PRINCIPLES_INDEX.md new file mode 100644 index 0000000..8fcac95 --- /dev/null +++ b/docs/reference/design-principles/shinkan/DESIGN_PRINCIPLES_INDEX.md @@ -0,0 +1,140 @@ +# Designprinzipien – Index (Jinkendo Produktfamilie) + +**Status:** Arbeitspapier / Übergabe +**Stand:** 2026-07-04 +**Zweck:** Zentraler Einstieg für **tragfähige Designprinzipien** der Jinkendo-Produktfamilie — Shinkan-Serie (15 Module), Abgleich mit Mitai (9 Module), Basis für Schwester-Apps. + +**Nicht enthalten:** App-Gesamtarchitektur, domänenspezifische Fachlogik im Detail, vollständige API-Referenz. + +**Ablage (Shinkan-Serie):** `docs/jinkendo-family/design-principles/*_DESIGN_PRINCIPLES.md` +**Übergeordnet:** [docs/jinkendo-family/README.md](../README.md) +**Mitai-Serie (vorläufig):** `mitai-jinkendo/.claude/docs/technical/DESIGN_PRINCIPLES_*.md` + +--- + +## Wofür diese Serie? + +Shinkan implementiert wiederkehrende **Querschnittsmuster** (Mandanten-Zugriff, Capabilities, Medien-Archiv, Planungsdomäne, KI-Laufzeit, Import, Navigation, Deploy) sowie **domänenspezifische Bausteine** (Übungskatalog, Fähigkeiten-Scoring, Trainingsplanung, Compliance-Meldungen). Die 15 Dokumente destillieren daraus: + +- **Was** übertragbar ist (Prinzip + Begründung + Tragfähigkeit) +- **Was** bewusst nicht kopiert werden soll („Nicht übernehmen“) +- **Wo** im Code nachgeschaut werden kann (Pfade, Specs) +- **Mitai-Abgleich** — welches Schwester-Dokument vergleichbar ist + +Jedes Dokument ist **eigenständig lesbar**; dieser Index ordnet Abhängigkeiten, Lese-Reihenfolge und den geplanten Familien-Review. + +--- + +## Dokumente (15/15) + +| # | Modul | Datei | Kernidee (1 Satz) | Mitai-Vergleich | +|---|-------|-------|-------------------|-----------------| +| 1 | Access Layer & Tenant | [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) | Ein `TenantContext` pro Request; einheitliche `visibility`/`club_id`-Semantik für Bibliotheksartefakte. | — (Shinkan-spezifisch) | +| 2 | Capability & Club Features | [CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) | Capabilities + Vereins-Kontingente; `GET /me/entitlements`; 4-Phasen-Rollout mit Env-Flags. | #3 Feature & Entitlement | +| 3 | Rights Registry | [RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md](./RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md) | Module registrieren Capabilities/Features bei Startup — kein vollständiger Vorab-Katalog in Migrationen. | #4 Registry / Plugin | +| 4 | Auth & Session | [AUTH_SESSION_DESIGN_PRINCIPLES.md](./AUTH_SESSION_DESIGN_PRINCIPLES.md) | Server-Sessions, `require_auth` als Depends; gemeinsame Mitai-Basis. | #5 Auth & Session | +| 5 | AI Prompt Runtime | [AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md](./AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md) | Schmale Laufzeit (`ai_prompt_runtime`); DB-Templates + Mustache; Kontext-Arten pro Domäne. | #1 Prompt Engine | +| 6 | Media Assets & Archiv | [MEDIA_ASSETS_DESIGN_PRINCIPLES.md](./MEDIA_ASSETS_DESIGN_PRINCIPLES.md) | Physisches Asset einmal, mehrfach verknüpft; Lifecycle, Legal Hold, Inline-Rich-Text. | — | +| 7 | Exercise Catalog | [EXERCISE_CATALOG_DESIGN_PRINCIPLES.md](./EXERCISE_CATALOG_DESIGN_PRINCIPLES.md) | Übung als Kernobjekt; Varianten, Governance, Progressionsgraph, Kombinationsübungen. | — | +| 8 | Skill Scoring | [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) | Regelbasiertes gewichtetes Profil; Peer-Vergleich nur unter gleichem Artefakttyp. | #2 Data Layer (teilweise) | +| 9 | Training Planning | [TRAINING_PLANNING_DESIGN_PRINCIPLES.md](./TRAINING_PLANNING_DESIGN_PRINCIPLES.md) | Einheiten mit Phasen/Streams; Rahmen-Bibliothek + Module; Coach/Durchführung getrennt. | — | +| 10 | Content Reports (P-13) | [CONTENT_REPORTS_DESIGN_PRINCIPLES.md](./CONTENT_REPORTS_DESIGN_PRINCIPLES.md) | Melde-Workflow in Posteingang; Priorität sensibler Gründe; Legal-Hold-Anschluss. | — | +| 11 | Wiki Import | [WIKI_IMPORT_DESIGN_PRINCIPLES.md](./WIKI_IMPORT_DESIGN_PRINCIPLES.md) | SMW-API-Ingest + Mapper; Preview/Dry-Run; Duplikat-Tracking — kein Raw-Wiki in DB. | #6 Universal Import | +| 12 | Navigation / IA | [NAVIGATION_IA_DESIGN_PRINCIPLES.md](./NAVIGATION_IA_DESIGN_PRINCIPLES.md) | `appNav.js` als SSoT; Admin-Hub horizontal; Onboarding-Nav ohne Verein. | #8 Navigation / IA | +| 13 | Migration & Deploy | [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](./MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) | `XXX_*.sql` beim Container-Start; develop/main → Dev/Prod; fail-fast. | #9 Migration & Deploy | +| 14 | Dashboard KPIs | [DASHBOARD_KPI_DESIGN_PRINCIPLES.md](./DASHBOARD_KPI_DESIGN_PRINCIPLES.md) | Aggregierter `/dashboard/kpis`-Roundtrip statt mehrerer Listen-Calls. | #7 Dashboard Widgets (vereinfacht) | +| 15 | Maturity Models | [MATURITY_MODELS_DESIGN_PRINCIPLES.md](./MATURITY_MODELS_DESIGN_PRINCIPLES.md) | Kontextsensitive Matrix-Auflösung; Export/Import-Stack für Admin-Portabilität. | — | + +--- + +## Empfohlene Lesereihenfolge + +### Schnellüberblick (45 Min) + +1. [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) — Shinkan-Kernunterscheidung zu Mitai +2. [RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md](./RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md) — Meta-Muster für Erweiterbarkeit +3. [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](./MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) — Familien-Basis + +### Vollständige Implementierung (neues Produkt) + +``` +Foundation: (13) Migration & Deploy → (4) Auth → (1) Access Layer → (2) Capabilities → (3) Registry +Domäne: (7) Exercise Catalog → (6) Media → (9) Training Planning → (8) Skill Scoring +Erweiterung: (5) AI Prompt Runtime → (11) Wiki Import → (15) Maturity Models +Compliance: (10) Content Reports +Oberfläche: (12) Navigation → (14) Dashboard KPIs +``` + +### Nur Familien-Review (Mitai ↔ Shinkan) + +| Mitai-Dokument | Shinkan-Gegenstück | Review-Fokus | +|----------------|-------------------|--------------| +| #1 Prompt Engine | #5 AI Prompt Runtime | Executor-Reife, Registry, Workflows | +| #2 Data Layer | #8 Skill Scoring | Berechnungs-SSoT vs. Router-Duplikat | +| #3 Feature & Entitlement | #2 Capability & Club Features | Subjekt: Profil vs. Verein | +| #4 Registry | #3 Rights Registry | Registrierungsmuster | +| #5 Auth | #4 Auth & Session | Gemeinsamer Code, IDOR-Risiken | +| #6 Universal Import | #11 Wiki Import | Ingest ≠ Interpretation | +| #7 Dashboard Widgets | #14 Dashboard KPIs | Konfigurierbarkeit vs. Aggregation | +| #8 Navigation | #12 Navigation | appNav-Pattern | +| #9 Migration & Deploy | #13 Migration & Deploy | Gleiches Startup-Muster | + +--- + +## Querschnittsthemen (über alle Docs) + +| Thema | Primär | Ergänzend | +|-------|--------|-----------| +| Mandanten-Isolation (`club_id`) | #1 Access Layer | #6 Media, #7 Exercise, #9 Planning | +| Sichtbarkeit `private`/`club`/`official` | #1 Access Layer | #6 Media, #7 Exercise | +| Capability-Gating | #2 Entitlement | #3 Registry, #5 AI Runtime | +| Validierung an der Grenze | #3 Registry | #6 Inline-Media, #11 Import-Mapper | +| Single Source of Truth (Berechnung) | #8 Skill Scoring | #5 AI Kontext-Builder | +| Dual Registry (Code + DB) | #3 Rights Registry | #2 Capabilities in DB | +| Bekannte Lücken dokumentieren | alle „Nicht übernehmen“ | Endpoint-Audit, Architekturschuld | + +--- + +## Verwandte normative Docs (Shinkan-spezifisch) + +| Thema | Agent-Guide / Spec | +|-------|-------------------| +| Zugriffsschicht | [ACCESS_LAYER_AND_GOVERNANCE_PLAN.md](../../../.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md), [ACCESS_LAYER_ENDPOINT_AUDIT.md](../../../.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md) | +| Capabilities | [CAPABILITY_CATALOG.v1.md](../../../.claude/docs/technical/CAPABILITY_CATALOG.v1.md) | +| Vereins-Features | [CLUB_MEMBERSHIP_AND_FEATURES.v1.md](../../../.claude/docs/technical/CLUB_MEMBERSHIP_AND_FEATURES.v1.md) | +| Medien | [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../../../.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) | +| KI-Zielbild | [AI_PROMPT_TARGET_ARCHITECTURE.md](../../../.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md) | +| Planung Streams | [PARALLEL_TRAINING_STREAMS_SPEC.md](../../../.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md) | +| Skill Scoring | [SKILL_SCORING_SPEC.md](../../../.claude/docs/technical/SKILL_SCORING_SPEC.md) | +| Architektur-Schuld | [docs/architecture/SCHULDEN_UND_REMEDIATION.md](../../architecture/SCHULDEN_UND_REMEDIATION.md) | + +--- + +## Übergabe-Checkliste (Familien-Review) + +``` +[ ] Pro Modul: Prinzipien vs. Mitai-Gegenstück abgleichen +[ ] Architekturschuld pro Modul in SCHULDEN_UND_REMEDIATION / „Nicht übernehmen“ verknüpfen +[ ] Gemeinsame Familien-Prinzipien aus Übereinstimmungen ableiten +[ ] Abweichungen bewusst dokumentieren (z. B. Vereins- vs. Profil-Entitlements) +[ ] Shared Code (auth.py, db_init) — eine Quelle oder Fork-Drift? +``` + +--- + +## Pflege + +| Aktion | Wo | +|--------|-----| +| Neues Querschnittsmodul extrahiert | Neues `*_DESIGN_PRINCIPLES.md` + Zeile in Tabelle oben | +| Shinkan-Implementierung ändert Muster | Betroffenes Einzeldokument + ggf. Querschnittstabelle | +| Mitai-Review abgeschlossen | Abschnitt „Familien-Prinzipien“ (separates Doc, Backlog) | + +--- + +## Changelog Index + +| Datum | Änderung | +|-------|----------| +| 2026-07-04 | Shinkan-Serie nach `docs/jinkendo-family/design-principles/` verschoben (Familien-Foundation) | +| 2026-07-04 | Index angelegt; Serie 1–15 aus Shinkan-Ist-Stand extrahiert | diff --git a/docs/reference/design-principles/shinkan/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..96f2e7d --- /dev/null +++ b/docs/reference/design-principles/shinkan/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md @@ -0,0 +1,128 @@ +# Exercise Catalog – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Exercise Catalog“ — Kernobjekt Übung, Varianten, Graphen, Kombination + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 7 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/exercises.py`, `exercise_progression_graphs.py` | +| Rich-Text | `backend/exercise_rich_text.py` | +| KI | `backend/exercise_ai.py` | +| Frontend | `frontend/src/pages/Exercises*.jsx`, Tab-Formular | +| Specs | `EXERCISES_ARCHITECTURE.md`, `EXERCISES_API_SPEC.md`, Kombinations-Spec | + +--- + +## Modul + +**Exercise Catalog** + +Shinkans Kernobjekt: Übungen mit mehrdimensionaler Einordnung (Skills, Fokus, Stile), Varianten, Progressionsgraphen, Kombinationsübungen (`method_archetype`, Stationen), Governance und Medien-Anbindung. + +--- + +## Designprinzipien + +### 1. Übung als zentrales Aggregate Root + +| | | +|---|---| +| **Prinzip** | Varianten, Medien, Skills, Graph-Knoten hängen an `exercises.id` — Owner/Governance auf Eltern-Übung. | +| **Begründung** | Eine Freigabe- und Lösch-Semantik; Varianten ohne eigenen Owner. | +| **Quelle** | `FACHLICHE_NUTZERFUNKTIONEN.md` §4.1, §4.7 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Monolith-Router/Seiten — Refaktor-Roadmap. | + +### 2. Tab-Formular statt Scroll-Monolith + +| | | +|---|---| +| **Prinzip** | Register: Stammdaten · Anleitung · Einordnung · Kombination · Varianten · Medien — Varianten/Medien erst nach erstem Save. | +| **Begründung** | UX für komplexe Objekte; klare Abhängigkeiten (IDs für Medien/Varianten). | +| **Quelle** | Nutzerfunktionen §4.1 | +| **Tragfähigkeit** | **hoch** (UI-Muster) | +| **Einschränkung** | Frontend-God-Page-Schuld dokumentiert. | + +### 3. Mehrdimensionale Filter-SSoT + +| | | +|---|---| +| **Prinzip** | Suche/Filter über Skills, Fokus, Stil, Zielgruppe, Status, Freigabelevel — Backend-Query + gespeicherte Präferenzen. | +| **Begründung** | Trainer finden Inhalte in großen Vereins-Katalogen. | +| **Quelle** | `SEARCH_FILTER_SPEC.md`; `exercises.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Performance schwere Listen — Baseline/Roadmap. | + +### 4. Varianten mit Voraussetzungskette + +| | | +|---|---| +| **Prinzip** | `exercise_variants` mit Reihenfolge, optional `prerequisite_variant_id`. | +| **Begründung** | Didaktische Abstufung innerhalb einer Übung. | +| **Quelle** | Migration 030; Planung nutzt Varianten-ID pro Eintrag | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Progressionsgraph als gerichtete Übungs-Beziehungen + +| | | +|---|---| +| **Prinzip** | Knoten = Übungen/Varianten; Kanten = „weiter“-Beziehungen; eigener Router + UI in Übungswelt. | +| **Begründung** | Didaktik und Planungs-KI nutzen denselben Graph. | +| **Quelle** | `exercise_progression_graphs.py`; `PLANNING_PROGRESSION_GRAPH_KI.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Graph-Editor-Komplexität; KI-Artefakte separat. | + +### 6. Kombinationsübungen als Sonderform im gleichen Katalog + +| | | +|---|---| +| **Prinzip** | `exercise_type=combination` mit Stationen, `method_archetype`, optionalem `method_profile`. | +| **Begründung** | In Planung wie normale Übung; Coach zeigt Stations-Layer. | +| **Quelle** | Migration 056/057; Kombinations-Spec V2 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Archetyp-Stufen B/C noch ausbaubar. | + +### 7. Governance integriert (nicht separates CMS) + +| | | +|---|---| +| **Prinzip** | `visibility`, `status` (draft/review/…), Access-Layer-Lösch/Transition-Regeln. | +| **Begründung** | Trainer-Workflow ohne externes Freigabe-Tool. | +| **Quelle** | `club_tenancy.py`; Content Change Requests → Posteingang | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Formales Review-Workflow noch leichtgewichtig. | + +### 8. Rich-Text-Felder mit Inline-Medien + +| | | +|---|---| +| **Prinzip** | Einheitliche Platzhalter/Render für `summary`, `goal`, `execution`, … | +| **Begründung** | Medienreicher Inhalt ohne iframe-Split. | +| **Quelle** | `exercise_rich_text.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +--- + +## Nicht übernehmen + +1. **Varianten mit eigenem Owner/Freigabe** — widerspricht Domänenmodell. +2. **Übungsliste ohne Tenant-Filter** — Access-Layer-Pflicht. +3. **KI-generierte Übungen ohne Governance-Felder** — immer draft/private Default. +4. **Progressionsgraph-Logik im Frontend allein** — Server validiert Kanten. + +--- + +## Verwandte Dokumentation + +- [EXERCISES_ARCHITECTURE.md](../../../.claude/docs/technical/EXERCISES_ARCHITECTURE.md) +- [MEDIA_ASSETS_DESIGN_PRINCIPLES.md](./MEDIA_ASSETS_DESIGN_PRINCIPLES.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/MATURITY_MODELS_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/MATURITY_MODELS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..1ea4c1b --- /dev/null +++ b/docs/reference/design-principles/shinkan/MATURITY_MODELS_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Maturity Models – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Maturity Models / Fähigkeitsmatrix“ — kontextsensitive Auflösung + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 15 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/maturity_models.py`, `matrix_editor.py`, `matrix_stack_bundle.py` | +| Admin-UI | `/admin/maturity-models` | +| Import | Wiki-Import Typ Modelle; Matrix-Stack Export/Import | +| Spec | `.claude/docs/technical/SKILLS_MATRIX_SPEC.md` | + +--- + +## Modul + +**Maturity Models & Matrix Stack** + +Matrixbasierte Reifegradmodelle mit Stufen und Zelltexten; kontextsensitive Auflösung über Bindings (Fokusbereich, Stilrichtung, Zielgruppe); Admin-Export/Import einzelner Modelle und Komplett-Stack. + +--- + +## Designprinzipien + +### 1. Kontext-Bindings M:N (leer = überall) + +| | | +|---|---| +| **Prinzip** | Modell verknüpft mit Fokus/Stil/Zielgruppe; leere Bindings = global gültig. | +| **Begründung** | Ein Stack deckt mehrere Trainingskontexte ab. | +| **Quelle** | `maturity_models.py` `_attach_context` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Auflösungs-Priorität bei mehreren Treffern dokumentieren. | + +### 2. Resolve-API für Laufzeit-Nutzung + +| | | +|---|---| +| **Prinzip** | Authentifizierte Nutzer listen/auflösen; Admin-only für Roh-ID-GET in Admin-UI. | +| **Begründung** | Trainer sehen passende Matrix; Rohdaten-Edit geschützt. | +| **Quelle** | Router-Docstring; Rollen-Checks | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 3. Matrix-Editor als separates Admin-Tool + +| | | +|---|---| +| **Prinzip** | `matrix_editor` Router für Zellbearbeitung — nicht im Trainer-Flow. | +| **Begründung** | Komplexe UI; Plattform-Redaktionsaufgabe. | +| **Quelle** | Admin-Nav „Fähigkeitsmatrix“ | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Komplexität — eigene Schuld-Kategorie. | + +### 4. Stack-Bundle Export/Import + +| | | +|---|---| +| **Prinzip** | `matrix_stack_bundle` — Komplett-Stack zwischen Umgebungen (Dev→Prod, Backup). | +| **Begründung** | Analog Prompt Import/Export — Konfiguration versionierbar außerhalb DB. | +| **Quelle** | Admin-Werkzeuge; Wiki-Import ergänzt | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Kein Diff/Merge wie Mitai Prompt-Import. | + +### 5. Plattform-Admin-Schreibschutz + +| | | +|---|---| +| **Prinzip** | Schreiben nur `admin`/`superadmin`; Lesen breiter für authentifizierte Nutzer (Resolve). | +| **Begründung** | Offizielle Kompetenzrahmen zentral gepflegt. | +| **Quelle** | `_require_admin` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Integration Wiki-Import für Modelle + +| | | +|---|---| +| **Prinzip** | SMW-Kategorie Modelle → Import-Pfad neben Übungen/Skills. | +| **Begründung** | Bestehende Wissensbasis karatetrainer.net nutzen. | +| **Quelle** | `import_wiki.py` `CATEGORY_MODELS` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Gap-Analyse SMW — nicht alle Wiki-Felder gemappt. | + +### 7. Orthogonal zu Skill Scoring + +| | | +|---|---| +| **Prinzip** | Matrix = beschreibende Stufen; Skill Scoring = gewichtete Übungs-Aggregation — getrennte Module. | +| **Begründung** | Keine Vermischung von Kompetenz-Raster und Trainings-KPI. | +| **Quelle** | Domänen-Trennung in Specs | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI kann beides nebenan zeigen — klare Labels nötig. | + +--- + +## Nicht übernehmen + +1. **Matrix-Zellen in Übungs-Score-Formel mischen** — ohne fachliche Spec. +2. **Trainer-Edit globaler offizieller Matrizen** — Admin-only. +3. **Import ohne Stack-Integrität** — Bundle-Validierung beachten. +4. **Resolve ohne Kontext-Parameter** wenn Mehrdeutigkeit — falsche Matrix. + +--- + +## Verwandte Dokumentation + +- [SKILLS_MATRIX_SPEC.md](../../../.claude/docs/technical/SKILLS_MATRIX_SPEC.md) +- [WIKI_IMPORT_DESIGN_PRINCIPLES.md](./WIKI_IMPORT_DESIGN_PRINCIPLES.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/MEDIA_ASSETS_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/MEDIA_ASSETS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..7e0f185 --- /dev/null +++ b/docs/reference/design-principles/shinkan/MEDIA_ASSETS_DESIGN_PRINCIPLES.md @@ -0,0 +1,119 @@ +# Media Assets & Archiv – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Media Assets & Archiv“ — physische Medien, Lifecycle, Inline + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 6 von 15 +**Mitai-Vergleich:** — (Mitai hat kein vergleichbares Medien-Archiv) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/media_assets.py`, `platform_media_storage.py` | +| Speicher | `backend/media_storage.py`, `MEDIA_ROOT` | +| Rechte/Audit | `backend/media_rights.py`, `media_legal_hold.py` | +| Inline Rich-Text | `backend/exercise_rich_text.py` | +| Retention-Job | `backend/scripts/media_retention_job.py` | +| Spec | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | + +--- + +## Modul + +**Media Assets & Archiv** + +Zentrale Verwaltung physischer Dateien (`media_assets`), Verknüpfung zu Übungen (`exercise_media`), mehrstufiger Lifecycle (Papierkorb, Legal Hold), Inline-Einbettung in Rich-Text. + +--- + +## Designprinzipien + +### 1. Physisches Asset einmal, mehrfach verknüpft + +| | | +|---|---| +| **Prinzip** | Datei in `media_assets`; Übungen referenzieren via `exercise_media.media_asset_id`. | +| **Begründung** | Keine Dubletten auf Platte; Wiederverwendung im Archiv. | +| **Quelle** | `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §1 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Pfade ohne Asset-ID können noch existieren. | + +### 2. Gleiche Visibility-Semantik wie Übungen + +| | | +|---|---| +| **Prinzip** | `private`/`club`/`official` + `club_id` — Access Layer für Download und Liste. | +| **Begründung** | Ein Freigabemodell für alle Bibliotheksartefakte. | +| **Quelle** | Spec §4; `media_rights.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Promotion Übung→official muss Assets mithheben — UI-Dialog Pflicht. | + +### 3. Lifecycle getrennt von Übungs-Verknüpfung + +| | | +|---|---| +| **Prinzip** | Verknüpfung in Übung lösen ≠ Asset physisch löschen; Papierkorb-Stufen separat. | +| **Begründung** | Trainer dürfen Link entfernen ohne Archiv-Löschrecht. | +| **Quelle** | Spec §5 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Retention-Job muss in Betrieb überwacht werden. | + +### 4. Legal Hold blockiert automatisierten Lifecycle + +| | | +|---|---| +| **Prinzip** | `legal_hold` schützt Asset vor Purge; Anbindung an Content Reports (Superadmin). | +| **Begründung** | Compliance bei Meldungen (P-11/P-13). | +| **Quelle** | `media_legal_hold.py`; `content_reports.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Inline-Medien: kanonisches Markup + Validierung + +| | | +|---|---| +| **Prinzip** | `{{exerciseMedia:id}}` → ``; IDs müssen zur Übung gehören. | +| **Begründung** | Ein Render-Pfad; keine broken References nach Medien-Löschung. | +| **Quelle** | `exercise_rich_text.py`; Spec §11 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Beim Erst-Anlegen der Übung keine Inline-Refs (Chicken-Egg). | + +### 6. Speicher-Abstraktion (local + konfigurierbarer Root) + +| | | +|---|---| +| **Prinzip** | `get_effective_media_root()` + `library/…`-Pfadkonvention; kein hardcodierter `/app/media` in Routern. | +| **Begründung** | NAS/externer Speicher vorbereitet. | +| **Quelle** | `media_storage.py`; `platform_media_storage` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | S3-Backend noch nicht vollständig. | + +### 7. Audit-Log für sensitive Aktionen + +| | | +|---|---| +| **Prinzip** | `media_asset_audit_log` bei Meldungen, Hold, kritischen Lifecycle-Events. | +| **Begründung** | Nachvollziehbarkeit für Admins und Compliance. | +| **Quelle** | `media_rights.write_audit_log_entry` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht jede Admin-Aktion geloggt. | + +--- + +## Nicht übernehmen + +1. **Download nur mit Übungs-ID ohne Asset-Governance** — Seitenkanal-Risiko. +2. **Copyright leer bei `official`** — Spec verbietet das fachlich. +3. **Physisches Löschen bei Referenzanzahl > 0** — Spec §5.3. +4. **Embed-URLs durch Lifecycle-Purge** — Embeds haben anderen Lebenszyklus. + +--- + +## Verwandte Dokumentation + +- [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../../../.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- [CONTENT_REPORTS_DESIGN_PRINCIPLES.md](./CONTENT_REPORTS_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..699ff08 --- /dev/null +++ b/docs/reference/design-principles/shinkan/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Migration & Deploy – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Migration & Deploy“ — Schema-Evolution, Container-Start + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 13 von 15 +**Mitai-Vergleich:** [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) (#9) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| DB-Init | `backend/db_init.py` | +| Migrationen | `backend/migrations/XXX_*.sql` | +| Version | `backend/version.py` (`DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) | +| Docker | `docker-compose.yml`, `docker-compose.dev-env.yml` | +| Deploy | develop → dev.shinkan · main → shinkan (Pi) | + +--- + +## Modul + +**Migration & Deploy** + +Nummerierte SQL-Migrationen beim Container-Start, Tracking in `schema_migrations`, Git-Branch → Umgebung, fail-fast ohne Auto-Rollback. + +--- + +## Designprinzipien + +### 1. Nummerierte Migrationen `XXX_*.sql` + +| | | +|---|---| +| **Prinzip** | Nur nummerierte Dateien in `backend/migrations/`; lexikographische Reihenfolge. | +| **Begründung** | Familien-Standard Mitai/Shinkan; vorhersagbare Anwendung. | +| **Quelle** | `db_init.py`; `CLAUDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Manuelle Nummern-Kollisionen vermeiden — Team-Disziplin. | + +### 2. Startup vor App — Migrationen blockieren Start bei Fehler + +| | | +|---|---| +| **Prinzip** | `db_init.py` wartet auf Postgres, wendet fehlende Migrationen an, dann FastAPI. | +| **Begründung** | Keine App mit veraltetem Schema. | +| **Quelle** | Container-Entrypoint / startup | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein automatisches Rollback — manuelle Recovery. | + +### 3. `schema_migrations` Tracking-Tabelle + +| | | +|---|---| +| **Prinzip** | Jede angewendete Datei wird persistiert; Wiederholung überspringt Bekannte. | +| **Begründung** | Idempotenz über Deploys hinweg. | +| **Quelle** | `ensure_migration_table`, `get_applied_migrations` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Geänderte Migration nach Apply — nicht neu ausführen (neue Nummer). | + +### 4. `DB_SCHEMA_VERSION` als dokumentierter Stand + +| | | +|---|---| +| **Prinzip** | `version.py` führt Schema-Version; MODULE_VERSIONS für Subsysteme. | +| **Begründung** | Support und Handover wissen erwarteten Stand. | +| **Quelle** | `backend/version.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Manuell pflegen bei Migration — Drift möglich. | + +### 5. develop/main → Dev/Prod mit festen Ports + +| | | +|---|---| +| **Prinzip** | Dev 3098/8098 · Prod 3003/8003 — nie ändern ohne explizite Freigabe. | +| **Begründung** | Deploy-Infrastruktur auf Pi/Synology stabil. | +| **Quelle** | `CLAUDE.md` Deployment | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Neue Spalten nur via Migration + +| | | +|---|---| +| **Prinzip** | Kein ad-hoc ALTER in Routern; Coding Rules. | +| **Begründung** | Reproduzierbare Umgebungen. | +| **Quelle** | `.claude/rules/CODING_RULES.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 7. IF NOT EXISTS / defensive SQL wo sinnvoll + +| | | +|---|---| +| **Prinzip** | Migrationen tolerant bei Wiederanlauf in Dev — aber Tracking verhindert Doppel-Apply. | +| **Begründung** | Recovery in Entwicklung erleichtern. | +| **Quelle** | Mitai-Migrations-Muster | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alles idempotent — komplexe Migrationen brauchen Transaktion. | + +--- + +## Nicht übernehmen + +1. **Manuelle psql-Schritte in Prod** ohne nummerierte Migration im Repo. +2. **Schema-Drift nur in schema.sql** ohne Migration — Init vs. Upgrade verwechseln. +3. **Auto-Rollback bei fehlgeschlagener Migration** — fail-fast, manuell fixen. +4. **Port-Änderung ohne Infra-Update** — bricht Fritz!Box/NAS-Routing. + +--- + +## Verwandte Dokumentation + +- [DATABASE_SCHEMA.md](../../../.claude/docs/technical/DATABASE_SCHEMA.md) +- Mitai: [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/NAVIGATION_IA_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/NAVIGATION_IA_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..3a640c9 --- /dev/null +++ b/docs/reference/design-principles/shinkan/NAVIGATION_IA_DESIGN_PRINCIPLES.md @@ -0,0 +1,107 @@ +# Navigation / IA – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Navigation & Information Architecture“ + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 12 von 15 +**Mitai-Vergleich:** [NAVIGATION_IA_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/NAVIGATION_IA_DESIGN_PRINCIPLES.md) (#8) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Hauptnav | `frontend/src/config/appNav.js` | +| Admin-Nav | `frontend/src/components/AdminPageNav.jsx` | +| Shells | `RequireAdmin`, App-Layout in `App.jsx` | +| Return-Kontext | `.claude/docs/technical/NAV_RETURN_CONTEXT_SPEC.md` | +| Styles | `frontend/src/app.css` (`.admin-top-nav`, Bottom-Nav) | + +--- + +## Modul + +**Navigation / IA** + +Single Source of Truth für Hauptnavigation (Mobile Bottom + Desktop Sidebar), separater Admin-Hub, Onboarding-Nav ohne Vereinsfeatures, rollen- und kontextabhängige Einblendung (Posteingang, Admin). + +--- + +## Designprinzipien + +### 1. `appNav.js` als SSoT für Hauptnavigation + +| | | +|---|---| +| **Prinzip** | `getMainNavItems(isAdmin, opts)` liefert Route, Label, Icon — eine Liste für Mobile und Desktop. | +| **Begründung** | Gleiches Familien-Muster wie Mitai `appNav`; keine divergierenden Nav-Arrays. | +| **Quelle** | `appNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Tiefe Unterrouten (Übung bearbeiten) nicht in Top-Nav — Shell/Back. | + +### 2. Admin als separater Hub + +| | | +|---|---| +| **Prinzip** | `/admin/*` mit horizontaler `AdminPageNav` — Plattform-Werkzeuge gebündelt. | +| **Begründung** | Trainer-Nav bleibt schlank; Admin-IA skaliert unabhängig. | +| **Quelle** | `AdminPageNav.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Admin-Nav hardcoded Array — kein `adminNav.js` SSoT wie Mitai ideal. | + +### 3. Onboarding-Nav reduziert + +| | | +|---|---| +| **Prinzip** | `getOnboardingNavItems()` — nur Verein + Einstellungen ohne Übungen/Planung. | +| **Begründung** | Nutzer ohne Vereinsmitgliedschaft nicht in leere Bereiche führen. | +| **Quelle** | `appNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Kontextabhängige Items (Posteingang) + +| | | +|---|---| +| **Prinzip** | `showInbox` Flag steuert Posteingang-Eintrag — Berechtigung aus Entitlements/Rolle. | +| **Begründung** | Kein toter Nav-Link für Trainer ohne Inbox-Recht. | +| **Quelle** | `baseItems({ showInbox })` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Logik zur `showInbox`-Setzung in App.jsx pflegen. | + +### 5. Responsive: Bottom-Nav Mobile, Sidebar Desktop + +| | | +|---|---| +| **Prinzip** | Gleiche Items, unterschiedliche Präsentation; CSS-Variablen für Abstände. | +| **Begründung** | PWA-typisches Muster; 80px Bottom-Padding für Nav. | +| **Quelle** | `app.css`; Design-System in `CLAUDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Breakpoint-Konsistenz mit Mitai (1024px) prüfen beim Familien-Review. | + +### 6. Return-Kontext für tiefe Bearbeitung + +| | | +|---|---| +| **Prinzip** | Spez `NAV_RETURN_CONTEXT_SPEC` — zurück zur Herkunftsliste mit Filter-State. | +| **Begründung** | Übungs-Editor aus Suche/Planung ohne Navigations-Verlust. | +| **Quelle** | Return-Context Spec | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alle Flows implementiert. | + +--- + +## Nicht übernehmen + +1. **Zwei unterschiedliche Nav-Arrays** für Mobile vs. Desktop. +2. **Admin-Routen in Haupt-Bottom-Nav** mischen (außer ein Admin-Einstieg). +3. **Hardcodierte Nav in jeder Page** — zentral in `appNav.js`. +4. **Fehlender Onboarding-Gate** — volle Nav ohne Verein verwirrt. + +--- + +## Verwandte Dokumentation + +- [NAV_RETURN_CONTEXT_SPEC.md](../../../.claude/docs/technical/NAV_RETURN_CONTEXT_SPEC.md) +- Mitai: [NAVIGATION_IA_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/NAVIGATION_IA_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..4fdf12f --- /dev/null +++ b/docs/reference/design-principles/shinkan/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md @@ -0,0 +1,114 @@ +# Rights Registry – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Rights Registry“ — Capabilities & Features zur Laufzeit registrieren + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 3 von 15 +**Mitai-Vergleich:** [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) (#4) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Registry-Kern | `backend/rights_registry.py` | +| Modul-Registrierungen | `backend/rights_registrations/` (`exercises.py`, `planning.py`, `platform.py`, `club_creation.py`) | +| Startup-Sync | Import in `backend/main.py` | +| Tests | `backend/tests/test_rights_registry.py` | + +--- + +## Modul + +**Rights Registry (Registry-first für Capabilities & Features)** + +Module deklarieren bei Implementierung, welche Rechte und Kontingente sie anbieten. Beim App-Start werden Definitionen in die DB synchronisiert (`capabilities`, `features`, Default-Grants). + +--- + +## Fachliche Verantwortung + +1. **Runtime-Registrierung** — `register_capability()`, `register_feature()` vor DB-Sync. +2. **Modul-Ownership** — Jedes Feature/Capability trägt `module`-Feld für Admin-Filter „Rollen & Rechte“. +3. **Default Club Grants** — Rollen → Capability-Mapping bei Erst-Sync. +4. **Kein vollständiger Vorab-Katalog in SQL-Migration** — nur was Module wirklich liefern. + +--- + +## Designprinzipien + +### 1. Registry-first statt Migrations-Monolith + +| | | +|---|---| +| **Prinzip** | Neue Rechte erscheinen durch Code-Registrierung + Startup-Upsert — nicht durch manuelle 079-Katalog-Migration pro Feature. | +| **Begründung** | Modul und Recht entstehen zusammen; weniger vergessene Katalog-Einträge. | +| **Quelle** | `rights_registry.py`; Docstring; `rights_registrations/` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Erste Basismigration seedet noch initiale Zeilen. | + +### 2. Modul-Datei pro Domäne + +| | | +|---|---| +| **Prinzip** | `rights_registrations/exercises.py` registriert nur Übungs-Rechte; Planung/Platform analog. | +| **Begründung** | Ownership klar; Merge-Konflikte lokalisiert. | +| **Quelle** | `rights_registrations/__init__.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Import-Reihenfolge muss in `main.py` garantiert sein. | + +### 3. Frozen Dataclass-Definitionen + +| | | +|---|---| +| **Prinzip** | `CapabilityRegistration` / `FeatureRegistration` als immutable `@dataclass(frozen=True)`. | +| **Begründung** | Keine nachträgliche Mutation nach Registrierung. | +| **Quelle** | `rights_registry.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Validierung an der Registrierungsgrenze + +| | | +|---|---| +| **Prinzip** | `register_*` wirft bei fehlendem `id` oder `module`. | +| **Begründung** | Fehler beim Import/Startup, nicht erst im Admin-UI. | +| **Quelle** | `rights_registry.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine Schema-Validierung für `limit_type`/`reset_period` zur Compile-Zeit. | + +### 5. DB als persistierter Katalog, Code als SSoT für neue IDs + +| | | +|---|---| +| **Prinzip** | Startup `sync_rights_registry_to_db()` upsertet aus In-Memory-Registry. | +| **Begründung** | Admin-UI liest DB; Entwickler erweitern Code-Registry. | +| **Quelle** | `rights_registry.py`; `main.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Deaktivierte Capabilities in DB vs. fehlende im Code — Reconcile-Policy dokumentieren. | + +### 6. Default Grants als Code-Daten + +| | | +|---|---| +| **Prinzip** | `default_club_grants: (role_code, capability_id)` pro Capability. | +| **Begründung** | Neue Module bringen sinnvolle Standard-Rollen mit. | +| **Quelle** | `rights_registrations/exercises.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Admin-Overrides in DB können bei Re-Sync überschrieben werden — ON CONFLICT-Verhalten beachten. | + +--- + +## Nicht übernehmen + +1. **Capabilities nur in SQL-Migration pflegen** — driftet vom implementierten Modul weg. +2. **Registrierung ohne Endpoint-Verdrahtung** — Spec: „nur Rechte mit echter Endpoint-Verdrahtung“. +3. **Zweite Registry-Philosophie** für Custom Roles — gleiche Capability-IDs wiederverwenden (Plan Stufe E). + +--- + +## Verwandte Dokumentation + +- [CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- [CAPABILITY_CATALOG.v1.md](../../../.claude/docs/technical/CAPABILITY_CATALOG.v1.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/SKILL_SCORING_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/SKILL_SCORING_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..c5fb249 --- /dev/null +++ b/docs/reference/design-principles/shinkan/SKILL_SCORING_DESIGN_PRINCIPLES.md @@ -0,0 +1,107 @@ +# Skill Scoring – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Skill Scoring & Profile“ — gewichtete Fähigkeiten-KPIs + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 8 von 15 +**Mitai-Vergleich:** [DATA_LAYER_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DATA_LAYER_DESIGN_PRINCIPLES.md) (#2, analog: Berechnungs-SSoT) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Kern | `backend/skill_scoring.py` | +| Profile-API | `backend/routers/skill_profiles.py` | +| Planungs-Vorschläge | Planung-Router + Fähigkeiten-Seite | +| Spec | `.claude/docs/technical/SKILL_SCORING_SPEC.md` | + +--- + +## Modul + +**Skill Scoring & Profiles** + +Regelbasierte Aggregation von `exercise_skills` über Artefakte (Module, Rahmenprogramme, Pläne, Graphen) zu gewichteten Profilen mit Peer-Vergleich innerhalb desselben Artefakttyps. + +--- + +## Designprinzipien + +### 1. Berechnung in einer Schicht (`skill_scoring.py`) + +| | | +|---|---| +| **Prinzip** | Scores, Gewichte, Peer-Perzentile — nicht in React oder Router-SQL duplizieren. | +| **Begründung** | Analog Mitai Data Layer: Charts, Listen-KPIs, Planungs-Vorschläge nutzen dieselbe Logik. | +| **Quelle** | `SKILL_SCORING_SPEC.md`; `skill_scoring.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne UI-Fallbacks können noch vereinfacht rechnen. | + +### 2. Gewichtung aus Trainings-Signalen + +| | | +|---|---| +| **Prinzip** | Dauer, Vorkommen, Intensität (`niedrig`/`mittel`/`hoch`), Stufen-Spanne — explizite Multiplikatoren. | +| **Begründung** | Nachvollziehbares Ranking ohne Black-Box-ML. | +| **Quelle** | `_INTENSITY_MULT`, `_level_range_multiplier` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `is_primary` / `development_contribution` bewusst ignoriert. | + +### 3. Peer-Vergleich nur unter gleichem Artefakttyp + +| | | +|---|---| +| **Prinzip** | Modul vs. Modul, Rahmen vs. Rahmen — nie Modul vs. Plan gemischt. | +| **Begründung** | Fachlich sinnvoller Vergleich; vermeidet irreführende Prozentwerte. | +| **Quelle** | Phase 3 Lieferung; Nutzerfunktionen §4.2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI muss Typ-Kontext klar labeln. | + +### 4. Sichtbarkeits-filterte Peer-Menge + +| | | +|---|---| +| **Prinzip** | Peer-Pool = nur für Nutzer sichtbare Artefakte (Access Layer). | +| **Begründung** | Keine Leaks über Scores fremder Vereins-Inhalte. | +| **Quelle** | `skill_scoring.py` + Tenant-Filter in Aufrufern | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Performance bei großen Pools. | + +### 5. Planungs-Vorschläge aus Profil-Delta + +| | | +|---|---| +| **Prinzip** | Fähigkeiten-Schwerpunkte → sortierte Vorschläge für Module/Rahmen/Regressionspfade. | +| **Begründung** | Schließt Loop zwischen Katalog und Planung. | +| **Quelle** | Fähigkeiten-Seite Phase 3 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | KI-Suche über Volltext — Backlog. | + +### 6. Default-Minuten für fehlende Dauer + +| | | +|---|---| +| **Prinzip** | `DEFAULT_ITEM_MINUTES` / `GRAPH_DEFAULT_ITEM_MINUTES` als explizite Konstanten. | +| **Begründung** | Deterministische Scores bei unvollständigen Planungsdaten. | +| **Quelle** | `skill_scoring.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Fachlich kalibrierbar — dokumentieren statt verstecken. | + +--- + +## Nicht übernehmen + +1. **Score-Berechnung im Frontend** für Listen-KPIs. +2. **Peer-Vergleich über Artefakttypen hinweg** — irreführend. +3. **ML-Black-Box statt regelbasierter Gewichte** — ohne explizite Produktentscheidung. +4. **Ignorieren der Tenant-Sichtbarkeit** im Peer-Pool. + +--- + +## Verwandte Dokumentation + +- [SKILL_SCORING_SPEC.md](../../../.claude/docs/technical/SKILL_SCORING_SPEC.md) +- [EXERCISE_CATALOG_DESIGN_PRINCIPLES.md](./EXERCISE_CATALOG_DESIGN_PRINCIPLES.md) +- [TRAINING_PLANNING_DESIGN_PRINCIPLES.md](./TRAINING_PLANNING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/TRAINING_PLANNING_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/TRAINING_PLANNING_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..32b4645 --- /dev/null +++ b/docs/reference/design-principles/shinkan/TRAINING_PLANNING_DESIGN_PRINCIPLES.md @@ -0,0 +1,129 @@ +# Training Planning – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Training Planning“ — Einheiten, Phasen, Rahmen, Coach + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 9 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Kalender-Einheiten | `backend/routers/training_planning.py` | +| Module | `backend/routers/training_modules.py` | +| Rahmen | `backend/routers/training_framework_programs.py` | +| Phasen/Streams | Migration 063; `PARALLEL_TRAINING_STREAMS_SPEC.md` | +| Frontend | `TrainingPlanningPage`, `TrainingCoachPage`, `TrainingUnitRunPage` | +| Utils | `frontend/src/utils/trainingPlanUtils.js` | + +--- + +## Modul + +**Training Planning & Frameworks** + +Planbare Trainingseinheiten mit Sektionen, **Phasen** (Ganzgruppe/Parallel) und **Streams**, Bibliotheks-**Rahmenprogramme** (Ziele/Slots), **Trainingsmodule**, Materialisierung aus Slots, Durchführungs- und Coaching-Ansichten. + +--- + +## Designprinzipien + +### 1. Einheit als planbares Aggregate + +| | | +|---|---| +| **Prinzip** | `training_units` + `training_unit_sections` + Items; Kopf: Gruppe, Datum, Trainer, Status. | +| **Begründung** | Klare Grenze Kalender vs. Bibliothek. | +| **Quelle** | Domain Model; `training_planning.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Router-Datei — Refaktor-Schuld. | + +### 2. Phasen/Streams als explizites Modell (nicht Marker-Sektionen) + +| | | +|---|---| +| **Prinzip** | `training_unit_phases` + `training_unit_parallel_streams`; Sektionen an Phase oder Stream gebunden. | +| **Begründung** | Breakout-Trainings fachlich korrekt; Coach/Rejoin-Logik. | +| **Quelle** | `PARALLEL_TRAINING_STREAMS_SPEC.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Einheiten → Default-Ganzgruppenphase; Vorlagen-Phasen teils offen. | + +### 3. API: verschachtelte `phases` + flache `sections` + +| | | +|---|---| +| **Prinzip** | GET liefert beides; PUT akzeptiert `phases` atomar; höchstens eines von phases/sections/exercises pro Request. | +| **Begründung** | Frontend normalisiert; Server validiert CHECK-Regeln. | +| **Quelle** | Spec §4; Planning-Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Server-Spiegelung neuer Abschnitte in phases — Handover offen. | + +### 4. Rahmen-Bibliothek: Slot = Blueprint-Unit + +| | | +|---|---| +| **Prinzip** | `framework_slot_id` auf Blueprint-`training_units`; Materialisierung → Kalender-Einheit für Gruppe. | +| **Begründung** | Wiederverwendbare Programme ohne Duplikat-Logik pro Slot-Typ. | +| **Quelle** | Migration 035–037 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI „Aus Rahmen übernehmen“ nicht flächendeckend. | + +### 5. Trainingsmodule als wiederverwendbare Übungsfolgen + +| | | +|---|---| +| **Prinzip** | Bibliotheks-Objekt mit Skill-Profil; Übernahme in geplante Einheit. | +| **Begründung** | Trainer-Bausteine zwischen Einzelübung und Rahmen. | +| **Quelle** | `training_modules.py`; Skill Scoring Phase 3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Drei Durchführungsmodi getrennt + +| | | +|---|---| +| **Prinzip** | Planung (edit) · Plan & Ablauf (run) · Coaching (step timeline, Stream-Picks, Nachbereitung). | +| **Begründung** | Unterschiedliche UX und Payloads; Coach speichert → Run-Ansicht. | +| **Quelle** | Nutzerfunktionen §4.4; `TrainingCoachPage` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Stream-Tabs in Run-Ansicht optional offen. | + +### 7. Governance über Gruppe/Verein, keine neuen Mandanten-Entitäten + +| | | +|---|---| +| **Prinzip** | Einheit → `training_group` → Verein; Access Layer für Bibliotheks-Rahmen/Module. | +| **Begründung** | Planung erbt Organisations-Kontext. | +| **Quelle** | `PARALLEL_TRAINING_STREAMS_SPEC.md` §4 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Stream-Trainer-Zuweisung UI unvollständig. | + +### 8. Kombinationsübungen in Planung transparent + +| | | +|---|---| +| **Prinzip** | Items ohne Variante; Coach zeigt Stations-Kandidaten + Archetyp-Hinweise. | +| **Begründung** | Gleiche Item-Schicht für Standard- und Kombi-Übungen. | +| **Quelle** | Migration 057; Kombinations-Spec Anhang A | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Archetyp-Stufen B/C ausbaubar. | + +--- + +## Nicht übernehmen + +1. **Parallele Phasen als reine UI-Konvention ohne DB-Phasen** — Minimalvariante verworfen. +2. **Rahmen-Slots als separate Exercise-Join-Tabelle** — Legacy `training_framework_slot_exercises` abgelöst. +3. **Planungs-KI direkt in Router-Strings** — AI Prompt Runtime nutzen. +4. **Blueprint-Einheiten in Kalenderlisten** — Filter `framework_slot_id IS NOT NULL`. + +--- + +## Verwandte Dokumentation + +- [PARALLEL_TRAINING_STREAMS_SPEC.md](../../../.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md) +- [TRAINING_FRAMEWORK_SPEC.md](../../../.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/reference/design-principles/shinkan/WIKI_IMPORT_DESIGN_PRINCIPLES.md b/docs/reference/design-principles/shinkan/WIKI_IMPORT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..267c6d8 --- /dev/null +++ b/docs/reference/design-principles/shinkan/WIKI_IMPORT_DESIGN_PRINCIPLES.md @@ -0,0 +1,118 @@ +# Wiki Import – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „MediaWiki Import“ — SMW-Ingest, Mapping, Tracking + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 11 von 15 +**Mitai-Vergleich:** [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) (#6) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Router | `backend/routers/import_wiki.py`, `import_wiki_admin.py` | +| Client | `backend/smw_client.py` | +| Mapper | `backend/smw_mapper.py` | +| Tracking | `wiki_import_log`, `wiki_import_references` | +| Spec | `.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md` | + +--- + +## Modul + +**Wiki Import (Semantic MediaWiki)** + +Import von Übungen, Fähigkeiten, Methoden und Reifegradmodellen aus externem Wiki via API — Preview, Dry-Run, Duplikat-Erkennung, Admin-only. + +--- + +## Designprinzipien + +### 1. Ingest ≠ Interpretation + +| | | +|---|---| +| **Prinzip** | `SmwClient` holt Rohdaten; `smw_mapper` mappt auf Shinkan-Modelle — getrennte Schichten. | +| **Begründung** | Analog Mitai Import: Transport/Parser ≠ Domänen-Insert. | +| **Quelle** | `import_wiki.py`; Mitai Universal Import | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine generische Import-Registry wie Mitai CSV — wiki-spezifisch. | + +### 2. Preview und Dry-Run vor Execute + +| | | +|---|---| +| **Prinzip** | `/preview` zeigt Kandidaten; `dry_run=true` ohne DB-Schreiben. | +| **Begründung** | Admin sieht Auswirkungen; sichere Iteration. | +| **Quelle** | `ImportExecuteRequest` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 3. Duplikat-Tracking über Wiki-Referenzen + +| | | +|---|---| +| **Prinzip** | `wiki_import_references` speichert Wiki-Titel ↔ Shinkan-ID für Re-Import. | +| **Begründung** | Idempotenz und Update statt blindem Duplicate. | +| **Quelle** | Domain Model Import-Tabellen | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Gap-Analyse in `SMW_IMPORTER_GAP_ANALYSIS.md` beachten. | + +### 4. Import-Typ als expliziter Parameter + +| | | +|---|---| +| **Prinzip** | `import_type`: `exercise` \| `skill` \| `method` \| Modelle — eigener Mapper-Pfad. | +| **Begründung** | Klare Verantwortung pro Ziel-Entität. | +| **Quelle** | `map_wiki_to_*` in `smw_mapper.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein Plug-in-Registry-Pattern wie Mitai Module-Registry. | + +### 5. Superadmin/Admin-only Execute + +| | | +|---|---| +| **Prinzip** | `require_admin` auf Execute — Massenimport ist Plattform-Risiko. | +| **Begründung** | Governance und Datenqualität. | +| **Quelle** | `import_wiki.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Kategorie aus Env mit Fallback + +| | | +|---|---| +| **Prinzip** | `MEDIAWIKI_CATEGORY_*` Env-Variablen; leere Query → Default je Typ. | +| **Begründung** | Wiki-Struktur konfigurierbar ohne Code-Deploy. | +| **Quelle** | `CATEGORY_EXERCISES` etc. | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Hardcoded Wiki-URL in Doku — Umgebungsspezifisch halten. | + +### 7. Background Tasks für lange Imports + +| | | +|---|---| +| **Prinzip** | FastAPI `BackgroundTasks` für Execute — HTTP nicht blockieren. | +| **Begründung** | Große Kategorien ohne Timeout. | +| **Quelle** | Execute-Endpoint | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Kein Job-Status-Polling-UI wie Mitai — Log-Tabelle nutzen. | + +--- + +## Nicht übernehmen + +1. **Rohe Wiki-HTML ungemappt in DB** — immer Mapper. +2. **Import ohne Log/Re-Import-Referenz** — Duplikat-Chaos. +3. **Trainer-self-service Wiki-Import** — Admin-only. +4. **Skill-Scoring beim Insert** — Scores gehören in `skill_scoring`-Schicht. + +--- + +## Verwandte Dokumentation + +- [MEDIAWIKI_IMPORT_SPEC.md](../../../.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md) +- [EXERCISE_CATALOG_DESIGN_PRINCIPLES.md](./EXERCISE_CATALOG_DESIGN_PRINCIPLES.md) +- Mitai: [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) From 1ce20d8bdb8db54660de6ca00bc6d5ca0259ff24 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:08:55 +0200 Subject: [PATCH 02/10] Sprint-0-Spezifikationen ergaenzen, AP0.1-Setup-Luecken schliessen (Health, CI, ADR-Vorlage). Co-authored-by: Cursor --- .cursor/rules/kairo-architecture.mdc | 45 +- .gitea/workflows/deploy-dev.yml | 10 +- .gitea/workflows/deploy-prod.yml | 8 +- .gitea/workflows/test.yml | 54 +- .gitignore | 1 + README.md | 21 + docs/DEPLOYMENT.md | 4 +- ...ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md | 38 ++ ...undation_Minimum_Viable_Foundation_v0.2.md | 200 ++++++++ .../Kairo_Architecture_References_v0.1.md | 6 + .../Kairo_Sprint0_Principle_Gate_v0.1.md | 105 ++++ .../Jinkendo_Kairo_Product_Spec_v0.2.md | 448 +++++++++++++++++ ...nkendo_Kairo_04_Sprint0_Foundation_v0.3.md | 466 ++++++++++++++++++ ...nt0_AP0_1_Project_Setup_Assignment_v0.1.md | 167 +++++++ .../Sprint0_Vibe_Coder_Handover_v0.1.md | 112 +++++ infra/README.md | 7 + scripts/load/README.md | 2 +- scripts/load/k6-health-baseline.js | 4 +- 18 files changed, 1651 insertions(+), 47 deletions(-) create mode 100644 docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md create mode 100644 docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md create mode 100644 docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md create mode 100644 docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md create mode 100644 docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md create mode 100644 docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md create mode 100644 docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md create mode 100644 infra/README.md diff --git a/.cursor/rules/kairo-architecture.mdc b/.cursor/rules/kairo-architecture.mdc index 057eb77..cb0050d 100644 --- a/.cursor/rules/kairo-architecture.mdc +++ b/.cursor/rules/kairo-architecture.mdc @@ -1,18 +1,41 @@ +--- +description: Kairo Sprint-0 Architektur-Leitplanken und Dokumentenpriorität +globs: backend/**,frontend/**,docs/**,.gitea/** +alwaysApply: true +--- +# Kairo Architecture Rules + +Du arbeitest an **Jinkendo Kairo** (Sprint 0). Kairo ist mandantenfähig und Actor-first. + +## Dokumentenpriorität bei Konflikten + +1. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +2. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +3. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +4. `docs/reference/design-principles/alignment/*` +5. Mitai/Shinkan-Einzelprinzipien (nur Begründung, kein Scope) ## Reference design principles -Reference material lives in: +Referenzmaterial: -- docs/reference/design-principles/mitai/ -- docs/reference/design-principles/shinkan/ -- docs/reference/design-principles/alignment/ +- `docs/reference/design-principles/mitai/` +- `docs/reference/design-principles/shinkan/` +- `docs/reference/design-principles/alignment/` -These files explain why certain patterns exist. They are not implementation scope unless the Kairo Sprint-0 Principle Gate or an Architecture Decision explicitly pulls them into scope. +Nicht automatisch Implementierungs-Scope. Übernahme nur via Principle Gate oder Architecture Decision. -Conflict order: -1. Kairo_Sprint0_Principle_Gate_v0.1.md -2. Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md -3. Jinkendo_Kairo_Product_Spec_v0.2.md -4. Alignment documents -5. Mitai/Shinkan individual principle documents +## Harte Guardrails + +- Kein Single-User ohne Tenant +- User ≠ Actor; Agenten sind Actors +- Auth, Capability, Feature, Governance trennen +- Keine hardcodierten Rechte, Prompts oder Fachkonfiguration +- Nummerierte SQL-Migrationen; kein ad-hoc DDL in Routern +- Keine Mitai-/Shinkan-Domänenlogik kopieren +- Keine Vorhaben-/Projektlogik vor Sprint-0-Abschluss + +## Abweichungen + +Architecture Decision Proposal nach Vorlage in `docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md`. diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index a9af2a9..9554674 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -18,11 +18,15 @@ jobs: docker compose -f docker-compose.dev-env.yml build --no-cache docker compose -f docker-compose.dev-env.yml up -d sleep 5 - if ! curl -sf http://localhost:8097/api/version; then + if ! curl -sf http://localhost:8097/api/health; then echo "✗ DEV API nicht erreichbar — Backend-Logs (Migration/Startup):" docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true exit 1 fi - echo "✓ DEV API healthy" - curl -sf http://localhost:3097/api/version && echo "✓ DEV über Frontend-Nginx (wie Browser) healthy" + echo "✓ DEV API /api/health OK" + if docker compose -f docker-compose.dev-env.yml ps --status running 2>/dev/null | grep -q frontend; then + curl -sf http://localhost:3097/api/health && echo "✓ DEV über Frontend-Nginx healthy" + else + echo "(Frontend-Check übersprungen — optional in AP0.1)" + fi echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 51f6801..b5cef74 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -18,6 +18,10 @@ jobs: docker compose build --no-cache docker compose up -d sleep 5 - curl -sf http://localhost:8004/api/version && echo "✓ PROD API (direkt) healthy" - curl -sf http://localhost:3004/api/version && echo "✓ PROD API über Frontend-Nginx (wie Browser) healthy" + curl -sf http://localhost:8004/api/health && echo "✓ PROD API (direkt) /api/health OK" + if docker compose ps --status running 2>/dev/null | grep -q frontend; then + curl -sf http://localhost:3004/api/health && echo "✓ PROD über Frontend-Nginx healthy" + else + echo "(Frontend-Check übersprungen — optional in AP0.1)" + fi echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 67c864b..5be58b3 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -54,11 +54,9 @@ jobs: done docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " - pip install -r /app/requirements-dev.txt && + pip install -q pytest httpx 2>/dev/null || pip install -q -r /app/requirements-dev.txt && cd /app && - ACCESS_LAYER_STRICT=1 python scripts/check_access_layer_hints.py && - python scripts/security_release_checks.py && - ACCESS_LAYER_INTEGRATION=1 SKIP_DB_MIGRATE=1 python -m pytest tests -m 'not slow' -ra -vv --tb=short + python -m pytest tests -ra -vv --tb=short " lint-backend: @@ -103,12 +101,16 @@ jobs: fi cd "$APP_DIR/frontend" + if [ ! -f package.json ]; then + echo "Frontend noch nicht vorhanden — übersprungen (AP0.1 optional)" + exit 0 + fi npm install npm run build echo "✓ Frontend build OK" k6-health-baseline: - name: k6 /health Baseline + name: k6 /api/health Baseline if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest env: @@ -133,36 +135,36 @@ jobs: echo "→ k6 gegen Dev (${DEV_BASE})." fi - - name: Dev /health abwarten + - name: Dev /api/health abwarten if: ${{ steps.e2e.outputs.mode == 'dev' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/health …" + echo "Warte auf $BASE/api/health …" for i in $(seq 1 90); do - if curl -sf "$BASE/health" >/dev/null 2>&1; then + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done - echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen." - curl -v "$BASE/health" || true + echo "Timeout: Dev /api/health nicht erreichbar — Deploy / DNS / Firewall prüfen." + curl -v "$BASE/api/health" || true exit 1 - - name: Prod /health abwarten + - name: Prod /api/health abwarten if: ${{ steps.e2e.outputs.mode == 'prod' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/health …" + echo "Warte auf $BASE/api/health …" for i in $(seq 1 60); do - if curl -sf "$BASE/health" >/dev/null 2>&1; then + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 5 done - echo "Timeout: Prod /health nicht erreichbar" - curl -v "$BASE/health" || true + echo "Timeout: Prod /api/health nicht erreichbar" + curl -v "$BASE/api/health" || true exit 1 - name: Install k6 @@ -181,7 +183,7 @@ jobs: sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 k6 version - - name: k6 Health-Baseline (parallele /health) + - name: k6 Health-Baseline (parallele /api/health) env: BASE_URL: ${{ steps.e2e.outputs.base_url }} run: | @@ -220,36 +222,36 @@ jobs: echo "→ Deployte Dev-Umgebung (${DEV_BASE}). Secrets: E2E_DEV_TEST_EMAIL / E2E_DEV_TEST_PASSWORD." fi - - name: Dev /health abwarten + - name: Dev /api/health abwarten if: ${{ steps.e2e.outputs.mode == 'dev' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/health …" + echo "Warte auf $BASE/api/health …" for i in $(seq 1 90); do - if curl -sf "$BASE/health" >/dev/null 2>&1; then + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done - echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen." - curl -v "$BASE/health" || true + echo "Timeout: Dev /api/health nicht erreichbar — Deploy / DNS / Firewall prüfen." + curl -v "$BASE/api/health" || true exit 1 - - name: Prod /health abwarten + - name: Prod /api/health abwarten if: ${{ steps.e2e.outputs.mode == 'prod' }} run: | BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/health …" + echo "Warte auf $BASE/api/health …" for i in $(seq 1 60); do - if curl -sf "$BASE/health" >/dev/null 2>&1; then + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 5 done - echo "Timeout: Prod /health nicht erreichbar" - curl -v "$BASE/health" || true + echo "Timeout: Prod /api/health nicht erreichbar" + curl -v "$BASE/api/health" || true exit 1 - name: Testnutzer registrieren (Dev, nur wenn möglich) diff --git a/.gitignore b/.gitignore index 06b8805..94060c3 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ coverage/ # Temp tmp/ *.tmp +*_alt.md # Claude: nur ausgewählte Bereiche versionieren .claude/** diff --git a/README.md b/README.md index b12eb6c..c4b9323 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,24 @@ docs/reference/design-principles/ Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. + +## Deployment + +Auto-Deploy via Gitea Actions auf dem Raspberry Pi (Ports 3004/8004 Prod · 3097/8097 Dev). + +Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) + +## AP0.1 – Stand Projektgrundlage + +| Bereich | Status | +|---------|--------| +| Git + Gitea (`develop` / `main`) | erledigt | +| Docker Compose (Prod + Dev) | erledigt | +| Gitea Actions (Deploy + Test) | erledigt, an Kairo angepasst | +| Sprint-0-Spezifikationen | im Repo | +| Designprinzipien-Referenz | im Repo | +| Backend-Skeleton (FastAPI, Migrationen, Tests) | **offen** | +| Frontend minimal | optional, **offen** | +| README „Local Development“ | folgt mit Backend-Skeleton | + +Nächster Schritt: Rest von AP0.1 gemäß `docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md`. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 6ea4d67..f3238ff 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -105,8 +105,8 @@ Workflows unter `.gitea/workflows/`: Health-Checks nach Deploy: -- Dev: `curl http://localhost:8097/api/version` und `http://localhost:3097/api/version` -- Prod: `curl http://localhost:8004/api/version` und `http://localhost:3004/api/version` +- Dev: `curl http://localhost:8097/api/health` (Backend direkt); mit Frontend zusätzlich `http://localhost:3097/api/health` +- Prod: `curl http://localhost:8004/api/health` (Backend direkt); mit Frontend zusätzlich `http://localhost:3004/api/health` --- diff --git a/docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md b/docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md new file mode 100644 index 0000000..3c937a1 --- /dev/null +++ b/docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md @@ -0,0 +1,38 @@ +# Architecture Decision Proposal + +Status: Entwurf +Stand: YYYY-MM-DD +Autor: + +--- + +## Problem + +Was ist die konkrete Entscheidungssituation? + +## Betroffene Regel + +Welches verbindliche Dokument oder Prinzip (Gate, Foundation, Spec) ist betroffen? + +## Optionen + +| Option | Kurzbeschreibung | Pro | Contra | +|--------|------------------|-----|--------| +| A | | | | +| B | | | | + +## Empfehlung + +Welche Option und warum? + +## Risiko + +Was kann schiefgehen? + +## Rückbaubarkeit + +Wie lässt sich die Entscheidung rückgängig machen oder später verfeinern? + +## Auswirkung auf Sprint 0 + +Betrifft das den Sprint-0-Scope oder nur spätere Arbeitspakete? diff --git a/docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md b/docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md new file mode 100644 index 0000000..27a04c3 --- /dev/null +++ b/docs/architecture/Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md @@ -0,0 +1,200 @@ +# Jinkendo Foundation +## Minimum Viable Foundation v0.2 + +Status: Arbeitsfassung +Stand: 2026-07-04 +Zweck: Minimale produktübergreifende Grundlage, soweit sie für Jinkendo Kairo Sprint 0 zwingend relevant ist. + +--- + +## 1. Zweck + +Diese Minimum Viable Foundation beschreibt nur die produktübergreifenden Prinzipien, die Kairo von Anfang an korrekt berücksichtigen muss. + +Sie ist keine vollständige Zielarchitektur der Jinkendo-Produktfamilie. + +Sie erzwingt keine Konvergenz von Mitai und Shinkan. + +Sie verhindert nur, dass Kairo grundlegende Plattformkonzepte falsch oder isoliert implementiert. + +--- + +## 2. Geltungsbereich + +Gilt für Kairo Sprint 0 und spätere neue Jinkendo-Produkte, soweit dieselben Prinzipien relevant sind. + +Nicht Gegenstand: + +- Umbau von Mitai +- Umbau von Shinkan +- zentrale Familienarchitektur +- Billing +- SSO +- vollständige Shared Packages + +--- + +## 3. Drift-Schutz + +Ein Prinzip wird nur Sprint-0-verbindlich, wenn alle drei Fragen mit „Ja“ beantwortet werden: + +1. Verhindert es später teures strukturelles Refactoring in Kairo? +2. Ist es für Sprint 0 zwingend erforderlich? +3. Kann es minimal umgesetzt werden, ohne Kairo zu überladen? + +Alles andere kommt ins Architektur-Backlog. + +--- + +## 4. Verbindliche Foundation-Prinzipien für Kairo Sprint 0 + +### F-01 Tenant-first + +Kairo ist mandantenfähig zu modellieren. + +Alle fachlichen Objekte benötigen Tenant-Bezug oder einen expliziten globalen Scope. + +### F-02 Actor-first + +Operative Verantwortung wird Actors zugeordnet. + +Actors können Menschen, KI-Agenten, Arbeitsgruppen oder externe Systeme sein. + +### F-03 TenantContext pro Request + +Jeder geschützte Request muss einen aufgelösten Kontext besitzen: + +- User +- Tenant +- Actor +- Rollen +- Capabilities + +### F-04 Auth, Capability, Feature und Governance trennen + +Kairo vermischt nicht: + +- Auth: Wer ist eingeloggt? +- Capability: Darf diese Funktion genutzt werden? +- Feature: Welche Produktfunktion ist betroffen? +- Governance: Darf dieses Objekt gesehen oder verändert werden? + +Feature-Limits und Billing werden nur vorbereitet, nicht in Sprint 0 vollständig gebaut. + +### F-05 Registry-first + +Neue steuerungsrelevante Funktionen registrieren sich an zentralen Registries. + +Mindestens: + +- Rights / Capability Registry +- Feature Registry +- Prompt Registry +- Configuration Registry + +### F-06 Prompts nicht hardcoden + +Produktive interne Prompts liegen nicht hart im Code. + +Sprint 0 benötigt: + +- Prompt Template +- Prompt Version +- Placeholder +- Kontextart +- Admin-Scope + +### F-07 Platzhalter kontrollieren + +Platzhalter sind typisiert, dokumentiert und werden validiert. + +Fehlende Pflichtplatzhalter führen zu kontrollierten Fehlern. + +### F-08 Keine hardcodierte fachliche Konfiguration + +Nicht hardcodieren: + +- Rollen +- Rechte +- Prompts +- fachliche Statuslisten +- Feature-Flags mit fachlicher Bedeutung +- Workflowparameter +- AI-Modellzuordnung + +Technische Defaults sind erlaubt, müssen aber dokumentiert und später überschreibbar sein. + +### F-09 Audit für Admin- und Agentenaktionen + +Administrative und agentenbezogene Aktionen müssen auditierbar sein. + +Sprint 0 benötigt ein einfaches AuditLog-Modell. + +### F-10 Nummerierte Migrationen und Fail-Fast-Startup + +Schemaänderungen laufen über nummerierte Migrationen. + +Fehlgeschlagene Migrationen dürfen den App-Start blockieren. + +--- + +## 5. Bewusst nicht Sprint-0-verbindlich + +Nicht in Sprint 0: + +- vollständige Entitlement-/Billing-Engine +- Usage-Zähler +- Tiers +- Pläne +- Coupons +- SSO +- vollständige Mitai Prompt Engine +- Shinkan Access Layer mit Vereinslogik +- Widget Dashboard +- Data Layer nach Mitai-Vorbild +- Import-Framework +- Media Assets +- Content Reports +- Maturity Models + +--- + +## 6. Referenzprinzipien + +### Aus Mitai relevant + +- Prompt Templates nicht hardcoden +- zentrale Prompt-Ausführung als Zielbild +- Registry-Muster +- Feature-Registry als Konzept +- Migration/Deploy-Standard +- Auth-Session-Basis + +### Aus Shinkan relevant + +- TenantContext +- Trennung von Portalrolle und fachlicher Rolle +- Rights Registry +- Capabilities vs. Features +- Entitlements-Snapshot als API-Idee +- schmale AI Prompt Runtime als Sprint-0-kompatibles Vorbild + +--- + +## 7. Kairo-spezifische Entscheidung + +Kairo übernimmt keine allgemeine Familienarchitektur. + +Kairo übernimmt nur: + +```text +Tenant + Actor + TenantContext +Capability Registry +Feature Registry minimal +Prompt Registry minimal +Placeholder validiert +Audit +Migration/Deploy +``` + +Alles andere ist späteres Architektur-Backlog. diff --git a/docs/architecture/Kairo_Architecture_References_v0.1.md b/docs/architecture/Kairo_Architecture_References_v0.1.md index 272637a..4cfd6a5 100644 --- a/docs/architecture/Kairo_Architecture_References_v0.1.md +++ b/docs/architecture/Kairo_Architecture_References_v0.1.md @@ -12,6 +12,12 @@ Stand: 2026-07-04 5. `docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md` 6. `CLAUDE.md` 7. `.cursor/rules/kairo-architecture.mdc` +8. `docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md` + +## Hilfsdokumente + +- `docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md` +- `infra/README.md` — Platzhalter für serverseitige Infra-Ergänzungen ## Referenzdokumente diff --git a/docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md b/docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md new file mode 100644 index 0000000..405d6df --- /dev/null +++ b/docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md @@ -0,0 +1,105 @@ +# Jinkendo Kairo +## Sprint-0 Principle Gate v0.1 + +Status: verbindlicher Scope-Filter für Sprint 0 +Stand: 2026-07-04 + +--- + +## 1. Zweck + +Dieses Dokument verhindert Drift. + +Es legt fest, welche Prinzipien aus Mitai/Shinkan/Family-Alignment für Kairo Sprint 0 verbindlich sind und welche ausdrücklich nicht umgesetzt werden. + +--- + +## 2. Entscheidungsregel + +Ein Prinzip ist nur dann Sprint-0-verbindlich, wenn alle drei Fragen mit Ja beantwortet werden: + +1. Verhindert es später teures strukturelles Refactoring? +2. Ist es für Sprint 0 zwingend erforderlich? +3. Kann es minimal umgesetzt werden, ohne Kairo zu überladen? + +--- + +## 3. Sprint-0-verbindliche Prinzipien + +| ID | Prinzip | Sprint-0-Konsequenz | +|---|---|---| +| G-01 | Tenant-first | Tenant ist Kernobjekt; keine Single-User-Architektur | +| G-02 | Actor-first | Assignments gehen an Actors, nicht direkt an User | +| G-03 | TenantContext pro Request | Auth-Kontext wird zentral aufgelöst | +| G-04 | Auth / Capability / Feature / Governance trennen | Keine Vermischung von Login, Rollen, Limits und Objektzugriff | +| G-05 | Rights Registry | Rechte/Capabilities werden registriert, nicht verstreut hardcodiert | +| G-06 | Feature Registry minimal | Features sind bekannt und benannt, auch wenn Limits später kommen | +| G-07 | Prompt Registry minimal | Prompts sind administrierbare Templates, nicht Code-Strings | +| G-08 | Platzhalter validieren | Pflichtplatzhalter dürfen nicht stillschweigend fehlen | +| G-09 | Config statt Hardcoding | Fachliche Konfiguration nicht im Code verdrahten | +| G-10 | Audit | Admin- und Agentenaktionen werden protokolliert | +| G-11 | Migration Standard | Nummerierte SQL-Migrationen + Tracking | +| G-12 | Fail-Fast Startup | App startet nicht mit fehlerhaftem Schema | + +--- + +## 4. Nur vorbereiten, nicht vollständig bauen + +| Thema | Sprint-0-Umfang | +|---|---| +| Entitlements | Minimaler `/me/entitlements` Snapshot: tenant, actor, roles, capabilities | +| Feature Limits | Datenmodell optional vorbereiten; keine Usage-Zähler | +| Prompt Engine | Templates, Versionen, Platzhalter; keine Workflows/Pipelines | +| Admin UI | Minimal prüfbar; keine ausgereifte Oberfläche | +| MCP | Schnittstellen später; keine MCP-Implementierung in Sprint 0 | +| AI Agenten | Actor Type Agent vorbereiten; keine vollständige Agentensteuerung | + +--- + +## 5. Explizit nicht Sprint 0 + +- Billing +- Tiers +- Pläne +- Coupons +- Usage-Zähler +- SSO +- Mitai Data Layer +- Mitai Widget Dashboard +- Universal Import +- Shinkan Exercise Catalog +- Shinkan Training Planning +- Shinkan Media Assets +- Shinkan Content Reports +- Shinkan Maturity Models +- vollständige Familien-Konvergenz +- Lebensmanager-/Seichō-Fachlogik + +--- + +## 6. Review-Regel + +Jeder neue Vorschlag im Sprint wird mit einer Kategorie versehen: + +```text +A – Sprint-0-verbindlich +B – Architektur-Backlog +C – Produktspezifisch / ignorieren +``` + +Nur Kategorie A darf in Sprint 0 umgesetzt werden. + +--- + +## 7. Abweichungsregel + +Ein Coding-Agent darf nicht eigenmächtig von diesem Gate abweichen. + +Bei notwendiger Abweichung erstellt er ein Architecture Decision Proposal mit: + +- Problem +- betroffene Regel +- vorgeschlagene Änderung +- Risiko +- Alternative +- Empfehlung diff --git a/docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md b/docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md new file mode 100644 index 0000000..308ff44 --- /dev/null +++ b/docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md @@ -0,0 +1,448 @@ +# Jinkendo Kairo +## Product Spec v0.2 + +Status: Arbeitsfassung +Stand: 2026-07-04 +Zweck: Konsolidierte Produktspezifikation für den Start des neuen Kairo-Repositories. + +--- + +## 1. Produktidentität + +**Jinkendo Kairo** ist der operative Program Director der Jinkendo-Produktfamilie. + +Kairo steuert Vorhaben, Programme, Projekte, Meilensteine, Maßnahmen, Backlogs, Reviews, Nachweise und die Zusammenarbeit von Menschen, Arbeitsgruppen und KI-Agenten. + +**Leitfrage:** + +> Welcher nächste Schritt bringt ein Vorhaben aktuell am wirkungsvollsten voran? + +--- + +## 2. Produktgrenze + +Kairo ist kein klassischer Projektmanager, keine reine ToDo-App und kein reines Softwareentwicklungstool. + +Kairo ist auch kein Lebenssinn-, Identitäts- oder Gesundheitsprodukt. + +### Kairo verantwortet + +- Vorhaben +- Programme +- Projekte +- Meilensteine +- Maßnahmen +- Backlog Items +- Nachweise +- Reviews +- Entscheidungen +- Blocker +- Actor-Zuweisungen +- Arbeitsgruppen +- KI-Agenten +- operative Priorisierung +- Umsetzungstransparenz + +### Kairo verantwortet nicht + +- persönliche Identität +- Lebensvisionen +- persönliche Entwicklungsmodelle +- Gesundheitsdaten +- Trainingsfachlogik +- Wissensmanagement +- Ernährungslogik +- Skill- oder Reifegradmodelle aus Shinkan + +Diese Domänen gehören zu anderen Jinkendo-Produkten wie mindnet, Mitai, Shinkan und perspektivisch Seichō. + +--- + +## 3. Abgrenzung zu Seichō + +**Seichō** verantwortet persönliche Entwicklung. + +**Kairo** verantwortet operative Umsetzung. + +Beispiel: + +- Seichō: „Ich möchte als Vater präsenter werden.“ +- Kairo: „Welche konkreten Vorhaben, Meilensteine und Maßnahmen setzen das um?“ + +Kairo darf persönliche Entwicklungsziele aus Seichō operativ unterstützen, aber nicht die persönliche Entwicklungslogik selbst übernehmen. + +--- + +## 4. Stable Core und Agile Edge + +### Stable Core + +Der Stable Core wird nur bewusst und selten geändert. + +Er umfasst: + +- Fachsprache +- Kernobjekte +- Produktgrenzen +- Actor-Modell +- Tenant-Modell +- Backlog-Modell +- Meilensteinmodell +- Rechte-/Capability-Grundmodell +- Prompt-/Registry-Grundmodell +- Änderungsregeln +- Agentenregeln + +### Agile Edge + +Der Agile Edge darf iterativ wachsen. + +Er umfasst: + +- UI +- Dashboards +- Reports +- MCP-Tools +- Integrationen +- Automationen +- Priorisierungsstrategien +- Visualisierungen +- Komfortfunktionen + +--- + +## 5. Ubiquitous Language + +| Begriff | Bedeutung | +|---|---| +| Vision | Langfristig gewünschter Zustand; primär außerhalb Kairo | +| Vorhaben | Aktiv verfolgte Veränderung oder angestrebtes Ergebnis | +| Programm | Bündel zusammengehöriger Projekte | +| Projekt | Zeitlich begrenzte Ergebniserzeugung | +| Meilenstein | Überprüfbarer Zielzustand, keine Aufgabe | +| Maßnahme | Kleinste operative Arbeitseinheit mit eindeutigem Ergebnis | +| Backlog Item | Noch nicht verbindlich priorisierter Handlungsbedarf | +| Review | Strukturierte Bewertung eines Scopes | +| Nachweis | Beleg für Fortschritt, Abschluss oder Entscheidung | +| Entscheidung | Festgehaltene Auswahl zwischen Optionen | +| Blocker | Hindernis, das Fortschritt verhindert oder stark erschwert | +| Actor | Mensch, KI-Agent, Arbeitsgruppe oder externes System | +| Assignment | Zuweisung einer Maßnahme an einen oder mehrere Actors | +| Tenant | Abgegrenzter Nutzungsraum, z. B. Person, Familie, Team, Organisation | +| TenantContext | Aufgelöster Request-Kontext mit User, Tenant, Actor, Rollen und Capabilities | +| Capability | Funktionale Erlaubnis: „Darf Actor X diese Funktion nutzen?“ | +| Feature | Registrierte Produktfunktion, optional später mit Limit/Kontingent | +| Governance | Objektbezogene Zugriffslogik: „Darf Actor X dieses Objekt sehen/ändern?“ | + +Neue Begriffe werden nur eingeführt, wenn bestehende Begriffe fachlich nicht ausreichen. + +--- + +## 6. Grundstruktur + +```text +Tenant + └── Actor + └── Vorhaben + ├── Programm + │ └── Projekt + │ ├── Meilenstein + │ ├── Maßnahme + │ └── Backlog Item + ├── Meilenstein + ├── Maßnahme + ├── Backlog Item + ├── Nachweis + └── Review +``` + +Nicht jedes Vorhaben benötigt Programme oder Projekte. + +Kleine Vorhaben können direkt Meilensteine, Maßnahmen und Backlog Items besitzen. + +--- + +## 7. Kernobjekte + +### Tenant + +Ein Tenant ist ein abgegrenzter Nutzungsraum. + +Beispiele: + +- persönlicher Arbeitsraum +- Familie +- Verein +- Team +- Organisation +- Projektverbund + +### User + +Ein User ist ein authentifizierter Account. + +Ein User kann Mitglied mehrerer Tenants sein. + +### Actor + +Ein Actor ist die operative Zurechnungseinheit. + +Actor Types: + +- Human +- Agent +- WorkingGroup +- ExternalSystem + +Alle Assignments erfolgen an Actors, nicht direkt an User. + +### Vorhaben + +Ein Vorhaben beschreibt eine aktiv verfolgte Veränderung oder ein angestrebtes Ergebnis. + +Es besitzt mindestens: + +- Titel +- Zielzustand +- Status +- Priorität +- verantwortliche Actors +- Bezug zu Maßnahmen, Meilensteinen, Backlog und Reviews + +### Programm + +Ein Programm bündelt zusammengehörige Projekte oder Vorhabenanteile. + +Programme sind optional. + +### Projekt + +Ein Projekt erzeugt ein konkretes Ergebnis innerhalb eines Vorhabens oder Programms. + +### Meilenstein + +Ein Meilenstein beschreibt einen überprüfbaren Zielzustand. + +Er ist keine Aufgabe. + +Mögliche Status: + +- geplant +- aktiv +- gefährdet +- erreicht +- verschoben +- verworfen + +### Maßnahme + +Eine Maßnahme ist die kleinste operative Arbeitseinheit. + +Mögliche Status: + +- offen +- bereit +- in Arbeit +- blockiert +- Review erforderlich +- erledigt +- verworfen + +### Backlog Item + +Backlog Items entstehen aus Ideen, Bugs, Risiken, Verbesserungen oder Agentenvorschlägen. + +Sie verpflichten nicht automatisch zur Umsetzung. + +### Nachweis + +Nachweise belegen Fortschritt oder Abschluss. + +Beispiele: + +- Commit +- Dokument +- Link +- Datei +- Messwert +- Foto +- Review-Ergebnis +- externe Bestätigung + +### Review + +Ein Review bewertet einen Scope und erzeugt ggf. Entscheidungen, Maßnahmen oder Backlog Items. + +### Blocker + +Ein Blocker verhindert oder gefährdet Fortschritt. + +Blocker müssen sichtbar, zugeordnet und reviewbar sein. + +--- + +## 8. Actor- und Assignment-Modell + +Actors können Menschen, KI-Agenten, Arbeitsgruppen oder externe Systeme sein. + +Unterstützte Assignment-Modi: + +- Exclusive: genau ein verantwortlicher Actor +- Collaborative: mehrere Actors arbeiten gemeinsam +- Pool: eine Arbeitsgruppe oder ein Pool kann übernehmen +- Proposed: Zuweisungsvorschlag, noch nicht angenommen +- Auction: Actors bewerben sich oder übernehmen bewusst + +Agenten sind keine Sonderfälle außerhalb des Modells. + +--- + +## 9. Multiuser und Mandantenfähigkeit + +Multiuser ist Kernfunktion, keine spätere Erweiterung. + +Kairo unterstützt: + +- private Vorhaben +- Familienvorhaben +- Teamvorhaben +- Organisationsvorhaben +- Arbeitsgruppen +- KI-Agenten innerhalb eines Tenants +- objektbezogene Sichtbarkeit und Bearbeitbarkeit + +Jede fachliche Entität ist tenantfähig zu modellieren. + +--- + +## 10. Rechte, Capabilities und Governance + +Kairo trennt: + +```text +Auth = Wer ist eingeloggt? +Capability = Darf Actor diese Funktion nutzen? +Feature = Welche Produktfunktion ist betroffen? +Governance = Darf Actor dieses konkrete Objekt sehen/ändern? +``` + +Feature-Limits, Usage-Zähler, Billing, Tiers und Pläne sind nicht Bestandteil von Sprint 0, werden aber im Modell nicht verbaut. + +--- + +## 11. Prompt- und AI-Grundmodell + +Kairo nutzt intern keine hardcodierten Produktionsprompts. + +Mindestmodell: + +- Prompt Templates in DB oder konfigurierbarer Persistenz +- Prompt Versions +- typisierte Platzhalter +- Kontextarten +- Adminbarkeit +- Preview/Testmöglichkeit später +- zentrale Prompt-Ausführung als Fassade + +Sprint 0 baut keine komplexe Workflow- oder Pipeline-Engine. + +--- + +## 12. Registry-Grundmodell + +Neue Funktionen registrieren sich an zentralen Steuerungsschichten. + +Mindestens vorgesehen: + +- Rights / Capability Registry +- Feature Registry +- Prompt Registry +- Configuration Registry +- Audit Events + +Ziel: keine verstreuten, unsichtbaren Sonderlogiken. + +--- + +## 13. Agentenregeln + +Agenten dürfen: + +- Kontext anfragen +- nächste Maßnahme abfragen +- Maßnahmen claimen +- Fortschritt melden +- Blocker melden +- Backlog Items vorschlagen +- Nachweise einreichen +- Reviews anfordern + +Agenten dürfen nicht ohne Freigabe: + +- strategische Ziele ändern +- Vorhaben löschen +- Meilensteine verschieben +- Rechte oder Rollen ändern +- Prompts produktiv ändern +- Admin-Konfiguration ändern +- private Daten anderer Actors auswerten + +--- + +## 14. Änderungsregime + +Änderungen werden klassifiziert als: + +| Ebene | Bedeutung | +|---|---| +| Operativ | Maßnahmen, Status, Nachweise, Kommentare | +| Taktisch | Prioritäten, Meilensteinzuschnitt, Assignment-Modus | +| Strategisch | Produktgrenze, Stable Core, grundlegende Ziele oder Rechte | + +Strategische Änderungen benötigen menschliche Freigabe. + +--- + +## 15. MVP-Zielbild + +Der MVP umfasst nach Sprint 0 schrittweise: + +- Tenant/User/Actor +- Vorhaben +- Projekte +- Meilensteine +- Maßnahmen +- Backlog +- Assignments +- Arbeitsgruppen +- Agentenstatus +- Reviews +- Nachweise +- Blocker +- Vorschlag der nächsten sinnvollen Maßnahme +- MCP-Minimalintegration für Coding-Agenten + +--- + +## 16. Nicht-Ziele des MVP + +Nicht Teil des MVP: + +- vollständige Mitai-Integration +- vollständige Shinkan-Integration +- vollständige mindnet-Integration +- vollständige Seichō-Integration +- vollständiges Billing +- zentrales SSO +- komplexe Prompt-Workflows +- KI-Autopilot für strategische Entscheidungen +- vollständiger Lebensmanager +- Gesundheits- oder Trainingsdomänenlogik + +--- + +## 17. Leitregel + +Kairo steuert Umsetzung. + +Sinn, Identität und persönliche Entwicklung verbleiben außerhalb seiner fachlichen Verantwortung. diff --git a/docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md b/docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md new file mode 100644 index 0000000..19f93fb --- /dev/null +++ b/docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md @@ -0,0 +1,466 @@ +# Jinkendo Kairo +## Dokument 04 – Sprint 0 Foundation v0.3 + +Status: ausführbares Sprint-0-Arbeitspaket +Stand: 2026-07-04 +Zweck: Vibe-Coder-fähiger Sprint-0-Scope für das neue Kairo-Repository. + +--- + +## 1. Sprintziel + +Sprint 0 schafft das technische und fachliche Fundament für Kairo. + +Nach Sprint 0 existiert noch kein vollständiger Program Director. + +Aber die Anwendung besitzt die notwendige Plattformstruktur, damit spätere Fachfunktionen nicht falsch aufgebaut werden. + +--- + +## 2. Sprint-Leitfrage + +> Ist Kairo von Anfang an mandantenfähig, actor-basiert, registry-basiert, promptfähig, auditierbar und nicht hardcoded? + +--- + +## 3. Verbindliche Leitdokumente + +1. `Jinkendo_Kairo_Product_Spec_v0.2.md` +2. `Jinkendo_Foundation_Minimum_Viable_Foundation_v0.2.md` +3. `Kairo_Sprint0_Principle_Gate_v0.1.md` +4. `CLAUDE.md` +5. `.cursor/rules/kairo-architecture.mdc` + +--- + +## 4. Scope + +Sprint 0 umfasst: + +- Repository und Grundarchitektur +- lokale Entwicklungsumgebung +- Datenbank und Migrationen +- Tenant-Modell +- User-Grundmodell +- Actor-Modell +- TenantMembership +- TenantContext +- Rollen-/Capability-Grundmodell +- Rights / Capability Registry +- minimale Feature Registry +- minimale Prompt Registry +- Prompt Templates +- Prompt Versions +- Placeholder-Modell +- Configuration-Grundmodell +- AuditLog +- minimaler `/api/me/entitlements` Snapshot +- minimale Admin-Prüfbarkeit +- technische Tests + +--- + +## 5. Nicht-Scope + +Nicht Teil von Sprint 0: + +- Vorhabenverwaltung +- Projekte +- Meilensteine +- Maßnahmen +- Backlog +- MCP-Server +- Program-Director-Logik +- komplexe AI-Workflows +- Prompt-Pipelines +- Billing +- SSO +- Usage-Zähler +- Mitai-/Shinkan-Integration +- Seichō-Lebensmanagerlogik +- produktionsreifes Designsystem +- mobile App + +--- + +## 6. Empfohlener Technologie-Start + +Diese Empfehlung ist pragmatisch und darf durch ein Architecture Decision Proposal angepasst werden. + +```text +Backend: FastAPI +Frontend: React oder Next.js +DB: PostgreSQL +Migration: nummerierte SQL-Dateien + schema_migrations +Deployment lokal: Docker Compose +Auth Sprint 0: einfache serverseitige Session oder vorbereitete Auth-Fassade +``` + +Wichtig: Die Technologieentscheidung darf die Architekturprinzipien nicht verletzen. + +--- + +## 7. Kernobjekte Sprint 0 + +### Tenant + +- id +- name +- slug +- status +- created_at +- updated_at + +### User + +- id +- email +- display_name +- status +- created_at +- updated_at + +### TenantMembership + +- id +- tenant_id +- user_id +- roles +- status + +### Actor + +- id +- tenant_id +- actor_type +- display_name +- linked_user_id optional +- status +- capabilities optional + +Actor Types: + +- Human +- Agent +- WorkingGroup +- ExternalSystem + +### Role + +- id +- tenant_id optional +- key +- name +- description +- scope +- status + +### Capability + +- id +- key +- module +- description +- status + +### Feature + +- id +- key +- module +- description +- status + +### PromptTemplate + +- id +- tenant_id optional +- feature_key optional +- key +- name +- purpose +- context_kind +- status +- active_version_id + +### PromptVersion + +- id +- prompt_template_id +- version +- body +- created_by +- created_at +- changelog + +### Placeholder + +- id +- key +- type +- description +- required +- source +- context_kind optional + +### ConfigurationEntry + +- id +- tenant_id optional +- key +- value +- scope +- description + +### AuditLog + +- id +- tenant_id optional +- actor_id optional +- action +- entity_type +- entity_id +- timestamp +- metadata + +--- + +## 8. Minimaler TenantContext + +Jeder geschützte Request soll perspektivisch auflösen: + +```text +user_id +tenant_id +actor_id +global_roles +tenant_roles +capabilities +``` + +Sprint 0 darf dies technisch minimal umsetzen, aber die Fassade muss vorhanden sein. + +--- + +## 9. Minimaler Entitlements Snapshot + +Endpoint: + +```text +GET /api/me/entitlements +``` + +Mindestantwort: + +```json +{ + "tenant": { + "tenant_id": "...", + "name": "..." + }, + "actor": { + "actor_id": "...", + "actor_type": "Human" + }, + "roles": [], + "capabilities": {}, + "features": {}, + "enforcement": { + "capabilities": "probe", + "features": "probe" + } +} +``` + +Keine Billing-/Usage-Logik in Sprint 0. + +--- + +## 10. Arbeitspakete + +### AP0.1 – Projektgrundlage + +- Repo-Struktur +- Backend-Grundgerüst +- Frontend-Grundgerüst optional +- Docker Compose +- PostgreSQL +- Migration Runner +- Health Endpoint +- Testbasis + +### AP0.2 – Tenant, User, Actor + +- Tenant +- User +- Membership +- Actor +- WorkingGroup Actor +- Agent Actor +- Seed für lokalen Admin + +### AP0.3 – Auth, TenantContext, Capabilities + +- einfache Auth-Fassade +- TenantContext-Fassade +- Role +- Capability +- Rights Registry +- zentrale Capability-Prüfung + +### AP0.4 – Prompt, Feature, Config Registry + +- Feature Registry +- PromptTemplate +- PromptVersion +- Placeholder +- ConfigurationEntry +- einfaches Prompt Rendering mit Placeholder Validation + +### AP0.5 – Audit und minimale Admin-Prüfbarkeit + +- AuditLog +- Audit bei Admin-/Registry-/Prompt-Änderungen +- minimale Admin-Routen oder Admin-Seite +- `/api/me/entitlements` + +--- + +## 11. User Stories + +### S0-01 – Projektgrundlage + +Als Entwickler möchte ich eine lauffähige Projektstruktur, damit Kairo iterativ erweitert werden kann. + +Akzeptanzkriterien: + +- Projekt kann lokal gestartet werden. +- Datenbank läuft lokal. +- Migrationen laufen reproduzierbar. +- Health Endpoint antwortet. +- Tests können ausgeführt werden. + +### S0-02 – Tenant anlegen + +Als Platform Admin möchte ich Tenants verwalten, damit abgegrenzte Nutzungsräume entstehen. + +Akzeptanzkriterien: + +- Tenant kann angelegt werden. +- Tenant kann deaktiviert werden. +- Tenant besitzt eindeutigen Slug. +- Fachliche Entitäten können Tenant-Bezug herstellen. + +### S0-03 – User und Membership + +Als Tenant Admin möchte ich User einem Tenant zuordnen. + +Akzeptanzkriterien: + +- User kann mehreren Tenants angehören. +- Membership besitzt Rollen. +- Deaktivierte Membership verliert Zugriff. + +### S0-04 – Actor-Modell + +Als System möchte ich Menschen, Agenten und Arbeitsgruppen einheitlich als Actors behandeln. + +Akzeptanzkriterien: + +- Human Actor kann mit User verknüpft werden. +- Agent Actor kann ohne User existieren. +- WorkingGroup Actor kann angelegt werden. +- Actor ist tenantbezogen. + +### S0-05 – Capability Registry + +Als Entwickler möchte ich Capabilities registrieren, damit Rechte nicht verstreut hardcodiert werden. + +Akzeptanzkriterien: + +- Capability kann registriert werden. +- Capability besitzt Key, Modul und Beschreibung. +- Registry kann in DB synchronisiert oder persistiert werden. +- Fehlende Capability führt zu kontrolliertem Fehler. + +### S0-06 – Prompt Registry + +Als Superadmin möchte ich Prompt Templates administrieren können. + +Akzeptanzkriterien: + +- Prompt Template kann angelegt werden. +- Prompt Version kann angelegt werden. +- Aktive Version ist bestimmbar. +- Template kann gerendert werden. +- Pflichtplatzhalter werden validiert. + +### S0-07 – Audit + +Als Betreiber möchte ich kritische Aktionen nachvollziehen können. + +Akzeptanzkriterien: + +- Änderungen an Tenant, Role, Capability, Feature, Prompt und Config erzeugen Audit Events. +- Audit Event enthält Actor, Tenant, Aktion, Entity und Zeitpunkt. + +### S0-08 – Entitlements Snapshot + +Als Frontend möchte ich einen Berechtigungssnapshot laden. + +Akzeptanzkriterien: + +- `/api/me/entitlements` liefert Tenant, Actor, Rollen, Capabilities und Features. +- Keine UI muss Rollenlogik selbst ableiten. + +--- + +## 12. Definition of Done Sprint 0 + +Sprint 0 gilt als abgeschlossen, wenn: + +- Anwendung lokal lauffähig ist. +- Migrationen funktionieren. +- Tenant/User/Actor/Membership implementiert sind. +- TenantContext-Fassade existiert. +- Capability Registry existiert. +- Feature Registry minimal existiert. +- PromptTemplate/PromptVersion/Placeholder existieren. +- Prompt Rendering validiert Pflichtplatzhalter. +- AuditLog existiert. +- `/api/me/entitlements` existiert. +- Es gibt Tests für die Kernmodelle. +- README, CLAUDE.md und Cursor-Regel sind aktuell. +- Keine fachlichen Prompts, Rollen oder Rechte sind verstreut hardcodiert. + +--- + +## 13. Verbotene Vereinfachungen + +Ein Coding-Agent darf nicht: + +- Tenant entfernen +- Actor durch reines User-Modell ersetzen +- Capabilities durch Ad-hoc-Rollenchecks ersetzen +- Prompts hardcoden +- Placeholder-Validation weglassen +- Feature Registry weglassen +- AuditLog weglassen +- Vorhaben/Projektlogik in Sprint 0 vorziehen +- Billing/Usage-Limits in Sprint 0 ausbauen +- Shinkan-Club-Logik oder Mitai-Tier-Logik kopieren + +--- + +## 14. Übergang zu Sprint 1 + +Sprint 1 startet erst, wenn Sprint 0 das Fundament trägt. + +Sprint 1 baut dann: + +- Vorhaben +- Programme optional +- Projekte +- erste Übersicht +- Statusmodell +- Basis-Governance für Vorhaben diff --git a/docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md b/docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md new file mode 100644 index 0000000..c5f6a95 --- /dev/null +++ b/docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md @@ -0,0 +1,167 @@ +# Jinkendo Kairo +## Sprint 0 – AP0.1 Projektgrundlage v0.1 + +Status: erster ausführbarer Coding-Auftrag +Stand: 2026-07-04 + +--- + +## 1. Auftrag + +Erstelle die technische Projektgrundlage für Jinkendo Kairo. + +AP0.1 baut noch keine fachliche Kairo-Domäne. + +Ziel ist ein lauffähiges, testbares, migrationsfähiges Grundgerüst. + +--- + +## 2. Verbindliche Leitplanken + +Vor Umsetzung lesen: + +- `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +- `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +- `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +- `CLAUDE.md` +- `.cursor/rules/kairo-architecture.mdc` + +--- + +## 3. Scope AP0.1 + +Erstelle: + +```text +backend/ +frontend/ optional minimal +infra/ +scripts/ +tests/ +``` + +Mindestumfang: + +- Backend-Skeleton +- FastAPI-App oder begründete Alternative +- Health Endpoint +- PostgreSQL-Anbindung +- Docker Compose lokal +- Migrationsverzeichnis +- Migration Runner +- `schema_migrations` Tracking +- erste Migration `001_init_core.sql` +- Testbasis +- README-Abschnitt „Local Development“ + +--- + +## 4. Nicht-Scope AP0.1 + +Nicht implementieren: + +- Tenant/User/Actor-Fachlogik über minimale Schema-Vorbereitung hinaus +- vollständige Auth +- Prompt Engine +- Feature Registry +- Capability Registry +- Vorhaben +- Projekte +- Maßnahmen +- Backlog +- MCP +- Billing +- UI-Komplexität + +--- + +## 5. Migrationsanforderungen + +Migrationen liegen unter: + +```text +backend/migrations/ +``` + +Namensschema: + +```text +001_init_core.sql +002_*.sql +003_*.sql +``` + +Pflicht: + +- `schema_migrations` Tracking +- lexikographische Ausführung +- bereits ausgeführte Migrationen überspringen +- Fehler blockiert App-Start oder Setup-Command +- keine ad-hoc DDL in Routern + +--- + +## 6. Health Endpoint + +Mindestendpoint: + +```text +GET /api/health +``` + +Antwortbeispiel: + +```json +{ + "status": "ok", + "app": "jinkendo-kairo", + "db": "ok" +} +``` + +--- + +## 7. Tests + +Mindestens: + +- App importierbar +- Health Endpoint antwortet +- Migration Runner erkennt Migrationen +- Migration Runner trägt ausgeführte Migration ein +- wiederholter Migration Run ist idempotent + +--- + +## 8. Akzeptanzkriterien + +AP0.1 ist abgeschlossen, wenn: + +- Projekt lokal startbar ist. +- Datenbank lokal startbar ist. +- Migrationen laufen. +- Health Endpoint funktioniert. +- Tests laufen. +- README beschreibt Setup. +- Keine fachliche Sprint-1-Logik wurde vorgezogen. +- Offene technische Entscheidungen sind dokumentiert. + +--- + +## 9. Erwarteter Abschlussbericht + +Der Coding-Agent liefert am Ende: + +```markdown +## Umgesetzt + +## Tests + +## Lokaler Start + +## Offene Fragen + +## Abweichungen von der Spezifikation + +## Empfehlung für AP0.2 +``` diff --git a/docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md b/docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md new file mode 100644 index 0000000..3c68574 --- /dev/null +++ b/docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md @@ -0,0 +1,112 @@ +# Jinkendo Kairo +## Sprint 0 – Vibe-Coder Handover v0.1 + +Status: Übergabedokument +Stand: 2026-07-04 + +--- + +## 1. Auftrag + +Baue nicht sofort den Program Director. + +Baue zuerst das Fundament, damit der Program Director später sauber entstehen kann. + +Sprint 0 ist erfolgreich, wenn Kairo technisch und fachlich vorbereitet ist für: + +- Mandanten +- Actors +- Rechte/Capabilities +- Prompts +- Platzhalter +- Konfiguration +- Audit +- spätere KI-Agenten + +--- + +## 2. Verbindliche Dokumente + +Lies in dieser Reihenfolge: + +1. `README.md` +2. `docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md` +3. `docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md` +4. `docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md` +5. `CLAUDE.md` +6. `.cursor/rules/kairo-architecture.mdc` + +--- + +## 3. Arbeitsprinzip + +Du darfst technische Details ausarbeiten. + +Du darfst keine fachlichen Architekturprinzipien ändern. + +Wenn eine Änderung notwendig erscheint, erstelle ein Architecture Decision Proposal. + +--- + +## 4. Harte Guardrails + +Nicht erlaubt: + +- Kairo als Single-User-App bauen +- Tenant weglassen +- Actor durch User ersetzen +- Agenten als Sonderlogik außerhalb Actor-Modell behandeln +- Rollen/Rechte hardcoden +- Prompts hardcoden +- Feature Registry weglassen +- Prompt Registry weglassen +- Placeholder-Validation weglassen +- Audit weglassen +- Vorhaben/Projektlogik vor Sprint-0-Abschluss implementieren +- Billing/Usage-Limits vorziehen +- Mitai- oder Shinkan-Domänenlogik kopieren + +--- + +## 5. Erwartete Arbeitsweise + +Für jedes Arbeitspaket: + +1. Kurzplan erstellen +2. Implementieren +3. Tests ergänzen +4. README oder relevante Doku aktualisieren +5. Abweichungen dokumentieren +6. Offene Fragen klar markieren + +--- + +## 6. Architecture Decision Proposal Format + +```markdown +# Architecture Decision Proposal + +## Problem + +## Betroffene Regel + +## Optionen + +## Empfehlung + +## Risiko + +## Rückbaubarkeit + +## Auswirkung auf Sprint 0 +``` + +--- + +## 7. AP0.1 zuerst + +Der erste Auftrag ist ausschließlich AP0.1 – Projektgrundlage. + +Kein Sprint-0-Gesamtbau. + +Kein Vorziehen von AP0.2–AP0.5 ohne Review. diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..1132462 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,7 @@ +# Infra + +Deployment- und Server-bezogene Ergänzungen (Reverse-Proxy-Snippets, NAS-Mount-Hinweise, Umgebungs-spezifische Notizen). + +**Hauptdoku:** [docs/DEPLOYMENT.md](../docs/DEPLOYMENT.md) + +Aktuell leer — bewusst minimal in AP0.1. Docker Compose liegt im Repository-Root. diff --git a/scripts/load/README.md b/scripts/load/README.md index 1394019..c869b98 100644 --- a/scripts/load/README.md +++ b/scripts/load/README.md @@ -1,6 +1,6 @@ # k6 Load-Tests (Phase 0) -Parallele `/health`-Requests gegen Dev oder Prod. +Parallele `/api/health`-Requests gegen Dev oder Prod. ## Lokal / CI diff --git a/scripts/load/k6-health-baseline.js b/scripts/load/k6-health-baseline.js index 9324153..62e9dd1 100644 --- a/scripts/load/k6-health-baseline.js +++ b/scripts/load/k6-health-baseline.js @@ -1,5 +1,5 @@ /** - * Phase-0-Baseline: parallele GET /health (kein Auth). + * Phase-0-Baseline: parallele GET /api/health (kein Auth). * BASE_URL optional, z. B. https://dev.kairo.jinkendo.de */ import http from 'k6/http' @@ -25,7 +25,7 @@ export const options = { const BASE = (__ENV.BASE_URL || 'https://dev.kairo.jinkendo.de').replace(/\/$/, '') export function health() { - const res = http.get(`${BASE}/health`, { tags: { scenario: 'health' } }) + const res = http.get(`${BASE}/api/health`, { tags: { scenario: 'health' } }) check(res, { 'health 2xx': (r) => r.status >= 200 && r.status < 300, }) From 0e5bf72dee1e593c80b25e337b181611fa9c9728 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:15:43 +0200 Subject: [PATCH 03/10] AP0.1: Backend, Frontend minimal, Tests und CI fuer Dev/Prod-Deploy. Co-authored-by: Cursor --- .env.example | 50 +---- .gitea/workflows/deploy-dev.yml | 30 +-- .gitea/workflows/deploy-prod.yml | 25 ++- .gitea/workflows/test.yml | 293 +++++++++------------------ CLAUDE.md | 2 +- README.md | 37 +++- backend/Dockerfile | 16 ++ backend/README.md | 14 -- backend/db.py | 38 ++++ backend/main.py | 71 +++++++ backend/migrations/001_init_core.sql | 12 ++ backend/requirements-dev.txt | 3 + backend/requirements.txt | 5 + backend/run_migrations.py | 198 ++++++++++++++++++ backend/tests/test_health.py | 30 +++ backend/tests/test_migrations.py | 57 ++++++ backend/version.py | 3 + docker-compose.dev-env.yml | 34 ++-- docker-compose.yml | 44 ++-- docs/DEPLOYMENT.md | 115 +++-------- frontend/Dockerfile | 15 ++ frontend/README.md | 13 -- frontend/index.html | 12 ++ frontend/nginx.conf | 22 ++ frontend/package.json | 19 ++ frontend/src/App.jsx | 31 +++ frontend/src/app.css | 30 +++ frontend/src/main.jsx | 10 + frontend/vite.config.js | 12 ++ tests/smoke-health.spec.js | 9 + 30 files changed, 828 insertions(+), 422 deletions(-) create mode 100644 backend/Dockerfile delete mode 100644 backend/README.md create mode 100644 backend/db.py create mode 100644 backend/main.py create mode 100644 backend/migrations/001_init_core.sql create mode 100644 backend/requirements-dev.txt create mode 100644 backend/requirements.txt create mode 100644 backend/run_migrations.py create mode 100644 backend/tests/test_health.py create mode 100644 backend/tests/test_migrations.py create mode 100644 backend/version.py create mode 100644 frontend/Dockerfile delete mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/nginx.conf create mode 100644 frontend/package.json create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/app.css create mode 100644 frontend/src/main.jsx create mode 100644 frontend/vite.config.js create mode 100644 tests/smoke-health.spec.js diff --git a/.env.example b/.env.example index 8d38b4a..5405d19 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,7 @@ -# === .env neben der jeweiligen docker-compose.*.yml kopieren ==================== -# Docker Compose ersetzt ${VARIABLE} beim Start. -# -# Pro Umgebung eigene Datei (z. B. ~/docker/kairo/.env für Prod, -# ~/docker/kairo-dev/.env für Dev) — dieselben SCHLÜSSEL, unterschiedliche Werte. -# Kein separates DEV_APP_URL vs APP_URL: immer APP_URL, ALLOWED_ORIGINS, DB_*, … +# === .env neben docker-compose.*.yml ========================================== +# Pro Umgebung eigene Datei (~/docker/kairo/.env Prod, ~/docker/kairo-dev/.env Dev) -# ─── Typische Werte PROD (docker-compose.yml) ───────────────────────────────── -# DB_NAME=kairo -# DB_USER=kairo_user -# DB_PASSWORD=… -# APP_URL=https://kairo.jinkendo.de -# ALLOWED_ORIGINS=https://kairo.jinkendo.de -# ENVIRONMENT=production - -# ─── Typische Werte DEV (docker-compose.dev-env.yml) ───────────────────────── +# ─── DEV (docker-compose.dev-env.yml) ──────────────────────────────────────── # DB_NAME=kairo_dev # DB_USER=kairo_dev # DB_PASSWORD=dev_password @@ -21,40 +9,10 @@ # ALLOWED_ORIGINS=https://dev.kairo.jinkendo.de,http://192.168.2.49:3097 # ENVIRONMENT=development -# ─── Ab hier: eine ausfüllbare Vorlage (bei uns meist Prod-Defaults) ─────────── -DB_HOST=postgres -DB_PORT=5432 +# ─── PROD (docker-compose.yml) ─────────────────────────────────────────────── DB_NAME=kairo DB_USER=kairo_user DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD - -OPENROUTER_API_KEY=your_api_key_here -OPENROUTER_MODEL=anthropic/claude-sonnet-4 - -# Vereins-Kontingente hart blockieren (KI-Kosten!). Nur 1, true oder yes aktivieren. -CLUB_FEATURE_ENFORCE=1 - -# KI-Debug (Docker): KAIRO_AI_DEBUG in docker-compose*.yml angebunden — 1 = ausführliche WARN-Logs. -# KAIRO_AI_DEBUG=1 - -SMTP_HOST=smtp.example.com -SMTP_PORT=587 -SMTP_USER=noreply@jinkendo.de -SMTP_PASS=your_smtp_password -SMTP_FROM=noreply@jinkendo.de -SMTP_SSL= -SMTP_STARTTLS= - -AUTO_ADMIN_FIRST_USER=true -ADMIN_BOOTSTRAP_EMAILS= - APP_URL=https://kairo.jinkendo.de ALLOWED_ORIGINS=https://kairo.jinkendo.de ENVIRONMENT=production - -# ─── Medien (optional, derzeit nicht aktiv) ─────────────────────────────────── -# Kairo benötigt aktuell keine Medien-Speicherung. Falls später nötig: -# 1. NAS-Freigabe auf dem Pi mounten (nicht lokal auf dem Raspberry!) -# 2. docker-compose.override.yml mit Bind-Mount ergänzen (siehe docs/DEPLOYMENT.md) -# KAIRO_MEDIA_HOST=/mnt/nas/kairo-media -# MEDIA_ROOT=/app/media diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 9554674..00cb0bb 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -12,21 +12,21 @@ jobs: run: | set -e echo "=== Deploying Kairo to DEVELOPMENT ===" - cd /home/lars/docker/kairo-dev - git fetch origin develop || git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git . - git reset --hard origin/develop - docker compose -f docker-compose.dev-env.yml build --no-cache - docker compose -f docker-compose.dev-env.yml up -d - sleep 5 - if ! curl -sf http://localhost:8097/api/health; then - echo "✗ DEV API nicht erreichbar — Backend-Logs (Migration/Startup):" - docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true - exit 1 - fi - echo "✓ DEV API /api/health OK" - if docker compose -f docker-compose.dev-env.yml ps --status running 2>/dev/null | grep -q frontend; then - curl -sf http://localhost:3097/api/health && echo "✓ DEV über Frontend-Nginx healthy" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo-dev" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b develop "$REPO" . else - echo "(Frontend-Check übersprungen — optional in AP0.1)" + git fetch origin develop + git checkout develop + git reset --hard origin/develop + fi + docker compose -f docker-compose.dev-env.yml build --no-cache + docker compose -f docker-compose.dev-env.yml up -d --wait + curl -sf http://localhost:8097/api/health && echo "✓ DEV API /api/health OK" + if docker compose -f docker-compose.dev-env.yml ps --status running 2>/dev/null | grep -q frontend; then + curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" fi echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index b5cef74..79380b8 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -12,16 +12,21 @@ jobs: run: | set -e echo "=== Deploying Kairo to PRODUCTION ===" - cd /home/lars/docker/kairo - git fetch origin main || git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git . - git reset --hard origin/main - docker compose build --no-cache - docker compose up -d - sleep 5 - curl -sf http://localhost:8004/api/health && echo "✓ PROD API (direkt) /api/health OK" - if docker compose ps --status running 2>/dev/null | grep -q frontend; then - curl -sf http://localhost:3004/api/health && echo "✓ PROD über Frontend-Nginx healthy" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b main "$REPO" . else - echo "(Frontend-Check übersprungen — optional in AP0.1)" + git fetch origin main + git checkout main + git reset --hard origin/main + fi + docker compose build --no-cache + docker compose up -d --wait + curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" + if docker compose ps --status running 2>/dev/null | grep -q frontend; then + curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" fi echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 5be58b3..fe623db 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,11 +1,11 @@ name: Test Suite -# develop: push/PR → Tests gegen Dev (parallel oder vor Deploy Development). -# main: kein push/PR-Trigger — vermeidet doppelten Dev-Lauf beim Merge develop→main; -# Prod-Tests nur via workflow_run nach erfolgreichem Deploy Production. +# push develop/main → compose-smoke (frischer Build auf dem Runner) +# workflow_run nach Deploy → pytest/k6/playwright gegen laufende Instanz +# pull_request → compose-smoke on: push: - branches: [develop] + branches: [develop, main] pull_request: branches: [develop] workflow_run: @@ -13,39 +13,65 @@ on: types: [completed] jobs: + compose-smoke: + if: github.event_name == 'push' || github.event_name == 'pull_request' + runs-on: ubuntu-latest + env: + DB_PASSWORD: ci_smoke_password + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Compose Stack bauen und testen + run: | + set -e + if [ "${{ github.ref_name }}" = "main" ]; then + COMPOSE="docker-compose.yml" + API_PORT=8004 + UI_PORT=3004 + else + COMPOSE="docker-compose.dev-env.yml" + API_PORT=8097 + UI_PORT=3097 + fi + + echo "=== Smoke: $COMPOSE ===" + docker compose -f "$COMPOSE" up -d --build --wait + + curl -sf "http://localhost:${API_PORT}/api/health" | tee /tmp/health-api.json + echo "✓ Backend /api/health OK" + + curl -sf "http://localhost:${UI_PORT}/api/health" | tee /tmp/health-ui.json + echo "✓ Frontend-Proxy /api/health OK" + + docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt + docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short + echo "✓ pytest OK" + + docker compose -f "$COMPOSE" down -v + pytest-backend: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest steps: - name: Backend pytest im deployten Container run: | set -e - EVENT_NAME="${{ github.event_name }}" - REF_NAME="${{ github.ref_name }}" - BASE_REF="${{ github.base_ref }}" RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - APP_DIR="/home/lars/docker/kairo" - COMPOSE_FILE="docker-compose.yml" - - if [ "$EVENT_NAME" = "workflow_run" ]; then - if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then - APP_DIR="/home/lars/docker/kairo-dev" - COMPOSE_FILE="docker-compose.dev-env.yml" - fi - elif [ "$REF_NAME" = "develop" ] || [ "$BASE_REF" = "develop" ]; then + if [ "$RUN_WORKFLOW" = "Deploy Production" ]; then + APP_DIR="/home/lars/docker/kairo" + COMPOSE_FILE="docker-compose.yml" + else APP_DIR="/home/lars/docker/kairo-dev" COMPOSE_FILE="docker-compose.dev-env.yml" fi cd "$APP_DIR" - echo "Warte auf stabilen backend-Container …" for i in $(seq 1 60); do if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then - echo "Backend bereit (Versuch $i)" break fi if [ "$i" -eq 60 ]; then - echo "Timeout: backend-Container nicht bereit" docker compose -f "$COMPOSE_FILE" ps || true docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true exit 1 @@ -54,116 +80,80 @@ jobs: done docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " - pip install -q pytest httpx 2>/dev/null || pip install -q -r /app/requirements-dev.txt && - cd /app && + pip install -q -r requirements-dev.txt && python -m pytest tests -ra -vv --tb=short " lint-backend: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + if: github.event_name == 'push' || github.event_name == 'pull_request' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') runs-on: ubuntu-latest steps: - - name: Check backend syntax - run: | - EVENT_NAME="${{ github.event_name }}" - REF_NAME="${{ github.ref_name }}" - RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - APP_DIR="/home/lars/docker/kairo" + - name: Checkout repository + if: github.event_name != 'workflow_run' + uses: actions/checkout@v4 - if [ "$EVENT_NAME" = "workflow_run" ]; then - if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then - APP_DIR="/home/lars/docker/kairo-dev" - fi - elif [ "$REF_NAME" = "develop" ]; then + - name: Check backend syntax (Checkout) + if: github.event_name != 'workflow_run' + run: python3 -m py_compile backend/main.py backend/run_migrations.py backend/db.py + + - name: Check backend syntax (Deploy-Pfad) + if: github.event_name == 'workflow_run' + run: | + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + if [ "$RUN_WORKFLOW" = "Deploy Production" ]; then + APP_DIR="/home/lars/docker/kairo" + else APP_DIR="/home/lars/docker/kairo-dev" fi - python3 -m py_compile "$APP_DIR/backend/main.py" echo "✓ Backend syntax OK" build-frontend: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Build frontend run: | - EVENT_NAME="${{ github.event_name }}" - REF_NAME="${{ github.ref_name }}" - RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - APP_DIR="/home/lars/docker/kairo" - - if [ "$EVENT_NAME" = "workflow_run" ]; then - if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then - APP_DIR="/home/lars/docker/kairo-dev" - fi - elif [ "$REF_NAME" = "develop" ]; then - APP_DIR="/home/lars/docker/kairo-dev" - fi - - cd "$APP_DIR/frontend" - if [ ! -f package.json ]; then - echo "Frontend noch nicht vorhanden — übersprungen (AP0.1 optional)" - exit 0 - fi + cd frontend npm install npm run build echo "✓ Frontend build OK" k6-health-baseline: name: k6 /api/health Baseline - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest - env: - E2E_TARGET_URL: https://dev.kairo.jinkendo.de steps: - name: Checkout repository uses: actions/checkout@v4 - - name: E2E-Ziel wählen (Dev über Proxy vs. Production) - id: e2e + - name: k6 Ziel wählen (localhost auf Runner) + id: k6 run: | - EVENT="${{ github.event_name }}" - WF_NAME="${{ github.event.workflow_run.name }}" - DEV_BASE="${{ env.E2E_TARGET_URL }}" - if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then - echo "mode=prod" >> $GITHUB_OUTPUT - echo "base_url=https://kairo.jinkendo.de" >> $GITHUB_OUTPUT - echo "→ k6 gegen Prod-Basis." + if [ "${{ github.event.workflow_run.name }}" = "Deploy Production" ]; then + echo "base_url=http://localhost:8004" >> $GITHUB_OUTPUT else - echo "mode=dev" >> $GITHUB_OUTPUT - echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT - echo "→ k6 gegen Dev (${DEV_BASE})." + echo "base_url=http://localhost:8097" >> $GITHUB_OUTPUT fi - - name: Dev /api/health abwarten - if: ${{ steps.e2e.outputs.mode == 'dev' }} + - name: /api/health abwarten run: | - BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/api/health …" - for i in $(seq 1 90); do + BASE="${{ steps.k6.outputs.base_url }}" + for i in $(seq 1 60); do if curl -sf "$BASE/api/health" >/dev/null 2>&1; then echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done - echo "Timeout: Dev /api/health nicht erreichbar — Deploy / DNS / Firewall prüfen." - curl -v "$BASE/api/health" || true - exit 1 - - - name: Prod /api/health abwarten - if: ${{ steps.e2e.outputs.mode == 'prod' }} - run: | - BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/api/health …" - for i in $(seq 1 60); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Health OK (Versuch $i)" - exit 0 - fi - sleep 5 - done - echo "Timeout: Prod /api/health nicht erreichbar" curl -v "$BASE/api/health" || true exit 1 @@ -177,26 +167,18 @@ jobs: aarch64|arm64) K6_ARCH=arm64 ;; *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; esac - echo "Installing k6 ${K6_VER} linux-${K6_ARCH}" curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz tar -xzf /tmp/k6.tgz -C /tmp sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - k6 version - - name: k6 Health-Baseline (parallele /api/health) + - name: k6 Health-Baseline env: - BASE_URL: ${{ steps.e2e.outputs.base_url }} - run: | - set -e - echo "k6 gegen BASE_URL=$BASE_URL" - k6 run scripts/load/k6-health-baseline.js - echo "✓ k6 Health-Baseline passed" + BASE_URL: ${{ steps.k6.outputs.base_url }} + run: k6 run scripts/load/k6-health-baseline.js - playwright-tests: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + playwright-smoke: + if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest - env: - E2E_TARGET_URL: https://dev.kairo.jinkendo.de steps: - name: Checkout repository uses: actions/checkout@v4 @@ -204,111 +186,34 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: "20" - - name: E2E-Ziel wählen (Dev über Proxy vs. Production) - id: e2e + - name: Playwright Ziel wählen + id: pw run: | - EVENT="${{ github.event_name }}" - WF_NAME="${{ github.event.workflow_run.name }}" - DEV_BASE="${{ env.E2E_TARGET_URL }}" - if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then - echo "mode=prod" >> $GITHUB_OUTPUT - echo "base_url=https://kairo.jinkendo.de" >> $GITHUB_OUTPUT - echo "→ Prod. Secrets: E2E_PROD_TEST_EMAIL / E2E_PROD_TEST_PASSWORD." + if [ "${{ github.event.workflow_run.name }}" = "Deploy Production" ]; then + echo "base_url=http://localhost:3004" >> $GITHUB_OUTPUT else - echo "mode=dev" >> $GITHUB_OUTPUT - echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT - echo "→ Deployte Dev-Umgebung (${DEV_BASE}). Secrets: E2E_DEV_TEST_EMAIL / E2E_DEV_TEST_PASSWORD." + echo "base_url=http://localhost:3097" >> $GITHUB_OUTPUT fi - - name: Dev /api/health abwarten - if: ${{ steps.e2e.outputs.mode == 'dev' }} + - name: /api/health abwarten run: | - BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/api/health …" - for i in $(seq 1 90); do + BASE="${{ steps.pw.outputs.base_url }}" + for i in $(seq 1 60); do if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Health OK (Versuch $i)" exit 0 fi sleep 2 done - echo "Timeout: Dev /api/health nicht erreichbar — Deploy / DNS / Firewall prüfen." - curl -v "$BASE/api/health" || true exit 1 - - name: Prod /api/health abwarten - if: ${{ steps.e2e.outputs.mode == 'prod' }} - run: | - BASE="${{ steps.e2e.outputs.base_url }}" - echo "Warte auf $BASE/api/health …" - for i in $(seq 1 60); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Health OK (Versuch $i)" - exit 0 - fi - sleep 5 - done - echo "Timeout: Prod /api/health nicht erreichbar" - curl -v "$BASE/api/health" || true - exit 1 - - - name: Testnutzer registrieren (Dev, nur wenn möglich) - if: ${{ steps.e2e.outputs.mode == 'dev' }} - env: - E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }} - E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }} - run: | - BASE="${{ steps.e2e.outputs.base_url }}" - if [ -z "$E2E_DEV_TEST_EMAIL" ] || [ -z "$E2E_DEV_TEST_PASSWORD" ]; then - echo "(Registrierung übersprungen — Secrets E2E_DEV_* nicht gesetzt.)" - exit 0 - fi - curl -sf -X POST "$BASE/api/auth/register" \ - -H "Content-Type: application/json" \ - -d "{\"email\":\"${E2E_DEV_TEST_EMAIL}\",\"password\":\"${E2E_DEV_TEST_PASSWORD}\",\"name\":\"Playwright CI\"}" \ - || echo "(Register evtl. schon erfolgt oder Limits — Login-Test gilt trotzdem.)" - - name: Install Playwright run: | - npm ci || npm install + npm install npx playwright install --with-deps chromium - - name: Run Playwright tests + - name: Run smoke tests env: - E2E_DEV_TEST_EMAIL: ${{ secrets.E2E_DEV_TEST_EMAIL }} - E2E_DEV_TEST_PASSWORD: ${{ secrets.E2E_DEV_TEST_PASSWORD }} - run: | - set -e - MODE="${{ steps.e2e.outputs.mode }}" - BASE_URL="${{ steps.e2e.outputs.base_url }}" - export PLAYWRIGHT_BASE_URL="$BASE_URL" - - if [ "$MODE" = "prod" ]; then - export TEST_EMAIL="${{ secrets.E2E_PROD_TEST_EMAIL }}" - export TEST_PASSWORD="${{ secrets.E2E_PROD_TEST_PASSWORD }}" - if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then - echo "Fehler: E2E_PROD_TEST_EMAIL und E2E_PROD_TEST_PASSWORD setzen." - exit 1 - fi - else - export TEST_EMAIL="$E2E_DEV_TEST_EMAIL" - export TEST_PASSWORD="$E2E_DEV_TEST_PASSWORD" - if [ -z "$TEST_EMAIL" ] || [ -z "$TEST_PASSWORD" ]; then - echo "Fehler: E2E_DEV_TEST_EMAIL und E2E_DEV_TEST_PASSWORD setzen (Playwright soll gegen Dev einloggen)." - exit 1 - fi - fi - - mkdir -p screenshots - npx playwright test - echo "✓ Playwright tests passed" - - - name: Upload test screenshots - if: failure() - uses: actions/upload-artifact@v3 - with: - name: playwright-screenshots - path: screenshots/ - retention-days: 7 + PLAYWRIGHT_BASE_URL: ${{ steps.pw.outputs.base_url }} + run: npx playwright test tests/smoke-health.spec.js diff --git a/CLAUDE.md b/CLAUDE.md index 63cee2a..de0a3f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,4 +117,4 @@ Bei notwendiger Abweichung erstelle ein Architecture Decision Proposal. ## 8. Aktueller erster Auftrag -AP0.1 – Projektgrundlage. +AP0.1 – Projektgrundlage (Backend, Frontend minimal, Tests, Deploy). diff --git a/README.md b/README.md index c4b9323..439f92b 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,30 @@ Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. -## Deployment +## Local Development -Auto-Deploy via Gitea Actions auf dem Raspberry Pi (Ports 3004/8004 Prod · 3097/8097 Dev). +Voraussetzungen: Docker + Docker Compose. -Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) +```bash +git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git +cd Kairo-Jinkendo +git checkout develop + +# Dev-Stack (PostgreSQL + Backend + Frontend) +docker compose -f docker-compose.dev-env.yml up --build + +# Health prüfen +curl http://localhost:8097/api/health +curl http://localhost:3097/api/health + +# Tests im Backend-Container +docker compose -f docker-compose.dev-env.yml exec backend pip install -r requirements-dev.txt +docker compose -f docker-compose.dev-env.yml exec backend python -m pytest tests -ra -vv +``` + +UI: http://localhost:3097 · API: http://localhost:8097 + +Deployment (Pi): [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) ## AP0.1 – Stand Projektgrundlage @@ -76,11 +95,7 @@ Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) |---------|--------| | Git + Gitea (`develop` / `main`) | erledigt | | Docker Compose (Prod + Dev) | erledigt | -| Gitea Actions (Deploy + Test) | erledigt, an Kairo angepasst | -| Sprint-0-Spezifikationen | im Repo | -| Designprinzipien-Referenz | im Repo | -| Backend-Skeleton (FastAPI, Migrationen, Tests) | **offen** | -| Frontend minimal | optional, **offen** | -| README „Local Development“ | folgt mit Backend-Skeleton | - -Nächster Schritt: Rest von AP0.1 gemäß `docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md`. +| Backend (FastAPI, Migrationen, `/api/health`) | erledigt | +| Frontend minimal (React + nginx Proxy) | erledigt | +| pytest (Health + Migrationen) | erledigt | +| Gitea Actions (Deploy + Test) | erledigt | diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..1b87504 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.12-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +ENV PIP_DEFAULT_TIMEOUT=120 +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index b14904a..0000000 --- a/backend/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Backend - -FastAPI-Anwendung — noch anzulegen. - -Erwartete Struktur (analog shinkan-jinkendo): - -``` -backend/ -├── Dockerfile -├── main.py -├── requirements.txt -├── migrations/ -└── routers/ -``` diff --git a/backend/db.py b/backend/db.py new file mode 100644 index 0000000..d94520c --- /dev/null +++ b/backend/db.py @@ -0,0 +1,38 @@ +"""PostgreSQL connection helpers.""" + +import os + +import psycopg2 +from psycopg2.extensions import connection + + +def db_params() -> dict[str, str]: + return { + "host": os.getenv("DB_HOST", "localhost"), + "port": os.getenv("DB_PORT", "5432"), + "dbname": os.getenv("DB_NAME", "kairo_dev"), + "user": os.getenv("DB_USER", "kairo_dev"), + "password": os.getenv("DB_PASSWORD", "dev_password"), + } + + +def get_connection() -> connection: + p = db_params() + return psycopg2.connect( + host=p["host"], + port=p["port"], + database=p["dbname"], + user=p["user"], + password=p["password"], + ) + + +def check_db() -> bool: + conn = get_connection() + try: + with conn.cursor() as cur: + cur.execute("SELECT 1") + cur.fetchone() + return True + finally: + conn.close() diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..3aabe41 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,71 @@ +"""Jinkendo Kairo — FastAPI application entry point.""" + +from __future__ import annotations + +import os +import sys + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from db import check_db +from version import APP_NAME, APP_VERSION, DB_SCHEMA_VERSION + +if os.getenv("SKIP_DB_MIGRATE", "").strip().lower() in ("1", "true", "yes"): + print("[SKIP_DB_MIGRATE] Migrationen übersprungen") +else: + import run_migrations + + exit_code = run_migrations.main() + if exit_code != 0: + print(f"[FAIL] Migrationen fehlgeschlagen (Exit {exit_code})") + sys.exit(exit_code) + +allowed_origins = [ + origin.strip() + for origin in os.getenv("ALLOWED_ORIGINS", "http://localhost:3097").split(",") + if origin.strip() +] + +app = FastAPI( + title="Jinkendo Kairo", + version=APP_VERSION, + docs_url="/api/docs" if os.getenv("ENVIRONMENT", "development") != "production" else None, + redoc_url=None, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=allowed_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/api/health") +def api_health(): + db_status = "ok" + try: + if not check_db(): + db_status = "error" + except Exception: + db_status = "error" + + status = "ok" if db_status == "ok" else "degraded" + return { + "status": status, + "app": APP_NAME, + "db": db_status, + "version": APP_VERSION, + "schema": DB_SCHEMA_VERSION, + } + + +@app.get("/api/version") +def api_version(): + return { + "app": APP_NAME, + "version": APP_VERSION, + "schema": DB_SCHEMA_VERSION, + } diff --git a/backend/migrations/001_init_core.sql b/backend/migrations/001_init_core.sql new file mode 100644 index 0000000..b6ff9b4 --- /dev/null +++ b/backend/migrations/001_init_core.sql @@ -0,0 +1,12 @@ +-- AP0.1: minimale Schema-Vorbereitung (keine Tenant/User/Actor-Fachlogik) + +CREATE TABLE IF NOT EXISTS kairo_app_meta ( + id SERIAL PRIMARY KEY, + key VARCHAR(128) UNIQUE NOT NULL, + value TEXT NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +INSERT INTO kairo_app_meta (key, value) +VALUES ('schema_phase', 'ap0.1') +ON CONFLICT (key) DO NOTHING; diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..ea71c80 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +pytest==8.3.4 +httpx==0.27.2 diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..dfd7b31 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.111.0 +uvicorn[standard]==0.29.0 +psycopg2-binary==2.9.9 +sqlparse>=0.5.0 +pydantic==2.7.1 diff --git a/backend/run_migrations.py b/backend/run_migrations.py new file mode 100644 index 0000000..9de7202 --- /dev/null +++ b/backend/run_migrations.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Apply numbered SQL migrations with schema_migrations tracking.""" + +from __future__ import annotations + +import os +import re +import shutil +import subprocess +import sys +import time +from typing import List, Tuple + +import psycopg2 +import sqlparse + +from db import db_params, get_connection + +_LEADING_DIGITS = re.compile(r"^(\d+)") + + +def init_migrations_table(conn) -> None: + with conn.cursor() as cur: + cur.execute( + """ + CREATE TABLE IF NOT EXISTS schema_migrations ( + id SERIAL PRIMARY KEY, + migration VARCHAR(255) UNIQUE NOT NULL, + executed_at TIMESTAMP DEFAULT NOW() + ) + """ + ) + conn.commit() + + +def _migration_sort_key(stem: str) -> Tuple[int, str]: + match = _LEADING_DIGITS.match(stem) + return (int(match.group(1)) if match else 0, stem) + + +def migration_files(migrations_dir: str) -> List[Tuple[str, str]]: + rows: List[Tuple[str, str]] = [] + for filename in os.listdir(migrations_dir): + if not filename.endswith(".sql") or not filename[0].isdigit(): + continue + stem = filename[:-4] + rows.append((stem, os.path.join(migrations_dir, filename))) + rows.sort(key=lambda item: _migration_sort_key(item[0])) + return rows + + +def executed_migrations(conn) -> set[str]: + with conn.cursor() as cur: + cur.execute("SELECT migration FROM schema_migrations") + return {row[0] for row in cur.fetchall()} + + +def pending_migrations(conn, migrations_dir: str) -> List[Tuple[str, str]]: + done = executed_migrations(conn) + return [(name, path) for name, path in migration_files(migrations_dir) if name not in done] + + +def _split_statements(sql_text: str) -> List[str]: + parts = sqlparse.split(sql_text.strip()) + return [part.strip() for part in parts if part and part.strip()] + + +def _run_with_psql(filepath: str) -> tuple[bool, str]: + psql = shutil.which("psql") + if not psql: + return False, "" + + p = db_params() + env = os.environ.copy() + env["PGPASSWORD"] = str(p["password"]) + cmd = [ + psql, + "-h", + p["host"], + "-p", + str(p["port"]), + "-U", + p["user"], + "-d", + p["dbname"], + "-v", + "ON_ERROR_STOP=1", + "-1", + "-f", + filepath, + ] + proc = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=7200) + if proc.returncode != 0: + tail = ((proc.stderr or "") + "\n" + (proc.stdout or "")).strip() + return False, tail[:8000] or f"exit {proc.returncode}" + return True, (proc.stdout or "").strip() + + +def _record_migration(conn, migration_name: str) -> None: + with conn.cursor() as cur: + cur.execute( + """ + INSERT INTO schema_migrations (migration) + VALUES (%s) + ON CONFLICT (migration) DO NOTHING + """, + (migration_name,), + ) + + +def run_migration(conn, migration_name: str, filepath: str) -> bool: + print(f"Running migration: {migration_name}") + try: + if shutil.which("psql"): + ok, diag = _run_with_psql(filepath) + if not ok: + print(f" [FAIL] psql:\n{diag or '(kein Output)'}") + conn.rollback() + return False + else: + with open(filepath, "r", encoding="utf-8") as handle: + body = handle.read() + statements = _split_statements(body) + with conn.cursor() as cur: + for stmt in statements: + cur.execute(stmt) + + _record_migration(conn, migration_name) + conn.commit() + print(f" [OK] {migration_name}") + return True + except Exception as exc: + conn.rollback() + print(f" [FAIL] {migration_name}: {exc}") + return False + + +def connect_with_retry(max_retries: int = 30): + p = db_params() + for attempt in range(max_retries): + try: + conn = get_connection() + conn.autocommit = False + print(f"[OK] Connected to database: {p['dbname']}") + return conn + except psycopg2.OperationalError: + if attempt >= max_retries - 1: + raise + print(f"Waiting for database... ({attempt + 1}/{max_retries})") + time.sleep(2) + + +def migrations_directory() -> str: + docker_path = "/app/migrations" + if os.path.isdir(docker_path): + return docker_path + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "migrations") + + +def main() -> int: + print("=" * 60) + print("Jinkendo Kairo — Database Migrations") + print("=" * 60) + + migrations_dir = migrations_directory() + if not os.path.isdir(migrations_dir): + print(f"[FAIL] migrations directory missing: {migrations_dir}") + return 1 + + try: + conn = connect_with_retry() + init_migrations_table(conn) + pending = pending_migrations(conn, migrations_dir) + + if not pending: + print("[OK] Keine ausstehenden Migrationen.") + conn.close() + return 0 + + print(f"{len(pending)} ausstehende Migration(en):") + for name, _ in pending: + print(f" - {name}") + + for migration_name, filepath in pending: + if not run_migration(conn, migration_name, filepath): + conn.close() + return 1 + + conn.close() + print("[OK] Migrationen abgeschlossen.") + return 0 + except Exception as exc: + print(f"[FAIL] {exc}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..1a0c7f0 --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,30 @@ +import os + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture() +def client(monkeypatch): + monkeypatch.setenv("SKIP_DB_MIGRATE", "1") + import importlib + + import main as main_module + + importlib.reload(main_module) + return TestClient(main_module.app) + + +def test_app_importable(): + import main + + assert main.app is not None + + +def test_health_endpoint(client): + response = client.get("/api/health") + assert response.status_code == 200 + payload = response.json() + assert payload["app"] == "jinkendo-kairo" + assert payload["status"] in {"ok", "degraded"} + assert payload["db"] in {"ok", "error"} diff --git a/backend/tests/test_migrations.py b/backend/tests/test_migrations.py new file mode 100644 index 0000000..3b6c5c2 --- /dev/null +++ b/backend/tests/test_migrations.py @@ -0,0 +1,57 @@ +"""Migration runner tests (require PostgreSQL).""" + +import os +from pathlib import Path + +import psycopg2 +import pytest + +import run_migrations + + +def _db_available() -> bool: + try: + conn = psycopg2.connect( + host=os.getenv("DB_HOST", "localhost"), + port=os.getenv("DB_PORT", "5432"), + dbname=os.getenv("DB_NAME", "kairo_dev"), + user=os.getenv("DB_USER", "kairo_dev"), + password=os.getenv("DB_PASSWORD", "dev_password"), + ) + conn.close() + return True + except psycopg2.OperationalError: + return False + + +pytestmark = pytest.mark.skipif(not _db_available(), reason="PostgreSQL nicht erreichbar") + + +def test_migration_runner_finds_migrations(): + migrations_dir = run_migrations.migrations_directory() + files = run_migrations.migration_files(migrations_dir) + names = [name for name, _ in files] + assert "001_init_core" in names + + +def test_migration_runner_is_idempotent(): + assert run_migrations.main() == 0 + assert run_migrations.main() == 0 + + conn = run_migrations.connect_with_retry(max_retries=5) + executed = run_migrations.executed_migrations(conn) + conn.close() + assert "001_init_core" in executed + + +def test_core_table_exists(): + conn = run_migrations.connect_with_retry(max_retries=5) + with conn.cursor() as cur: + cur.execute( + """ + SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = 'public' AND table_name = 'kairo_app_meta' + """ + ) + assert cur.fetchone()[0] == 1 + conn.close() diff --git a/backend/version.py b/backend/version.py new file mode 100644 index 0000000..033eae6 --- /dev/null +++ b/backend/version.py @@ -0,0 +1,3 @@ +APP_VERSION = "0.1.0-ap0.1" +DB_SCHEMA_VERSION = "001" +APP_NAME = "jinkendo-kairo" diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 647987e..2643696 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -12,6 +12,11 @@ services: - dev-kairo-db-data:/var/lib/postgresql/data ports: - "5437:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-kairo_dev} -d ${DB_NAME:-kairo_dev}"] + interval: 5s + timeout: 5s + retries: 10 restart: unless-stopped networks: - dev-kairo-network @@ -26,22 +31,26 @@ services: DB_NAME: "${DB_NAME:-kairo_dev}" DB_USER: "${DB_USER:-kairo_dev}" DB_PASSWORD: "${DB_PASSWORD:-dev_password}" - OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} - OPENROUTER_MODEL: ${OPENROUTER_MODEL} - KAIRO_AI_DEBUG: "${KAIRO_AI_DEBUG:-}" - SMTP_HOST: ${SMTP_HOST} - SMTP_PORT: ${SMTP_PORT} - SMTP_USER: ${SMTP_USER} - SMTP_PASS: ${SMTP_PASS} - SMTP_FROM: ${SMTP_FROM} APP_URL: "${APP_URL:-https://dev.kairo.jinkendo.de}" - ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.kairo.jinkendo.de,http://192.168.2.49:3097}" + ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.kairo.jinkendo.de,http://192.168.2.49:3097,http://localhost:3097}" ENVIRONMENT: "${ENVIRONMENT:-development}" - CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}" ports: - "8097:8000" depends_on: - - postgres + postgres: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')", + ] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s restart: unless-stopped networks: - dev-kairo-network @@ -55,7 +64,8 @@ services: ports: - "3097:80" depends_on: - - backend + backend: + condition: service_healthy restart: unless-stopped networks: - dev-kairo-network diff --git a/docker-compose.yml b/docker-compose.yml index 7724240..5961fa6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,11 +7,16 @@ services: environment: POSTGRES_DB: "${DB_NAME:-kairo}" POSTGRES_USER: "${DB_USER:-kairo_user}" - POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_PASSWORD: "${DB_PASSWORD:-change_me}" volumes: - kairo-db-data:/var/lib/postgresql/data ports: - "127.0.0.1:5436:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-kairo_user} -d ${DB_NAME:-kairo}"] + interval: 5s + timeout: 5s + retries: 10 restart: unless-stopped networks: - kairo-network @@ -24,29 +29,29 @@ services: environment: DB_HOST: postgres DB_PORT: 5432 - DB_NAME: kairo - DB_USER: kairo_user - DB_PASSWORD: ${DB_PASSWORD} - OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} - OPENROUTER_MODEL: ${OPENROUTER_MODEL} - KAIRO_AI_DEBUG: "${KAIRO_AI_DEBUG:-}" - SMTP_HOST: ${SMTP_HOST} - SMTP_PORT: ${SMTP_PORT} - SMTP_USER: ${SMTP_USER} - SMTP_PASS: ${SMTP_PASS} - SMTP_FROM: ${SMTP_FROM} - SMTP_SSL: ${SMTP_SSL:-} - SMTP_STARTTLS: ${SMTP_STARTTLS:-} - AUTO_ADMIN_FIRST_USER: "${AUTO_ADMIN_FIRST_USER:-true}" - ADMIN_BOOTSTRAP_EMAILS: "${ADMIN_BOOTSTRAP_EMAILS:-}" + DB_NAME: "${DB_NAME:-kairo}" + DB_USER: "${DB_USER:-kairo_user}" + DB_PASSWORD: "${DB_PASSWORD:-change_me}" APP_URL: "${APP_URL:-https://kairo.jinkendo.de}" ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://kairo.jinkendo.de}" ENVIRONMENT: "${ENVIRONMENT:-production}" - CLUB_FEATURE_ENFORCE: "${CLUB_FEATURE_ENFORCE:-1}" ports: - "8004:8000" depends_on: - - postgres + postgres: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')", + ] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s restart: unless-stopped networks: - kairo-network @@ -61,7 +66,8 @@ services: ports: - "3004:80" depends_on: - - backend + backend: + condition: service_healthy restart: unless-stopped networks: - kairo-network diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index f3238ff..a00fc0b 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,6 +1,6 @@ # Deployment – Kairo Jinkendo -**Stand:** 2026-07-04 +**Stand:** 2026-07-04 (AP0.1) **Server:** Raspberry Pi 5 (`192.168.2.49`) — gleicher Host wie Shinkan/Mitai **Runner:** Gitea Actions (`/home/lars/gitea-runner/`) @@ -17,110 +17,59 @@ | **PostgreSQL (localhost)** | 5436 | 5437 | | **Domain** | kairo.jinkendo.de | dev.kairo.jinkendo.de | -**Familien-Referenz (bereits belegt):** - -| App | Prod UI/API | Dev UI/API | -|-----|-------------|------------| -| Mitai | 3002 / 8002 | 3099 / 8099 | -| Shinkan | 3003 / 8003 | 3098 / 8098 | -| **Kairo** | **3004 / 8004** | **3097 / 8097** | - --- ## Einmalige Server-Einrichtung -Auf dem Pi als User `lars` ausführen: - ```bash -# Deploy-Verzeichnisse anlegen -mkdir -p /home/lars/docker/kairo -mkdir -p /home/lars/docker/kairo-dev +mkdir -p /home/lars/docker/kairo /home/lars/docker/kairo-dev -# Production -cd /home/lars/docker/kairo -git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git . -git checkout main -cp .env.example .env -# .env bearbeiten (DB_PASSWORD, SMTP, OPENROUTER, …) - -# Development cd /home/lars/docker/kairo-dev git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git . git checkout develop cp .env.example .env -# .env mit Dev-Werten bearbeiten (siehe Kommentare in .env.example) -``` +# DB_PASSWORD setzen -> **Hinweis:** Erstes `docker compose up` funktioniert erst, wenn `backend/` und `frontend/` mit Dockerfiles vorhanden sind. +cd /home/lars/docker/kairo +git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git . +git checkout main +cp .env.example .env +# Prod-DB_PASSWORD setzen +``` --- -## Medien (optional, derzeit nicht eingerichtet) +## Gitea Actions – Checkliste -Kairo benötigt **aktuell keine Medien-Speicherung**. Es sind keine Medien-Verzeichnisse auf dem Pi anzulegen. +1. **Actions aktiviert** (Repository → Settings → Actions) +2. **Pi-Runner registriert** — derselbe wie Shinkan/Mitai (`ubuntu-latest`) +3. Push `develop` → `deploy-dev.yml` → `curl http://localhost:8097/api/health` +4. Merge `main` → `deploy-prod.yml` → `curl http://localhost:8004/api/health` +5. Nach Deploy: `test.yml` (workflow_run) — pytest, k6, Playwright smoke gegen **localhost** -Falls später Datei-Uploads o. Ä. nötig werden: +| Workflow | Trigger | +|----------|---------| +| `deploy-dev.yml` | Push `develop` | +| `deploy-prod.yml` | Push `main` | +| `test.yml` | Push/PR (compose-smoke) + nach Deploy (Integration) | -1. **NAS-Freigabe** auf dem Synology anlegen (nicht auf dem Raspberry Pi speichern). -2. **Mount auf dem Pi** einrichten (z. B. `/mnt/nas/kairo-media` bzw. `/mnt/nas/kairo-media/dev`). -3. **`docker-compose.override.yml`** im jeweiligen Deploy-Verzeichnis ergänzen (wird von Git ignoriert): - -```yaml -services: - backend: - environment: - MEDIA_ROOT: /app/media - volumes: - - /mnt/nas/kairo-media:/app/media # Dev: …/kairo-media/dev -``` - -4. Im Backend `MEDIA_ROOT` auswerten — erst wenn die App Medien unterstützt. - -Analog Shinkan (`SHINKAN_MEDIA_HOST`), aber bewusst **nicht** Teil des Initial-Setups. +CI auf dem Pi nutzt localhost-Ports. Öffentliche Domains sind für Reverse-Proxy optional. --- -## Reverse Proxy (Synology / Fritz!Box) +## Medien (optional) -Analog zu Shinkan — neue Hostnamen im Proxy eintragen: +Aktuell keine Medien-Speicherung. Bei Bedarf: NAS-Mount + `docker-compose.override.yml` (siehe frühere Doku-Version im Git-Verlauf). + +--- + +## Reverse Proxy (optional) | Hostname | Ziel (Pi) | |----------|-----------| | `kairo.jinkendo.de` | `http://192.168.2.49:3004` | | `dev.kairo.jinkendo.de` | `http://192.168.2.49:3097` | -SSL/TLS wie bei den Schwester-Apps (Let's Encrypt über Synology). - ---- - -## Gitea Actions - -Workflows unter `.gitea/workflows/`: - -| Workflow | Trigger | Aktion | -|----------|---------|--------| -| `deploy-dev.yml` | Push auf `develop` | Build + Deploy nach `kairo-dev/` | -| `deploy-prod.yml` | Push auf `main` | Build + Deploy nach `kairo/` | -| `test.yml` | PR/Push `develop`, nach Deploy | pytest, Lint, Frontend-Build, k6, Playwright | - -Health-Checks nach Deploy: - -- Dev: `curl http://localhost:8097/api/health` (Backend direkt); mit Frontend zusätzlich `http://localhost:3097/api/health` -- Prod: `curl http://localhost:8004/api/health` (Backend direkt); mit Frontend zusätzlich `http://localhost:3004/api/health` - ---- - -## Gitea Secrets (für E2E-Tests) - -In Gitea unter Repository → Settings → Actions → Secrets (analog Shinkan): - -| Secret | Verwendung | -|--------|------------| -| `E2E_DEV_TEST_EMAIL` | Playwright Dev-Login | -| `E2E_DEV_TEST_PASSWORD` | Playwright Dev-Login | -| `E2E_PROD_TEST_EMAIL` | Playwright Prod-Login | -| `E2E_PROD_TEST_PASSWORD` | Playwright Prod-Login | - --- ## Manuelles Deploy @@ -139,12 +88,4 @@ docker compose build --no-cache docker compose up -d ``` ---- - -## Nächster Schritt (App-Code) - -1. `backend/` mit FastAPI-Skeleton (main.py, Dockerfile, migrations/) -2. `frontend/` mit React/Vite-Skeleton (Dockerfile, nginx.conf) -3. Erster Push auf `develop` → Auto-Deploy Dev -4. Reverse-Proxy-Einträge für Domains aktivieren -5. Gitea E2E-Secrets setzen +Health: `curl http://localhost:8097/api/health` (Dev) bzw. `http://localhost:8004/api/health` (Prod) diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..885d0b3 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine AS build + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +ARG VITE_API_URL +ENV VITE_API_URL=$VITE_API_URL +RUN npm run build + +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index c6eaf61..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Frontend - -React + Vite SPA — noch anzulegen. - -Erwartete Struktur (analog shinkan-jinkendo): - -``` -frontend/ -├── Dockerfile -├── nginx.conf -├── package.json -└── src/ -``` diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..5cb0ee9 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Jinkendo Kairo + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..065de8f --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + resolver 127.0.0.11 valid=10s ipv6=off; + + location ^~ /api/ { + set $docker_backend_svc backend; + proxy_pass http://$docker_backend_svc:8000$request_uri; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..e99b667 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,19 @@ +{ + "name": "kairo-jinkendo-frontend", + "version": "0.1.0-ap0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port 3097", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "vite": "^5.1.4" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..b612a68 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react' + +export default function App() { + const [health, setHealth] = useState(null) + const [error, setError] = useState(null) + + useEffect(() => { + fetch('/api/health') + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`) + return res.json() + }) + .then(setHealth) + .catch((err) => setError(err.message)) + }, []) + + return ( +
+

Jinkendo Kairo

+

Operativer Program Director — Sprint 0 / AP0.1

+
+

API Health

+ {error &&

Fehler: {error}

} + {!error && !health &&

Lade …

} + {health && ( +
{JSON.stringify(health, null, 2)}
+ )} +
+
+ ) +} diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 0000000..2a8997c --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,30 @@ +:root { + font-family: system-ui, sans-serif; + color: #1a1a1a; + background: #f6f7fb; +} + +body { + margin: 0; +} + +.shell { + max-width: 720px; + margin: 2rem auto; + padding: 0 1rem; +} + +.card { + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + padding: 1rem; +} + +.error { + color: #b00020; +} + +pre { + overflow: auto; +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..7392451 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import { createRoot } from 'react-dom/client' +import App from './App.jsx' +import './app.css' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..c544669 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 3097, + proxy: { + '/api': 'http://127.0.0.1:8097', + }, + }, +}) diff --git a/tests/smoke-health.spec.js b/tests/smoke-health.spec.js new file mode 100644 index 0000000..5b23efe --- /dev/null +++ b/tests/smoke-health.spec.js @@ -0,0 +1,9 @@ +const { test, expect } = require('@playwright/test'); + +test('GET /api/health liefert jinkendo-kairo', async ({ request }) => { + const response = await request.get('/api/health'); + expect(response.ok()).toBeTruthy(); + const body = await response.json(); + expect(body.app).toBe('jinkendo-kairo'); + expect(body.status).toMatch(/ok|degraded/); +}); From b1e00bb347b989faf6348e9538830e6e72e46442 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:35:09 +0200 Subject: [PATCH 04/10] CI-Fix: compose-smoke nicht bei Push (Port-Konflikt mit Deploy), DB ohne Host-Port. Co-authored-by: Cursor --- .gitea/workflows/deploy-dev.yml | 3 +++ .gitea/workflows/deploy-prod.yml | 3 +++ .gitea/workflows/test.yml | 28 +++++++++++++--------------- docker-compose.dev-env.yml | 7 +++---- docker-compose.yml | 4 ++-- docs/DEPLOYMENT.md | 2 +- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 00cb0bb..05b55e6 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -29,4 +29,7 @@ jobs: if docker compose -f docker-compose.dev-env.yml ps --status running 2>/dev/null | grep -q frontend; then curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" fi + docker compose -f docker-compose.dev-env.yml exec -T backend pip install -q -r requirements-dev.txt + docker compose -f docker-compose.dev-env.yml exec -T backend python -m pytest tests -ra -vv --tb=short + echo "✓ DEV pytest OK" echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 79380b8..551a1cb 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -29,4 +29,7 @@ jobs: if docker compose ps --status running 2>/dev/null | grep -q frontend; then curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" fi + docker compose exec -T backend pip install -q -r requirements-dev.txt + docker compose exec -T backend python -m pytest tests -ra -vv --tb=short + echo "✓ PROD pytest OK" echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index fe623db..710fb06 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,8 +1,8 @@ name: Test Suite -# push develop/main → compose-smoke (frischer Build auf dem Runner) -# workflow_run nach Deploy → pytest/k6/playwright gegen laufende Instanz -# pull_request → compose-smoke +# push develop/main → nur lint + frontend-build (Deploy läuft separat) +# pull_request → compose-smoke auf alternativen Ports (kein Konflikt mit kairo-dev) +# workflow_run nach Deploy → pytest/k6/playwright gegen deployte Instanz (localhost) on: push: branches: [develop, main] @@ -14,28 +14,26 @@ on: jobs: compose-smoke: - if: github.event_name == 'push' || github.event_name == 'pull_request' + # Nicht bei push: Deploy nutzt dieselben Standard-Ports auf dem Pi. + if: github.event_name == 'pull_request' runs-on: ubuntu-latest env: + COMPOSE_PROJECT_NAME: kairo-ci-smoke DB_PASSWORD: ci_smoke_password + KAIRO_BACKEND_PORT: 18197 + KAIRO_FRONTEND_PORT: 13197 steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Compose Stack bauen und testen + - name: Compose Stack bauen und testen (isolierte CI-Ports) run: | set -e - if [ "${{ github.ref_name }}" = "main" ]; then - COMPOSE="docker-compose.yml" - API_PORT=8004 - UI_PORT=3004 - else - COMPOSE="docker-compose.dev-env.yml" - API_PORT=8097 - UI_PORT=3097 - fi + COMPOSE="docker-compose.dev-env.yml" + API_PORT="${KAIRO_BACKEND_PORT}" + UI_PORT="${KAIRO_FRONTEND_PORT}" - echo "=== Smoke: $COMPOSE ===" + echo "=== Smoke: $COMPOSE (Projekt $COMPOSE_PROJECT_NAME, API $API_PORT) ===" docker compose -f "$COMPOSE" up -d --build --wait curl -sf "http://localhost:${API_PORT}/api/health" | tee /tmp/health-api.json diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 2643696..24a53d2 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -10,8 +10,7 @@ services: POSTGRES_PASSWORD: "${DB_PASSWORD:-dev_password}" volumes: - dev-kairo-db-data:/var/lib/postgresql/data - ports: - - "5437:5432" + # Kein Host-Port: DB nur im Compose-Netz (vermeidet Konflikte mit kairo-dev + CI) healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-kairo_dev} -d ${DB_NAME:-kairo_dev}"] interval: 5s @@ -35,7 +34,7 @@ services: ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://dev.kairo.jinkendo.de,http://192.168.2.49:3097,http://localhost:3097}" ENVIRONMENT: "${ENVIRONMENT:-development}" ports: - - "8097:8000" + - "${KAIRO_BACKEND_PORT:-8097}:8000" depends_on: postgres: condition: service_healthy @@ -62,7 +61,7 @@ services: args: VITE_API_URL: "" ports: - - "3097:80" + - "${KAIRO_FRONTEND_PORT:-3097}:80" depends_on: backend: condition: service_healthy diff --git a/docker-compose.yml b/docker-compose.yml index 5961fa6..c7fdb74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: ALLOWED_ORIGINS: "${ALLOWED_ORIGINS:-https://kairo.jinkendo.de}" ENVIRONMENT: "${ENVIRONMENT:-production}" ports: - - "8004:8000" + - "${KAIRO_BACKEND_PORT:-8004}:8000" depends_on: postgres: condition: service_healthy @@ -64,7 +64,7 @@ services: VITE_API_URL: "" container_name: kairo-ui ports: - - "3004:80" + - "${KAIRO_FRONTEND_PORT:-3004}:80" depends_on: backend: condition: service_healthy diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a00fc0b..e0ef84c 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -14,7 +14,7 @@ | **Server-Verzeichnis** | `/home/lars/docker/kairo` | `/home/lars/docker/kairo-dev` | | **Frontend-Port** | 3004 | 3097 | | **Backend-Port** | 8004 | 8097 | -| **PostgreSQL (localhost)** | 5436 | 5437 | +| **PostgreSQL** | nur im Container-Netz (Prod optional localhost:5436) | nur im Container-Netz | | **Domain** | kairo.jinkendo.de | dev.kairo.jinkendo.de | --- From 486268efd99901d05e79f9c5e4140319c1a855aa Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:37:29 +0200 Subject: [PATCH 05/10] CI: Integrationstests in Deploy-Workflow, Test Suite nur Lint/Build bei Push. Co-authored-by: Cursor --- .gitea/workflows/deploy-dev.yml | 20 +++- .gitea/workflows/deploy-prod.yml | 20 +++- .gitea/workflows/test.yml | 157 ++----------------------------- docs/DEPLOYMENT.md | 12 +-- 4 files changed, 45 insertions(+), 164 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 05b55e6..8b30d97 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -8,7 +8,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - name: Deploy to Development + - name: Deploy and verify Development run: | set -e echo "=== Deploying Kairo to DEVELOPMENT ===" @@ -26,10 +26,22 @@ jobs: docker compose -f docker-compose.dev-env.yml build --no-cache docker compose -f docker-compose.dev-env.yml up -d --wait curl -sf http://localhost:8097/api/health && echo "✓ DEV API /api/health OK" - if docker compose -f docker-compose.dev-env.yml ps --status running 2>/dev/null | grep -q frontend; then - curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" - fi + curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" docker compose -f docker-compose.dev-env.yml exec -T backend pip install -q -r requirements-dev.txt docker compose -f docker-compose.dev-env.yml exec -T backend python -m pytest tests -ra -vv --tb=short echo "✓ DEV pytest OK" + if command -v k6 >/dev/null 2>&1; then + BASE_URL=http://localhost:8097 k6 run scripts/load/k6-health-baseline.js + echo "✓ DEV k6 OK" + else + echo "(k6 nicht installiert — Health bereits via curl geprüft)" + fi + if command -v node >/dev/null 2>&1; then + npm install --no-audit --no-fund + npx playwright install --with-deps chromium + PLAYWRIGHT_BASE_URL=http://localhost:3097 npx playwright test tests/smoke-health.spec.js + echo "✓ DEV Playwright smoke OK" + else + echo "(Node nicht verfügbar — Playwright übersprungen)" + fi echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index 551a1cb..5f62d91 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -8,7 +8,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - name: Deploy to Production + - name: Deploy and verify Production run: | set -e echo "=== Deploying Kairo to PRODUCTION ===" @@ -26,10 +26,22 @@ jobs: docker compose build --no-cache docker compose up -d --wait curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" - if docker compose ps --status running 2>/dev/null | grep -q frontend; then - curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" - fi + curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" docker compose exec -T backend pip install -q -r requirements-dev.txt docker compose exec -T backend python -m pytest tests -ra -vv --tb=short echo "✓ PROD pytest OK" + if command -v k6 >/dev/null 2>&1; then + BASE_URL=http://localhost:8004 k6 run scripts/load/k6-health-baseline.js + echo "✓ PROD k6 OK" + else + echo "(k6 nicht installiert — Health bereits via curl geprüft)" + fi + if command -v node >/dev/null 2>&1; then + npm install --no-audit --no-fund + npx playwright install --with-deps chromium + PLAYWRIGHT_BASE_URL=http://localhost:3004 npx playwright test tests/smoke-health.spec.js + echo "✓ PROD Playwright smoke OK" + else + echo "(Node nicht verfügbar — Playwright übersprungen)" + fi echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 710fb06..35f6336 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,20 +1,16 @@ name: Test Suite -# push develop/main → nur lint + frontend-build (Deploy läuft separat) -# pull_request → compose-smoke auf alternativen Ports (kein Konflikt mit kairo-dev) -# workflow_run nach Deploy → pytest/k6/playwright gegen deployte Instanz (localhost) +# Bei Push auf develop/main: nur schnelle Checks (Lint + Frontend-Build). +# Vollständige Tests (pytest, k6, Playwright) laufen im Deploy-Workflow +# deploy-dev.yml / deploy-prod.yml — Gitea/act unterstützt workflow_run oft nicht. on: push: branches: [develop, main] pull_request: branches: [develop] - workflow_run: - workflows: ["Deploy Development", "Deploy Production"] - types: [completed] jobs: compose-smoke: - # Nicht bei push: Deploy nutzt dieselben Standard-Ports auf dem Pi. if: github.event_name == 'pull_request' runs-on: ubuntu-latest env: @@ -36,10 +32,10 @@ jobs: echo "=== Smoke: $COMPOSE (Projekt $COMPOSE_PROJECT_NAME, API $API_PORT) ===" docker compose -f "$COMPOSE" up -d --build --wait - curl -sf "http://localhost:${API_PORT}/api/health" | tee /tmp/health-api.json + curl -sf "http://localhost:${API_PORT}/api/health" echo "✓ Backend /api/health OK" - curl -sf "http://localhost:${UI_PORT}/api/health" | tee /tmp/health-ui.json + curl -sf "http://localhost:${UI_PORT}/api/health" echo "✓ Frontend-Proxy /api/health OK" docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt @@ -48,66 +44,18 @@ jobs: docker compose -f "$COMPOSE" down -v - pytest-backend: - if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - - name: Backend pytest im deployten Container - run: | - set -e - RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - if [ "$RUN_WORKFLOW" = "Deploy Production" ]; then - APP_DIR="/home/lars/docker/kairo" - COMPOSE_FILE="docker-compose.yml" - else - APP_DIR="/home/lars/docker/kairo-dev" - COMPOSE_FILE="docker-compose.dev-env.yml" - fi - - cd "$APP_DIR" - for i in $(seq 1 60); do - if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then - break - fi - if [ "$i" -eq 60 ]; then - docker compose -f "$COMPOSE_FILE" ps || true - docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true - exit 1 - fi - sleep 5 - done - - docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " - pip install -q -r requirements-dev.txt && - python -m pytest tests -ra -vv --tb=short - " - lint-backend: - if: github.event_name == 'push' || github.event_name == 'pull_request' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') runs-on: ubuntu-latest steps: - name: Checkout repository - if: github.event_name != 'workflow_run' uses: actions/checkout@v4 - - name: Check backend syntax (Checkout) - if: github.event_name != 'workflow_run' - run: python3 -m py_compile backend/main.py backend/run_migrations.py backend/db.py - - - name: Check backend syntax (Deploy-Pfad) - if: github.event_name == 'workflow_run' + - name: Check backend syntax run: | - RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - if [ "$RUN_WORKFLOW" = "Deploy Production" ]; then - APP_DIR="/home/lars/docker/kairo" - else - APP_DIR="/home/lars/docker/kairo-dev" - fi - python3 -m py_compile "$APP_DIR/backend/main.py" + python3 -m py_compile backend/main.py backend/run_migrations.py backend/db.py echo "✓ Backend syntax OK" build-frontend: - if: github.event_name == 'push' || github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Checkout repository @@ -124,94 +72,3 @@ jobs: npm install npm run build echo "✓ Frontend build OK" - - k6-health-baseline: - name: k6 /api/health Baseline - if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: k6 Ziel wählen (localhost auf Runner) - id: k6 - run: | - if [ "${{ github.event.workflow_run.name }}" = "Deploy Production" ]; then - echo "base_url=http://localhost:8004" >> $GITHUB_OUTPUT - else - echo "base_url=http://localhost:8097" >> $GITHUB_OUTPUT - fi - - - name: /api/health abwarten - run: | - BASE="${{ steps.k6.outputs.base_url }}" - for i in $(seq 1 60); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - echo "Health OK (Versuch $i)" - exit 0 - fi - sleep 2 - done - curl -v "$BASE/api/health" || true - exit 1 - - - name: Install k6 - run: | - set -e - K6_VER="v0.55.0" - ARCH=$(uname -m) - case "$ARCH" in - x86_64) K6_ARCH=amd64 ;; - aarch64|arm64) K6_ARCH=arm64 ;; - *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; - esac - curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz - tar -xzf /tmp/k6.tgz -C /tmp - sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - - - name: k6 Health-Baseline - env: - BASE_URL: ${{ steps.k6.outputs.base_url }} - run: k6 run scripts/load/k6-health-baseline.js - - playwright-smoke: - if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Playwright Ziel wählen - id: pw - run: | - if [ "${{ github.event.workflow_run.name }}" = "Deploy Production" ]; then - echo "base_url=http://localhost:3004" >> $GITHUB_OUTPUT - else - echo "base_url=http://localhost:3097" >> $GITHUB_OUTPUT - fi - - - name: /api/health abwarten - run: | - BASE="${{ steps.pw.outputs.base_url }}" - for i in $(seq 1 60); do - if curl -sf "$BASE/api/health" >/dev/null 2>&1; then - exit 0 - fi - sleep 2 - done - exit 1 - - - name: Install Playwright - run: | - npm install - npx playwright install --with-deps chromium - - - name: Run smoke tests - env: - PLAYWRIGHT_BASE_URL: ${{ steps.pw.outputs.base_url }} - run: npx playwright test tests/smoke-health.spec.js diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index e0ef84c..a833aed 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -47,13 +47,13 @@ cp .env.example .env 4. Merge `main` → `deploy-prod.yml` → `curl http://localhost:8004/api/health` 5. Nach Deploy: `test.yml` (workflow_run) — pytest, k6, Playwright smoke gegen **localhost** -| Workflow | Trigger | -|----------|---------| -| `deploy-dev.yml` | Push `develop` | -| `deploy-prod.yml` | Push `main` | -| `test.yml` | Push/PR (compose-smoke) + nach Deploy (Integration) | +| Workflow | Trigger | Inhalt | +|----------|---------|--------| +| `deploy-dev.yml` | Push `develop` | Deploy + Health + pytest + k6/Playwright (falls installiert) | +| `deploy-prod.yml` | Push `main` | Deploy + Health + pytest + k6/Playwright (falls installiert) | +| `test.yml` | Push/PR | Lint + Frontend-Build; PR zusätzlich compose-smoke | -CI auf dem Pi nutzt localhost-Ports. Öffentliche Domains sind für Reverse-Proxy optional. +Bei Push auf `develop` sind in **Test Suite** nur `lint-backend` und `build-frontend` aktiv — das ist beabsichtigt. Die Integrationstests laufen im Workflow **Deploy Development**. --- From 5cc9282b31aa85e75552fe6798b91fb4e576338c Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:40:44 +0200 Subject: [PATCH 06/10] CI: Deploy und alle Tests in test.yml (needs deploy), separate Deploy-Workflows entfernt. Co-authored-by: Cursor --- .gitea/workflows/deploy-dev.yml | 47 ------- .gitea/workflows/deploy-prod.yml | 47 ------- .gitea/workflows/test.yml | 228 ++++++++++++++++++++++++++----- README.md | 12 +- docs/DEPLOYMENT.md | 11 +- 5 files changed, 206 insertions(+), 139 deletions(-) delete mode 100644 .gitea/workflows/deploy-dev.yml delete mode 100644 .gitea/workflows/deploy-prod.yml diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml deleted file mode 100644 index 8b30d97..0000000 --- a/.gitea/workflows/deploy-dev.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Deploy Development - -on: - push: - branches: [develop] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Deploy and verify Development - run: | - set -e - echo "=== Deploying Kairo to DEVELOPMENT ===" - REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" - TARGET="/home/lars/docker/kairo-dev" - mkdir -p "$TARGET" - cd "$TARGET" - if [ ! -d .git ]; then - git clone -b develop "$REPO" . - else - git fetch origin develop - git checkout develop - git reset --hard origin/develop - fi - docker compose -f docker-compose.dev-env.yml build --no-cache - docker compose -f docker-compose.dev-env.yml up -d --wait - curl -sf http://localhost:8097/api/health && echo "✓ DEV API /api/health OK" - curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" - docker compose -f docker-compose.dev-env.yml exec -T backend pip install -q -r requirements-dev.txt - docker compose -f docker-compose.dev-env.yml exec -T backend python -m pytest tests -ra -vv --tb=short - echo "✓ DEV pytest OK" - if command -v k6 >/dev/null 2>&1; then - BASE_URL=http://localhost:8097 k6 run scripts/load/k6-health-baseline.js - echo "✓ DEV k6 OK" - else - echo "(k6 nicht installiert — Health bereits via curl geprüft)" - fi - if command -v node >/dev/null 2>&1; then - npm install --no-audit --no-fund - npx playwright install --with-deps chromium - PLAYWRIGHT_BASE_URL=http://localhost:3097 npx playwright test tests/smoke-health.spec.js - echo "✓ DEV Playwright smoke OK" - else - echo "(Node nicht verfügbar — Playwright übersprungen)" - fi - echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml deleted file mode 100644 index 5f62d91..0000000 --- a/.gitea/workflows/deploy-prod.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Deploy Production - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Deploy and verify Production - run: | - set -e - echo "=== Deploying Kairo to PRODUCTION ===" - REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" - TARGET="/home/lars/docker/kairo" - mkdir -p "$TARGET" - cd "$TARGET" - if [ ! -d .git ]; then - git clone -b main "$REPO" . - else - git fetch origin main - git checkout main - git reset --hard origin/main - fi - docker compose build --no-cache - docker compose up -d --wait - curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" - curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" - docker compose exec -T backend pip install -q -r requirements-dev.txt - docker compose exec -T backend python -m pytest tests -ra -vv --tb=short - echo "✓ PROD pytest OK" - if command -v k6 >/dev/null 2>&1; then - BASE_URL=http://localhost:8004 k6 run scripts/load/k6-health-baseline.js - echo "✓ PROD k6 OK" - else - echo "(k6 nicht installiert — Health bereits via curl geprüft)" - fi - if command -v node >/dev/null 2>&1; then - npm install --no-audit --no-fund - npx playwright install --with-deps chromium - PLAYWRIGHT_BASE_URL=http://localhost:3004 npx playwright test tests/smoke-health.spec.js - echo "✓ PROD Playwright smoke OK" - else - echo "(Node nicht verfügbar — Playwright übersprungen)" - fi - echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 35f6336..0d9128f 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,8 +1,7 @@ name: Test Suite -# Bei Push auf develop/main: nur schnelle Checks (Lint + Frontend-Build). -# Vollständige Tests (pytest, k6, Playwright) laufen im Deploy-Workflow -# deploy-dev.yml / deploy-prod.yml — Gitea/act unterstützt workflow_run oft nicht. +# Push develop/main: deploy → pytest, k6, Playwright (needs deploy, gleicher Workflow) +# Pull Request: compose-smoke auf isolierten Ports (kein Konflikt mit kairo-dev) on: push: branches: [develop, main] @@ -10,40 +9,6 @@ on: branches: [develop] jobs: - compose-smoke: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - env: - COMPOSE_PROJECT_NAME: kairo-ci-smoke - DB_PASSWORD: ci_smoke_password - KAIRO_BACKEND_PORT: 18197 - KAIRO_FRONTEND_PORT: 13197 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Compose Stack bauen und testen (isolierte CI-Ports) - run: | - set -e - COMPOSE="docker-compose.dev-env.yml" - API_PORT="${KAIRO_BACKEND_PORT}" - UI_PORT="${KAIRO_FRONTEND_PORT}" - - echo "=== Smoke: $COMPOSE (Projekt $COMPOSE_PROJECT_NAME, API $API_PORT) ===" - docker compose -f "$COMPOSE" up -d --build --wait - - curl -sf "http://localhost:${API_PORT}/api/health" - echo "✓ Backend /api/health OK" - - curl -sf "http://localhost:${UI_PORT}/api/health" - echo "✓ Frontend-Proxy /api/health OK" - - docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt - docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short - echo "✓ pytest OK" - - docker compose -f "$COMPOSE" down -v - lint-backend: runs-on: ubuntu-latest steps: @@ -72,3 +37,192 @@ jobs: npm install npm run build echo "✓ Frontend build OK" + + deploy: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Deploy to target environment + run: | + set -e + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + + if [ "${{ github.ref_name }}" = "main" ]; then + TARGET="/home/lars/docker/kairo" + BRANCH="main" + COMPOSE="docker-compose.yml" + API_PORT=8004 + UI_PORT=3004 + echo "=== Deploy PRODUCTION ===" + else + TARGET="/home/lars/docker/kairo-dev" + BRANCH="develop" + COMPOSE="docker-compose.dev-env.yml" + API_PORT=8097 + UI_PORT=3097 + echo "=== Deploy DEVELOPMENT ===" + fi + + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b "$BRANCH" "$REPO" . + else + git fetch origin "$BRANCH" + git checkout "$BRANCH" + git reset --hard "origin/$BRANCH" + fi + + docker compose -f "$COMPOSE" build --no-cache + docker compose -f "$COMPOSE" up -d --wait + + curl -sf "http://localhost:${API_PORT}/api/health" && echo "✓ API /api/health OK" + curl -sf "http://localhost:${UI_PORT}/api/health" && echo "✓ Frontend-Proxy /api/health OK" + + echo "DEPLOY_TARGET=$TARGET" >> "$GITHUB_ENV" + echo "DEPLOY_COMPOSE=$COMPOSE" >> "$GITHUB_ENV" + echo "DEPLOY_API_PORT=$API_PORT" >> "$GITHUB_ENV" + echo "DEPLOY_UI_PORT=$UI_PORT" >> "$GITHUB_ENV" + + compose-smoke: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + env: + COMPOSE_PROJECT_NAME: kairo-ci-smoke + DB_PASSWORD: ci_smoke_password + KAIRO_BACKEND_PORT: 18197 + KAIRO_FRONTEND_PORT: 13197 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Compose Stack bauen und testen (isolierte CI-Ports) + run: | + set -e + COMPOSE="docker-compose.dev-env.yml" + API_PORT="${KAIRO_BACKEND_PORT}" + UI_PORT="${KAIRO_FRONTEND_PORT}" + + docker compose -f "$COMPOSE" up -d --build --wait + curl -sf "http://localhost:${API_PORT}/api/health" + curl -sf "http://localhost:${UI_PORT}/api/health" + docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt + docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short + docker compose -f "$COMPOSE" down -v + echo "✓ PR compose-smoke + pytest OK" + + pytest-backend: + if: github.event_name == 'push' + needs: [deploy] + runs-on: ubuntu-latest + steps: + - name: Backend pytest im deployten Container + run: | + set -e + if [ "${{ github.ref_name }}" = "main" ]; then + APP_DIR="/home/lars/docker/kairo" + COMPOSE="docker-compose.yml" + else + APP_DIR="/home/lars/docker/kairo-dev" + COMPOSE="docker-compose.dev-env.yml" + fi + + cd "$APP_DIR" + for i in $(seq 1 60); do + if docker compose -f "$COMPOSE" exec -T backend true 2>/dev/null; then + echo "Backend bereit (Versuch $i)" + break + fi + if [ "$i" -eq 60 ]; then + docker compose -f "$COMPOSE" ps || true + docker compose -f "$COMPOSE" logs backend --tail 80 || true + exit 1 + fi + sleep 5 + done + + docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt + docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short + echo "✓ pytest OK" + + k6-health-baseline: + name: k6 /api/health Baseline + if: github.event_name == 'push' + needs: [deploy] + runs-on: ubuntu-latest + steps: + - name: k6 gegen deployte Instanz (localhost) + run: | + set -e + if [ "${{ github.ref_name }}" = "main" ]; then + APP_DIR="/home/lars/docker/kairo" + BASE_URL="http://localhost:8004" + else + APP_DIR="/home/lars/docker/kairo-dev" + BASE_URL="http://localhost:8097" + fi + + for i in $(seq 1 30); do + if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then + break + fi + if [ "$i" -eq 30 ]; then + curl -v "$BASE_URL/api/health" || true + exit 1 + fi + sleep 2 + done + + cd "$APP_DIR" + K6_VER="v0.55.0" + if ! command -v k6 >/dev/null 2>&1; then + ARCH=$(uname -m) + case "$ARCH" in + x86_64) K6_ARCH=amd64 ;; + aarch64|arm64) K6_ARCH=arm64 ;; + *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; + esac + curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz + tar -xzf /tmp/k6.tgz -C /tmp + sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 + fi + + BASE_URL="$BASE_URL" k6 run scripts/load/k6-health-baseline.js + echo "✓ k6 OK" + + playwright-smoke: + if: github.event_name == 'push' + needs: [deploy] + runs-on: ubuntu-latest + steps: + - name: Playwright smoke gegen deployte Instanz + run: | + set -e + if [ "${{ github.ref_name }}" = "main" ]; then + APP_DIR="/home/lars/docker/kairo" + BASE_URL="http://localhost:3004" + else + APP_DIR="/home/lars/docker/kairo-dev" + BASE_URL="http://localhost:3097" + fi + + for i in $(seq 1 30); do + if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then + break + fi + if [ "$i" -eq 30 ]; then + exit 1 + fi + sleep 2 + done + + cd "$APP_DIR" + if ! command -v node >/dev/null 2>&1; then + echo "Fehler: Node.js auf dem Runner erforderlich für Playwright." + exit 1 + fi + + npm install --no-audit --no-fund + npx playwright install --with-deps chromium + PLAYWRIGHT_BASE_URL="$BASE_URL" npx playwright test tests/smoke-health.spec.js + echo "✓ Playwright smoke OK" diff --git a/README.md b/README.md index 439f92b..ed44039 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,16 @@ Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. +## Deployment + +Auto-Deploy und Tests laufen im Workflow **Test Suite** (`.gitea/workflows/test.yml`): + +``` +develop/main push → lint ∥ build → deploy → pytest ∥ k6 ∥ Playwright +``` + +Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) + ## Local Development Voraussetzungen: Docker + Docker Compose. @@ -87,8 +97,6 @@ docker compose -f docker-compose.dev-env.yml exec backend python -m pytest tests UI: http://localhost:3097 · API: http://localhost:8097 -Deployment (Pi): [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) - ## AP0.1 – Stand Projektgrundlage | Bereich | Status | diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a833aed..6ed2554 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -47,13 +47,12 @@ cp .env.example .env 4. Merge `main` → `deploy-prod.yml` → `curl http://localhost:8004/api/health` 5. Nach Deploy: `test.yml` (workflow_run) — pytest, k6, Playwright smoke gegen **localhost** -| Workflow | Trigger | Inhalt | -|----------|---------|--------| -| `deploy-dev.yml` | Push `develop` | Deploy + Health + pytest + k6/Playwright (falls installiert) | -| `deploy-prod.yml` | Push `main` | Deploy + Health + pytest + k6/Playwright (falls installiert) | -| `test.yml` | Push/PR | Lint + Frontend-Build; PR zusätzlich compose-smoke | +| Workflow | Trigger | Jobs | +|----------|---------|------| +| `test.yml` | Push `develop` / `main` | lint, build → **deploy** → **pytest**, **k6**, **Playwright** | +| `test.yml` | Pull Request | lint, build, compose-smoke (+ pytest) | -Bei Push auf `develop` sind in **Test Suite** nur `lint-backend` und `build-frontend` aktiv — das ist beabsichtigt. Die Integrationstests laufen im Workflow **Deploy Development**. +Alle Integrationstests laufen im **selben** Workflow via `needs: deploy` — kein separates Deploy-Workflow mehr, kein `workflow_run`. --- From 2b96518a52cbfb31f482903ca0447f154be9cbce Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:43:57 +0200 Subject: [PATCH 07/10] CI-Struktur wie Shinkan: deploy-dev/prod getrennt, test.yml mit pytest und CI-Ports fuer compose-smoke. Co-authored-by: Cursor --- .gitea/workflows/deploy-dev.yml | 35 ++++ .gitea/workflows/deploy-prod.yml | 30 ++++ .gitea/workflows/test.yml | 292 ++++++++++++++++--------------- README.md | 12 +- docker-compose.dev-env.yml | 3 +- docs/DEPLOYMENT.md | 14 +- infra/ci-smoke.env.example | 7 + 7 files changed, 244 insertions(+), 149 deletions(-) create mode 100644 .gitea/workflows/deploy-dev.yml create mode 100644 .gitea/workflows/deploy-prod.yml create mode 100644 infra/ci-smoke.env.example diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml new file mode 100644 index 0000000..0b95c44 --- /dev/null +++ b/.gitea/workflows/deploy-dev.yml @@ -0,0 +1,35 @@ +name: Deploy Development + +on: + push: + branches: [develop] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Development + run: | + set -e + echo "=== Deploying Kairo to DEVELOPMENT ===" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo-dev" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b develop "$REPO" . + else + git fetch origin develop + git checkout develop + git reset --hard origin/develop + fi + docker compose -f docker-compose.dev-env.yml build --no-cache + docker compose -f docker-compose.dev-env.yml up -d --wait + if ! curl -sf http://localhost:8097/api/health; then + echo "✗ DEV API nicht erreichbar — Backend-Logs:" + docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true + exit 1 + fi + echo "✓ DEV API /api/health OK" + curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK" + echo "=== Kairo DEV Deploy complete ===" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml new file mode 100644 index 0000000..a8f8889 --- /dev/null +++ b/.gitea/workflows/deploy-prod.yml @@ -0,0 +1,30 @@ +name: Deploy Production + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Production + run: | + set -e + echo "=== Deploying Kairo to PRODUCTION ===" + REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" + TARGET="/home/lars/docker/kairo" + mkdir -p "$TARGET" + cd "$TARGET" + if [ ! -d .git ]; then + git clone -b main "$REPO" . + else + git fetch origin main + git checkout main + git reset --hard origin/main + fi + docker compose build --no-cache + docker compose up -d --wait + curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" + curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" + echo "=== Kairo PROD Deploy complete ===" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 0d9128f..60f22b6 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1,88 +1,125 @@ name: Test Suite -# Push develop/main: deploy → pytest, k6, Playwright (needs deploy, gleicher Workflow) -# Pull Request: compose-smoke auf isolierten Ports (kein Konflikt mit kairo-dev) +# develop: push/PR → Tests gegen deployte Dev-Instanz (wartet auf Container). +# main: kein push/PR — Prod-Tests via workflow_run nach Deploy Production. +# compose-smoke (PR): eigener Stack auf CI-Ports 18197/13197 (kein Konflikt mit kairo-dev). on: push: - branches: [develop, main] + branches: [develop] pull_request: branches: [develop] + workflow_run: + workflows: ["Deploy Development", "Deploy Production"] + types: [completed] jobs: + pytest-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Backend pytest im deployten Container + run: | + set -e + EVENT_NAME="${{ github.event_name }}" + REF_NAME="${{ github.ref_name }}" + BASE_REF="${{ github.base_ref }}" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + APP_DIR="/home/lars/docker/kairo" + COMPOSE_FILE="docker-compose.yml" + + if [ "$EVENT_NAME" = "workflow_run" ]; then + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + COMPOSE_FILE="docker-compose.dev-env.yml" + fi + elif [ "$REF_NAME" = "develop" ] || [ "$BASE_REF" = "develop" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + COMPOSE_FILE="docker-compose.dev-env.yml" + fi + + cd "$APP_DIR" + echo "Warte auf stabilen backend-Container …" + for i in $(seq 1 60); do + if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then + echo "Backend bereit (Versuch $i)" + break + fi + if [ "$i" -eq 60 ]; then + echo "Timeout: backend-Container nicht bereit" + docker compose -f "$COMPOSE_FILE" ps || true + docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true + exit 1 + fi + sleep 5 + done + + docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc " + pip install -q -r requirements-dev.txt && + python -m pytest tests -ra -vv --tb=short + " + echo "✓ pytest OK" + lint-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout repository + if: github.event_name != 'workflow_run' uses: actions/checkout@v4 - - name: Check backend syntax + - name: Check backend syntax (Checkout) + if: github.event_name != 'workflow_run' run: | python3 -m py_compile backend/main.py backend/run_migrations.py backend/db.py echo "✓ Backend syntax OK" + - name: Check backend syntax (Deploy-Pfad) + if: github.event_name == 'workflow_run' + run: | + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" + else + APP_DIR="/home/lars/docker/kairo" + fi + python3 -m py_compile "$APP_DIR/backend/main.py" + echo "✓ Backend syntax OK" + build-frontend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - name: Checkout repository + if: github.event_name != 'workflow_run' uses: actions/checkout@v4 - name: Setup Node.js + if: github.event_name != 'workflow_run' uses: actions/setup-node@v4 with: node-version: "20" - - name: Build frontend + - name: Build frontend (Checkout) + if: github.event_name != 'workflow_run' run: | cd frontend npm install npm run build echo "✓ Frontend build OK" - deploy: - if: github.event_name == 'push' - runs-on: ubuntu-latest - steps: - - name: Deploy to target environment + - name: Build frontend (Deploy-Pfad) + if: github.event_name == 'workflow_run' run: | - set -e - REPO="http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git" - - if [ "${{ github.ref_name }}" = "main" ]; then - TARGET="/home/lars/docker/kairo" - BRANCH="main" - COMPOSE="docker-compose.yml" - API_PORT=8004 - UI_PORT=3004 - echo "=== Deploy PRODUCTION ===" + RUN_WORKFLOW="${{ github.event.workflow_run.name }}" + if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then + APP_DIR="/home/lars/docker/kairo-dev" else - TARGET="/home/lars/docker/kairo-dev" - BRANCH="develop" - COMPOSE="docker-compose.dev-env.yml" - API_PORT=8097 - UI_PORT=3097 - echo "=== Deploy DEVELOPMENT ===" + APP_DIR="/home/lars/docker/kairo" fi - - mkdir -p "$TARGET" - cd "$TARGET" - if [ ! -d .git ]; then - git clone -b "$BRANCH" "$REPO" . - else - git fetch origin "$BRANCH" - git checkout "$BRANCH" - git reset --hard "origin/$BRANCH" - fi - - docker compose -f "$COMPOSE" build --no-cache - docker compose -f "$COMPOSE" up -d --wait - - curl -sf "http://localhost:${API_PORT}/api/health" && echo "✓ API /api/health OK" - curl -sf "http://localhost:${UI_PORT}/api/health" && echo "✓ Frontend-Proxy /api/health OK" - - echo "DEPLOY_TARGET=$TARGET" >> "$GITHUB_ENV" - echo "DEPLOY_COMPOSE=$COMPOSE" >> "$GITHUB_ENV" - echo "DEPLOY_API_PORT=$API_PORT" >> "$GITHUB_ENV" - echo "DEPLOY_UI_PORT=$UI_PORT" >> "$GITHUB_ENV" + cd "$APP_DIR/frontend" + npm install + npm run build + echo "✓ Frontend build OK" compose-smoke: if: github.event_name == 'pull_request' @@ -96,133 +133,112 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Compose Stack bauen und testen (isolierte CI-Ports) + - name: Isolierter Stack (CI-Ports, kein Konflikt mit kairo-dev) run: | set -e COMPOSE="docker-compose.dev-env.yml" API_PORT="${KAIRO_BACKEND_PORT}" UI_PORT="${KAIRO_FRONTEND_PORT}" - + echo "compose-smoke auf localhost:${API_PORT} / ${UI_PORT}" docker compose -f "$COMPOSE" up -d --build --wait curl -sf "http://localhost:${API_PORT}/api/health" curl -sf "http://localhost:${UI_PORT}/api/health" docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short docker compose -f "$COMPOSE" down -v - echo "✓ PR compose-smoke + pytest OK" - - pytest-backend: - if: github.event_name == 'push' - needs: [deploy] - runs-on: ubuntu-latest - steps: - - name: Backend pytest im deployten Container - run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - COMPOSE="docker-compose.yml" - else - APP_DIR="/home/lars/docker/kairo-dev" - COMPOSE="docker-compose.dev-env.yml" - fi - - cd "$APP_DIR" - for i in $(seq 1 60); do - if docker compose -f "$COMPOSE" exec -T backend true 2>/dev/null; then - echo "Backend bereit (Versuch $i)" - break - fi - if [ "$i" -eq 60 ]; then - docker compose -f "$COMPOSE" ps || true - docker compose -f "$COMPOSE" logs backend --tail 80 || true - exit 1 - fi - sleep 5 - done - - docker compose -f "$COMPOSE" exec -T backend pip install -q -r requirements-dev.txt - docker compose -f "$COMPOSE" exec -T backend python -m pytest tests -ra -vv --tb=short - echo "✓ pytest OK" + echo "✓ compose-smoke OK" k6-health-baseline: name: k6 /api/health Baseline - if: github.event_name == 'push' - needs: [deploy] + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: k6 gegen deployte Instanz (localhost) + - name: Checkout repository + uses: actions/checkout@v4 + + - name: k6-Ziel wählen + id: k6 run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - BASE_URL="http://localhost:8004" + EVENT="${{ github.event_name }}" + WF_NAME="${{ github.event.workflow_run.name }}" + if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then + echo "base_url=http://localhost:8004" >> $GITHUB_OUTPUT else - APP_DIR="/home/lars/docker/kairo-dev" - BASE_URL="http://localhost:8097" + echo "base_url=http://localhost:8097" >> $GITHUB_OUTPUT fi - for i in $(seq 1 30); do - if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then - break - fi - if [ "$i" -eq 30 ]; then - curl -v "$BASE_URL/api/health" || true - exit 1 + - name: /api/health abwarten + run: | + BASE="${{ steps.k6.outputs.base_url }}" + for i in $(seq 1 90); do + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then + echo "Health OK (Versuch $i)" + exit 0 fi sleep 2 done + curl -v "$BASE/api/health" || true + exit 1 - cd "$APP_DIR" + - name: Install k6 + run: | + set -e K6_VER="v0.55.0" - if ! command -v k6 >/dev/null 2>&1; then - ARCH=$(uname -m) - case "$ARCH" in - x86_64) K6_ARCH=amd64 ;; - aarch64|arm64) K6_ARCH=arm64 ;; - *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; - esac - curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz - tar -xzf /tmp/k6.tgz -C /tmp - sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - fi + ARCH=$(uname -m) + case "$ARCH" in + x86_64) K6_ARCH=amd64 ;; + aarch64|arm64) K6_ARCH=arm64 ;; + *) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;; + esac + curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz + tar -xzf /tmp/k6.tgz -C /tmp + sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - BASE_URL="$BASE_URL" k6 run scripts/load/k6-health-baseline.js - echo "✓ k6 OK" + - name: k6 Health-Baseline + env: + BASE_URL: ${{ steps.k6.outputs.base_url }} + run: k6 run scripts/load/k6-health-baseline.js playwright-smoke: - if: github.event_name == 'push' - needs: [deploy] + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: Playwright smoke gegen deployte Instanz + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Playwright-Ziel wählen + id: pw run: | - set -e - if [ "${{ github.ref_name }}" = "main" ]; then - APP_DIR="/home/lars/docker/kairo" - BASE_URL="http://localhost:3004" + EVENT="${{ github.event_name }}" + WF_NAME="${{ github.event.workflow_run.name }}" + if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then + echo "base_url=http://localhost:3004" >> $GITHUB_OUTPUT else - APP_DIR="/home/lars/docker/kairo-dev" - BASE_URL="http://localhost:3097" + echo "base_url=http://localhost:3097" >> $GITHUB_OUTPUT fi - for i in $(seq 1 30); do - if curl -sf "$BASE_URL/api/health" >/dev/null 2>&1; then - break - fi - if [ "$i" -eq 30 ]; then - exit 1 + - name: /api/health abwarten + run: | + BASE="${{ steps.pw.outputs.base_url }}" + for i in $(seq 1 90); do + if curl -sf "$BASE/api/health" >/dev/null 2>&1; then + exit 0 fi sleep 2 done + exit 1 - cd "$APP_DIR" - if ! command -v node >/dev/null 2>&1; then - echo "Fehler: Node.js auf dem Runner erforderlich für Playwright." - exit 1 - fi - - npm install --no-audit --no-fund + - name: Install Playwright + run: | + npm install npx playwright install --with-deps chromium - PLAYWRIGHT_BASE_URL="$BASE_URL" npx playwright test tests/smoke-health.spec.js - echo "✓ Playwright smoke OK" + + - name: Run Playwright smoke + env: + PLAYWRIGHT_BASE_URL: ${{ steps.pw.outputs.base_url }} + run: npx playwright test tests/smoke-health.spec.js diff --git a/README.md b/README.md index ed44039..60b0c4d 100644 --- a/README.md +++ b/README.md @@ -64,13 +64,15 @@ Diese Dokumente sind Referenzen, kein direkter Sprint-Scope. Verbindlich ist nur, was über das Kairo Sprint-0 Principle Gate oder eine Architecture Decision übernommen wurde. -## Deployment +## Deployment & CI -Auto-Deploy und Tests laufen im Workflow **Test Suite** (`.gitea/workflows/test.yml`): +Zwei getrennte Gitea-Workflows (wie Shinkan): -``` -develop/main push → lint ∥ build → deploy → pytest ∥ k6 ∥ Playwright -``` +| Workflow | Branch | +|----------|--------| +| **Deploy Development** | `develop` | +| **Deploy Production** | `main` | +| **Test Suite** | nach Deploy + bei Push/PR `develop` | Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) diff --git a/docker-compose.dev-env.yml b/docker-compose.dev-env.yml index 24a53d2..90f04a9 100644 --- a/docker-compose.dev-env.yml +++ b/docker-compose.dev-env.yml @@ -1,5 +1,6 @@ # Keine festen container_name — Compose-Namen haben Projektprefix (-postgres-1). -# Medien: aktuell nicht vorgesehen. Bei Bedarf NAS-Mount + docker-compose.override.yml (siehe docs/DEPLOYMENT.md). +# Host-Ports: KAIRO_BACKEND_PORT (Default 8097), KAIRO_FRONTEND_PORT (Default 3097). +# CI compose-smoke: 18197/13197 — siehe infra/ci-smoke.env.example services: postgres: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 6ed2554..6b66524 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -47,12 +47,16 @@ cp .env.example .env 4. Merge `main` → `deploy-prod.yml` → `curl http://localhost:8004/api/health` 5. Nach Deploy: `test.yml` (workflow_run) — pytest, k6, Playwright smoke gegen **localhost** -| Workflow | Trigger | Jobs | -|----------|---------|------| -| `test.yml` | Push `develop` / `main` | lint, build → **deploy** → **pytest**, **k6**, **Playwright** | -| `test.yml` | Pull Request | lint, build, compose-smoke (+ pytest) | +| Workflow | Trigger | Zweck | +|----------|---------|--------| +| `deploy-dev.yml` | Push `develop` | Deploy nach `/home/lars/docker/kairo-dev` | +| `deploy-prod.yml` | Push `main` | Deploy nach `/home/lars/docker/kairo` | +| `test.yml` | Push/PR `develop`, nach Deploy (`workflow_run`) | pytest, k6, Playwright gegen deployte Instanz | +| `test.yml` → `compose-smoke` | Pull Request | Eigener Stack auf **CI-Ports 18197/13197** | -Alle Integrationstests laufen im **selben** Workflow via `needs: deploy` — kein separates Deploy-Workflow mehr, kein `workflow_run`. +Struktur wie Shinkan: **Deploy** und **Test Suite** sind getrennte Workflows in Gitea. + +Bei Push auf `develop` starten Deploy und Test Suite parallel; pytest wartet auf den Backend-Container der deployten Instanz (Ports 8097/3097). Nach Deploy Production feuert `workflow_run` die Prod-Tests (Ports 8004/3004). --- diff --git a/infra/ci-smoke.env.example b/infra/ci-smoke.env.example new file mode 100644 index 0000000..3d0a94b --- /dev/null +++ b/infra/ci-smoke.env.example @@ -0,0 +1,7 @@ +# CI-Ports für isolierte compose-smoke-Läufe (Pull Requests). +# Deploy kairo-dev nutzt 8097/3097 — keine Überschneidung mit 18197/13197. + +COMPOSE_PROJECT_NAME=kairo-ci-smoke +KAIRO_BACKEND_PORT=18197 +KAIRO_FRONTEND_PORT=13197 +DB_PASSWORD=ci_smoke_password From a979d0e5f99ec648b448389ec006ed72ec755887 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:45:43 +0200 Subject: [PATCH 08/10] =?UTF-8?q?Frontend-Build=20aus=20test.yml=20entfern?= =?UTF-8?q?t=20=E2=80=94=20laeuft=20im=20Deploy=20via=20docker=20compose?= =?UTF-8?q?=20build.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .gitea/workflows/deploy-dev.yml | 3 ++- .gitea/workflows/deploy-prod.yml | 3 ++- .gitea/workflows/test.yml | 36 -------------------------------- docs/DEPLOYMENT.md | 2 +- 4 files changed, 5 insertions(+), 39 deletions(-) diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 0b95c44..770015c 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -23,7 +23,8 @@ jobs: git checkout develop git reset --hard origin/develop fi - docker compose -f docker-compose.dev-env.yml build --no-cache + docker compose -f docker-compose.dev-env.yml build --no-cache backend frontend + echo "✓ Backend + Frontend gebaut (Frontend: npm run build im Dockerfile)" docker compose -f docker-compose.dev-env.yml up -d --wait if ! curl -sf http://localhost:8097/api/health; then echo "✗ DEV API nicht erreichbar — Backend-Logs:" diff --git a/.gitea/workflows/deploy-prod.yml b/.gitea/workflows/deploy-prod.yml index a8f8889..55ea082 100644 --- a/.gitea/workflows/deploy-prod.yml +++ b/.gitea/workflows/deploy-prod.yml @@ -23,7 +23,8 @@ jobs: git checkout main git reset --hard origin/main fi - docker compose build --no-cache + docker compose build --no-cache backend frontend + echo "✓ Backend + Frontend gebaut (Frontend: npm run build im Dockerfile)" docker compose up -d --wait curl -sf http://localhost:8004/api/health && echo "✓ PROD API /api/health OK" curl -sf http://localhost:3004/api/health && echo "✓ PROD Frontend-Proxy /api/health OK" diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 60f22b6..43005fd 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -85,42 +85,6 @@ jobs: python3 -m py_compile "$APP_DIR/backend/main.py" echo "✓ Backend syntax OK" - build-frontend: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - name: Checkout repository - if: github.event_name != 'workflow_run' - uses: actions/checkout@v4 - - - name: Setup Node.js - if: github.event_name != 'workflow_run' - uses: actions/setup-node@v4 - with: - node-version: "20" - - - name: Build frontend (Checkout) - if: github.event_name != 'workflow_run' - run: | - cd frontend - npm install - npm run build - echo "✓ Frontend build OK" - - - name: Build frontend (Deploy-Pfad) - if: github.event_name == 'workflow_run' - run: | - RUN_WORKFLOW="${{ github.event.workflow_run.name }}" - if [ "$RUN_WORKFLOW" = "Deploy Development" ]; then - APP_DIR="/home/lars/docker/kairo-dev" - else - APP_DIR="/home/lars/docker/kairo" - fi - cd "$APP_DIR/frontend" - npm install - npm run build - echo "✓ Frontend build OK" - compose-smoke: if: github.event_name == 'pull_request' runs-on: ubuntu-latest diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 6b66524..1c7eac6 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -51,7 +51,7 @@ cp .env.example .env |----------|---------|--------| | `deploy-dev.yml` | Push `develop` | Deploy nach `/home/lars/docker/kairo-dev` | | `deploy-prod.yml` | Push `main` | Deploy nach `/home/lars/docker/kairo` | -| `test.yml` | Push/PR `develop`, nach Deploy (`workflow_run`) | pytest, k6, Playwright gegen deployte Instanz | +| `test.yml` | Push/PR `develop`, nach Deploy | lint, pytest, k6, Playwright (Frontend-Build läuft im Deploy via Docker) | | `test.yml` → `compose-smoke` | Pull Request | Eigener Stack auf **CI-Ports 18197/13197** | Struktur wie Shinkan: **Deploy** und **Test Suite** sind getrennte Workflows in Gitea. From 1765daed2d8b46826d04d25311742fbe7036e753 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:45:58 +0200 Subject: [PATCH 09/10] Kommentar in test.yml: Frontend-Build gehoert zum Deploy. Co-authored-by: Cursor --- .gitea/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 43005fd..ed76634 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -2,6 +2,7 @@ name: Test Suite # develop: push/PR → Tests gegen deployte Dev-Instanz (wartet auf Container). # main: kein push/PR — Prod-Tests via workflow_run nach Deploy Production. +# Frontend-Build: deploy-dev/prod (docker compose build backend frontend), nicht hier. # compose-smoke (PR): eigener Stack auf CI-Ports 18197/13197 (kein Konflikt mit kairo-dev). on: push: From 1d8e22be826dee5f52d0c2d5ec6ce59ab7b12e1a Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 4 Jul 2026 19:47:45 +0200 Subject: [PATCH 10/10] k6-CI fuer Raspberry Pi: weniger Last, quiet-Modus, nach pytest. Co-authored-by: Cursor --- .gitea/workflows/test.yml | 15 ++++++++++++--- scripts/load/k6-health-baseline.js | 24 +++++++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index ed76634..6c30656 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -115,6 +115,7 @@ jobs: k6-health-baseline: name: k6 /api/health Baseline + needs: [pytest-backend] if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: @@ -160,11 +161,19 @@ jobs: sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6 - name: k6 Health-Baseline - env: - BASE_URL: ${{ steps.k6.outputs.base_url }} - run: k6 run scripts/load/k6-health-baseline.js + run: | + set -e + BASE_URL="${{ steps.k6.outputs.base_url }}" + if [ -z "$BASE_URL" ]; then + echo "BASE_URL leer — steps.k6.outputs.base_url prüfen" + exit 1 + fi + echo "k6 gegen BASE_URL=$BASE_URL (K6_CI=1, quiet)" + BASE_URL="$BASE_URL" K6_CI=1 k6 run --quiet scripts/load/k6-health-baseline.js + echo "✓ k6 Health-Baseline OK" playwright-smoke: + needs: [pytest-backend] if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: diff --git a/scripts/load/k6-health-baseline.js b/scripts/load/k6-health-baseline.js index 62e9dd1..cb69d76 100644 --- a/scripts/load/k6-health-baseline.js +++ b/scripts/load/k6-health-baseline.js @@ -1,16 +1,23 @@ /** * Phase-0-Baseline: parallele GET /api/health (kein Auth). * BASE_URL optional, z. B. https://dev.kairo.jinkendo.de + * + * K6_CI=1: leichtere Last für Self-hosted Runner (Raspberry Pi). */ import http from 'k6/http' import { check } from 'k6' +const isCi = __ENV.K6_CI === '1' || __ENV.K6_CI === 'true' +const vus = parseInt(__ENV.K6_VUS || (isCi ? '5' : '10'), 10) +const duration = __ENV.K6_DURATION || (isCi ? '15s' : '30s') +const p95Ms = parseInt(__ENV.K6_P95_MS || (isCi ? '5000' : '3000'), 10) + export const options = { scenarios: { health: { executor: 'constant-vus', - vus: 10, - duration: '30s', + vus, + duration, gracefulStop: '5s', tags: { scenario: 'health' }, exec: 'health', @@ -18,8 +25,9 @@ export const options = { }, thresholds: { http_req_failed: ['rate<0.05'], - 'http_req_duration{scenario:health}': ['p(95)<3000'], + 'http_req_duration{scenario:health}': [`p(95)<${p95Ms}`], }, + summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)'], } const BASE = (__ENV.BASE_URL || 'https://dev.kairo.jinkendo.de').replace(/\/$/, '') @@ -30,3 +38,13 @@ export function health() { 'health 2xx': (r) => r.status >= 200 && r.status < 300, }) } + +export function handleSummary(data) { + const failed = data.metrics.http_req_failed?.values?.rate ?? 0 + const p95 = data.metrics.http_req_duration?.values?.['p(95)'] ?? 0 + const lines = [ + `k6 summary: BASE=${BASE} vus=${vus} duration=${duration}`, + ` http_req_failed=${(failed * 100).toFixed(2)}% p(95)=${p95.toFixed(1)}ms (limit ${p95Ms}ms)`, + ] + return { stdout: lines.join('\n') + '\n' } +}