Merge pull request 'initial base setup' (#1) from develop into main
All checks were successful
Deploy Production / deploy (push) Successful in 39s
All checks were successful
Deploy Production / deploy (push) Successful in 39s
Reviewed-on: #1
This commit is contained in:
commit
b29fb2f538
41
.cursor/rules/kairo-architecture.mdc
Normal file
41
.cursor/rules/kairo-architecture.mdc
Normal file
|
|
@ -0,0 +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
|
||||
|
||||
Referenzmaterial:
|
||||
|
||||
- `docs/reference/design-principles/mitai/`
|
||||
- `docs/reference/design-principles/shinkan/`
|
||||
- `docs/reference/design-principles/alignment/`
|
||||
|
||||
Nicht automatisch Implementierungs-Scope. Übernahme nur via Principle Gate oder Architecture Decision.
|
||||
|
||||
## 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`.
|
||||
60
.env.example
60
.env.example
|
|
@ -1,70 +1,18 @@
|
|||
# === .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
|
||||
# KAIRO_MEDIA_HOST=/kairo-media
|
||||
# MEDIA_ROOT=/app/media
|
||||
|
||||
# ─── 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
|
||||
# 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
|
||||
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 (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
|
||||
|
|
|
|||
|
|
@ -12,17 +12,25 @@ 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/version; then
|
||||
echo "✗ DEV API nicht erreichbar — Backend-Logs (Migration/Startup):"
|
||||
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 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:"
|
||||
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"
|
||||
curl -sf http://localhost:3097/api/health && echo "✓ DEV Frontend-Proxy /api/health OK"
|
||||
echo "=== Kairo DEV Deploy complete ==="
|
||||
|
|
|
|||
|
|
@ -12,12 +12,20 @@ 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/version && echo "✓ PROD API (direkt) healthy"
|
||||
curl -sf http://localhost:3004/api/version && echo "✓ PROD API über Frontend-Nginx (wie Browser) 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
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
git reset --hard origin/main
|
||||
fi
|
||||
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"
|
||||
echo "=== Kairo PROD Deploy complete ==="
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
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.
|
||||
# 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:
|
||||
branches: [develop]
|
||||
|
|
@ -54,115 +55,95 @@ jobs:
|
|||
done
|
||||
|
||||
docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc "
|
||||
pip install -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
|
||||
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: Check backend syntax
|
||||
- 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
|
||||
echo "✓ Backend syntax OK"
|
||||
|
||||
- name: Check backend syntax (Deploy-Pfad)
|
||||
if: github.event_name == 'workflow_run'
|
||||
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
|
||||
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: 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"
|
||||
npm install
|
||||
npm run build
|
||||
echo "✓ Frontend build OK"
|
||||
|
||||
k6-health-baseline:
|
||||
name: k6 /health Baseline
|
||||
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
||||
compose-smoke:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
E2E_TARGET_URL: https://dev.kairo.jinkendo.de
|
||||
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: E2E-Ziel wählen (Dev über Proxy vs. Production)
|
||||
id: e2e
|
||||
- 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 "✓ compose-smoke OK"
|
||||
|
||||
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:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: k6-Ziel wählen
|
||||
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."
|
||||
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 /health abwarten
|
||||
if: ${{ steps.e2e.outputs.mode == 'dev' }}
|
||||
- name: /api/health abwarten
|
||||
run: |
|
||||
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||
echo "Warte auf $BASE/health …"
|
||||
BASE="${{ steps.k6.outputs.base_url }}"
|
||||
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
|
||||
exit 1
|
||||
|
||||
- name: Prod /health abwarten
|
||||
if: ${{ steps.e2e.outputs.mode == 'prod' }}
|
||||
run: |
|
||||
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||
echo "Warte auf $BASE/health …"
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf "$BASE/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
|
||||
curl -v "$BASE/api/health" || true
|
||||
exit 1
|
||||
|
||||
- name: Install k6
|
||||
|
|
@ -175,26 +156,26 @@ 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 /health)
|
||||
env:
|
||||
BASE_URL: ${{ steps.e2e.outputs.base_url }}
|
||||
- name: k6 Health-Baseline
|
||||
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 }}"
|
||||
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-tests:
|
||||
playwright-smoke:
|
||||
needs: [pytest-backend]
|
||||
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
|
||||
|
|
@ -202,111 +183,36 @@ 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."
|
||||
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 /health abwarten
|
||||
if: ${{ steps.e2e.outputs.mode == 'dev' }}
|
||||
- name: /api/health abwarten
|
||||
run: |
|
||||
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||
echo "Warte auf $BASE/health …"
|
||||
BASE="${{ steps.pw.outputs.base_url }}"
|
||||
for i in $(seq 1 90); do
|
||||
if curl -sf "$BASE/health" >/dev/null 2>&1; then
|
||||
echo "Health OK (Versuch $i)"
|
||||
if curl -sf "$BASE/api/health" >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen."
|
||||
curl -v "$BASE/health" || true
|
||||
exit 1
|
||||
|
||||
- name: Prod /health abwarten
|
||||
if: ${{ steps.e2e.outputs.mode == 'prod' }}
|
||||
run: |
|
||||
BASE="${{ steps.e2e.outputs.base_url }}"
|
||||
echo "Warte auf $BASE/health …"
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf "$BASE/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
|
||||
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 Playwright smoke
|
||||
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
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -59,6 +59,7 @@ coverage/
|
|||
# Temp
|
||||
tmp/
|
||||
*.tmp
|
||||
*_alt.md
|
||||
|
||||
# Claude: nur ausgewählte Bereiche versionieren
|
||||
.claude/**
|
||||
|
|
|
|||
143
CLAUDE.md
143
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 (Backend, Frontend minimal, Tests, Deploy).
|
||||
|
|
|
|||
134
README.md
134
README.md
|
|
@ -1,63 +1,111 @@
|
|||
# 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
|
||||
|
||||
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`
|
||||
|
||||
## Arbeitsmodus
|
||||
|
||||
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/
|
||||
```
|
||||
|
||||
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 & CI
|
||||
|
||||
Zwei getrennte Gitea-Workflows (wie Shinkan):
|
||||
|
||||
| Workflow | Branch |
|
||||
|----------|--------|
|
||||
| **Deploy Development** | `develop` |
|
||||
| **Deploy Production** | `main` |
|
||||
| **Test Suite** | nach Deploy + bei Push/PR `develop` |
|
||||
|
||||
Details: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)
|
||||
|
||||
## Local Development
|
||||
|
||||
Voraussetzungen: Docker + Docker Compose.
|
||||
|
||||
```bash
|
||||
git clone http://192.168.2.144:3000/Lars/Kairo-Jinkendo.git
|
||||
cd Kairo-Jinkendo
|
||||
cp .env.example .env
|
||||
# .env anpassen
|
||||
git checkout develop
|
||||
|
||||
# Development
|
||||
# Dev-Stack (PostgreSQL + Backend + Frontend)
|
||||
docker compose -f docker-compose.dev-env.yml up --build
|
||||
|
||||
# Production (lokal)
|
||||
docker compose 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
|
||||
```
|
||||
|
||||
Frontend (Dev): http://localhost:3097
|
||||
Backend (Dev): http://localhost:8097
|
||||
UI: http://localhost:3097 · API: http://localhost:8097
|
||||
|
||||
## Server-Einrichtung
|
||||
## AP0.1 – Stand Projektgrundlage
|
||||
|
||||
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
|
||||
| Bereich | Status |
|
||||
|---------|--------|
|
||||
| Git + Gitea (`develop` / `main`) | erledigt |
|
||||
| Docker Compose (Prod + Dev) | erledigt |
|
||||
| Backend (FastAPI, Migrationen, `/api/health`) | erledigt |
|
||||
| Frontend minimal (React + nginx Proxy) | erledigt |
|
||||
| pytest (Health + Migrationen) | erledigt |
|
||||
| Gitea Actions (Deploy + Test) | erledigt |
|
||||
|
|
|
|||
16
backend/Dockerfile
Normal file
16
backend/Dockerfile
Normal file
|
|
@ -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"]
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# Backend
|
||||
|
||||
FastAPI-Anwendung — noch anzulegen.
|
||||
|
||||
Erwartete Struktur (analog shinkan-jinkendo):
|
||||
|
||||
```
|
||||
backend/
|
||||
├── Dockerfile
|
||||
├── main.py
|
||||
├── requirements.txt
|
||||
├── migrations/
|
||||
└── routers/
|
||||
```
|
||||
38
backend/db.py
Normal file
38
backend/db.py
Normal file
|
|
@ -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()
|
||||
71
backend/main.py
Normal file
71
backend/main.py
Normal file
|
|
@ -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,
|
||||
}
|
||||
12
backend/migrations/001_init_core.sql
Normal file
12
backend/migrations/001_init_core.sql
Normal file
|
|
@ -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;
|
||||
3
backend/requirements-dev.txt
Normal file
3
backend/requirements-dev.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
-r requirements.txt
|
||||
pytest==8.3.4
|
||||
httpx==0.27.2
|
||||
5
backend/requirements.txt
Normal file
5
backend/requirements.txt
Normal file
|
|
@ -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
|
||||
198
backend/run_migrations.py
Normal file
198
backend/run_migrations.py
Normal file
|
|
@ -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())
|
||||
30
backend/tests/test_health.py
Normal file
30
backend/tests/test_health.py
Normal file
|
|
@ -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"}
|
||||
57
backend/tests/test_migrations.py
Normal file
57
backend/tests/test_migrations.py
Normal file
|
|
@ -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()
|
||||
3
backend/version.py
Normal file
3
backend/version.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
APP_VERSION = "0.1.0-ap0.1"
|
||||
DB_SCHEMA_VERSION = "001"
|
||||
APP_NAME = "jinkendo-kairo"
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Keine festen container_name — Compose-Namen haben Projektprefix (<projekt>-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.
|
||||
# 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:
|
||||
|
|
@ -11,8 +11,12 @@ 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
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev-kairo-network
|
||||
|
|
@ -27,32 +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}"
|
||||
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"
|
||||
- "${KAIRO_BACKEND_PORT:-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
|
||||
|
|
@ -64,9 +62,10 @@ services:
|
|||
args:
|
||||
VITE_API_URL: ""
|
||||
ports:
|
||||
- "3097:80"
|
||||
- "${KAIRO_FRONTEND_PORT:-3097}:80"
|
||||
depends_on:
|
||||
- backend
|
||||
backend:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dev-kairo-network
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -5,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
|
||||
|
|
@ -22,39 +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}"
|
||||
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"
|
||||
- "${KAIRO_BACKEND_PORT:-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
|
||||
|
|
@ -67,9 +64,10 @@ services:
|
|||
VITE_API_URL: ""
|
||||
container_name: kairo-ui
|
||||
ports:
|
||||
- "3004:80"
|
||||
- "${KAIRO_FRONTEND_PORT:-3004}:80"
|
||||
depends_on:
|
||||
- backend
|
||||
backend:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- kairo-network
|
||||
|
|
|
|||
|
|
@ -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/`)
|
||||
|
||||
|
|
@ -14,90 +14,65 @@
|
|||
| **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 |
|
||||
| **Medien-Host-Pfad** | `/kairo-media` | `/kairo-media/dev` |
|
||||
|
||||
**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
|
||||
# Verzeichnisse anlegen
|
||||
mkdir -p /home/lars/docker/kairo
|
||||
mkdir -p /home/lars/docker/kairo-dev
|
||||
mkdir -p /kairo-media /kairo-media/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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reverse Proxy (Synology / Fritz!Box)
|
||||
## Gitea Actions – Checkliste
|
||||
|
||||
Analog zu Shinkan — neue Hostnamen im Proxy eintragen:
|
||||
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**
|
||||
|
||||
| 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 | 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.
|
||||
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
## Medien (optional)
|
||||
|
||||
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/version` und `http://localhost:3097/api/version`
|
||||
- Prod: `curl http://localhost:8004/api/version` und `http://localhost:3004/api/version`
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
|
@ -116,12 +91,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)
|
||||
|
|
|
|||
38
docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md
Normal file
38
docs/architecture/ARCHITECTURE_DECISION_PROPOSAL_TEMPLATE.md
Normal file
|
|
@ -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?
|
||||
|
|
@ -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.
|
||||
67
docs/architecture/Kairo_Architecture_References_v0.1.md
Normal file
67
docs/architecture/Kairo_Architecture_References_v0.1.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# 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`
|
||||
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
|
||||
|
||||
### 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.
|
||||
105
docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md
Normal file
105
docs/architecture/Kairo_Sprint0_Principle_Gate_v0.1.md
Normal file
|
|
@ -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
|
||||
448
docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md
Normal file
448
docs/product/Jinkendo_Kairo_Product_Spec_v0.2.md
Normal file
|
|
@ -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.
|
||||
56
docs/reference/design-principles/README.md
Normal file
56
docs/reference/design-principles/README.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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) |
|
||||
|
|
@ -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=<optional>
|
||||
|
||||
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 |
|
||||
|
|
@ -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, `<img>`, 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 | ✅ |
|
||||
|
|
@ -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 | ✅ |
|
||||
|
|
@ -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` |
|
||||
|
|
@ -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` |
|
||||
|
|
@ -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/<branch>` — 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 |
|
||||
|
|
@ -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 `<Outlet />`; 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 | ✅ |
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 | ✅ |
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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 |
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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}}` → `<span data-shinkan-exercise-media="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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
466
docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md
Normal file
466
docs/sprints/Jinkendo_Kairo_04_Sprint0_Foundation_v0.3.md
Normal file
|
|
@ -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
|
||||
167
docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md
Normal file
167
docs/sprints/Sprint0_AP0_1_Project_Setup_Assignment_v0.1.md
Normal file
|
|
@ -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
|
||||
```
|
||||
112
docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md
Normal file
112
docs/sprints/Sprint0_Vibe_Coder_Handover_v0.1.md
Normal file
|
|
@ -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.
|
||||
15
frontend/Dockerfile
Normal file
15
frontend/Dockerfile
Normal file
|
|
@ -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;"]
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Frontend
|
||||
|
||||
React + Vite SPA — noch anzulegen.
|
||||
|
||||
Erwartete Struktur (analog shinkan-jinkendo):
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── Dockerfile
|
||||
├── nginx.conf
|
||||
├── package.json
|
||||
└── src/
|
||||
```
|
||||
12
frontend/index.html
Normal file
12
frontend/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jinkendo Kairo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
22
frontend/nginx.conf
Normal file
22
frontend/nginx.conf
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
19
frontend/package.json
Normal file
19
frontend/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
31
frontend/src/App.jsx
Normal file
31
frontend/src/App.jsx
Normal file
|
|
@ -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 (
|
||||
<main className="shell">
|
||||
<h1>Jinkendo Kairo</h1>
|
||||
<p>Operativer Program Director — Sprint 0 / AP0.1</p>
|
||||
<section className="card">
|
||||
<h2>API Health</h2>
|
||||
{error && <p className="error">Fehler: {error}</p>}
|
||||
{!error && !health && <p>Lade …</p>}
|
||||
{health && (
|
||||
<pre>{JSON.stringify(health, null, 2)}</pre>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
30
frontend/src/app.css
Normal file
30
frontend/src/app.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
10
frontend/src/main.jsx
Normal file
10
frontend/src/main.jsx
Normal file
|
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
12
frontend/vite.config.js
Normal file
12
frontend/vite.config.js
Normal file
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
})
|
||||
7
infra/README.md
Normal file
7
infra/README.md
Normal file
|
|
@ -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.
|
||||
7
infra/ci-smoke.env.example
Normal file
7
infra/ci-smoke.env.example
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
/**
|
||||
* 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
|
||||
*
|
||||
* 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,15 +25,26 @@ 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(/\/$/, '')
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
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' }
|
||||
}
|
||||
|
|
|
|||
9
tests/smoke-health.spec.js
Normal file
9
tests/smoke-health.spec.js
Normal file
|
|
@ -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/);
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user