From 3181cc126d50f2464711cfaf49b639262c1a0164 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 19 Jun 2026 12:35:30 +0200 Subject: [PATCH 1/6] Enhance Planning AI Context with Catalog Exercise Hints - Introduced support for catalog exercise hints in various functions, including `_build_stage_ai_context`, `try_suggest_ai_stage_step`, and `build_gap_fill_goal_text`, improving the contextual information available for AI suggestions. - Added a new function `catalog_exercise_hints_block` to consolidate hints retrieval from the catalog context. - Updated the `PlanningIntentContext` to include catalog exercise hints, ensuring they are part of the API output. - Implemented tests to verify the inclusion of catalog exercise hints in goal text generation and rematch logic. - Incremented version numbers and updated changelog to reflect these enhancements. --- backend/planning_exercise_path_ai_fill.py | 30 +++++++++++++++++++ backend/planning_exercise_path_builder.py | 11 +++++++ backend/planning_intent_context.py | 16 +++++++++- backend/planning_path_rematch.py | 24 ++++++++++++++- backend/planning_progression_roadmap.py | 2 ++ backend/planning_prompt_variables.py | 22 ++++++++++++++ .../test_planning_exercise_path_ai_fill.py | 14 +++++++++ backend/tests/test_planning_path_rematch.py | 26 ++++++++++++++++ backend/version.py | 15 ++++++++-- docs/HANDOVER.md | 9 +++--- .../PLANNING_CATALOG_PROMPT_SNIPPETS.md | 11 +++---- 11 files changed, 166 insertions(+), 14 deletions(-) diff --git a/backend/planning_exercise_path_ai_fill.py b/backend/planning_exercise_path_ai_fill.py index a679584..aa75705 100644 --- a/backend/planning_exercise_path_ai_fill.py +++ b/backend/planning_exercise_path_ai_fill.py @@ -19,6 +19,8 @@ from planning_exercise_form_context import ( prior_path_steps_before_major, ) from planning_exercise_semantics import PlanningSemanticBrief, brief_to_summary_dict +from planning_catalog_context import ProgressionPlanningCatalogContext +from planning_prompt_variables import catalog_exercise_hints_block _logger = logging.getLogger("shinkan.planning_exercise_path_ai_fill") @@ -54,6 +56,7 @@ def _build_stage_ai_context( step_after: Optional[Mapping[str, Any]] = None, prior_steps: Optional[Sequence[Mapping[str, Any]]] = None, start_situation: Optional[str] = None, + catalog_exercise_hints: Optional[str] = None, ) -> ExerciseFormAiPromptContext: """KI-Kontext für unbesetzte Roadmap-Stufe (keine Brücke zwischen falschen Array-Indizes).""" gap = dict(spec.get("gap") or {}) @@ -97,6 +100,9 @@ def _build_stage_ai_context( sketch = (spec.get("sketch") or "").strip() if sketch and sketch != learning_goal: goal_parts.extend(["", f"Kontext: {sketch}"]) + hints = (catalog_exercise_hints or "").strip() + if hints: + goal_parts.extend(["", "Katalog-Hinweise Übungsanlage:", hints]) goal = "\n".join(goal_parts) focus_hint = topic if brief.topic_type == "technique" else None @@ -118,6 +124,7 @@ def try_suggest_ai_stage_step( brief: PlanningSemanticBrief, spec: Mapping[str, Any], steps: Sequence[Mapping[str, Any]], + catalog: Optional[ProgressionPlanningCatalogContext] = None, ) -> Optional[Dict[str, Any]]: """KI-Vorschlag für leere Roadmap-Stufe.""" major_idx = spec.get("roadmap_major_step_index") @@ -136,6 +143,8 @@ def try_suggest_ai_stage_step( if not gap.get("learning_goal"): gap["learning_goal"] = spec.get("title_hint") or spec.get("sketch") + exercise_hints = catalog_exercise_hints_block(cur, catalog) if catalog else "" + ctx = _build_stage_ai_context( goal_query=goal_query, brief=brief, @@ -143,6 +152,7 @@ def try_suggest_ai_stage_step( step_before=step_before, step_after=step_after, prior_steps=prior_steps, + catalog_exercise_hints=exercise_hints or None, ) try: ai_payload = run_exercise_form_ai_suggestion(cur, ctx=ctx) @@ -189,6 +199,7 @@ def _build_gap_ai_context( gap: Mapping[str, Any], title_hint: Optional[str] = None, sketch_hint: Optional[str] = None, + catalog_exercise_hints: Optional[str] = None, ) -> ExerciseFormAiPromptContext: topic = (brief.primary_topic or "Technik").strip() phase = gap.get("expected_phase") or "vertiefung" @@ -206,6 +217,9 @@ def _build_gap_ai_context( ] if sketch: goal_parts.extend(["", f"Hinweis: {sketch}"]) + hints = (catalog_exercise_hints or "").strip() + if hints: + goal_parts.extend(["", "Katalog-Hinweise Übungsanlage:", hints]) goal = "\n".join(goal_parts) focus_hint = topic if brief.topic_type == "technique" else None @@ -271,8 +285,10 @@ def try_suggest_ai_bridge_step( gap: Mapping[str, Any], title_hint: Optional[str] = None, sketch_hint: Optional[str] = None, + catalog: Optional[ProgressionPlanningCatalogContext] = None, ) -> Optional[Dict[str, Any]]: """Ruft exercise AI suggest auf — kein Speichern in DB.""" + exercise_hints = catalog_exercise_hints_block(cur, catalog) if catalog else "" ctx = _build_gap_ai_context( goal_query=goal_query, brief=brief, @@ -281,6 +297,7 @@ def try_suggest_ai_bridge_step( gap=gap, title_hint=title_hint, sketch_hint=sketch_hint, + catalog_exercise_hints=exercise_hints or None, ) g_plain = strip_html_to_plain(ctx.goal) if not g_plain.strip() and not (ctx.title or "").strip(): @@ -478,6 +495,7 @@ def build_gap_fill_goal_text( step_a: Optional[Mapping[str, Any]] = None, step_b: Optional[Mapping[str, Any]] = None, roadmap_snapshot: Optional[Mapping[str, Any]] = None, + catalog_exercise_hints: Optional[str] = None, ) -> str: """Ausführlicher Zieltext für KI-Neuanlage aus Pfad-, Roadmap- und Stufen-Kontext.""" topic = (brief.primary_topic or "Technik").strip() @@ -555,6 +573,9 @@ def build_gap_fill_goal_text( parts.append(f"Qualitätsprüfung: {spec['rationale']}") if spec.get("sketch"): parts.append(f"Skizze: {spec['sketch']}") + hints = (catalog_exercise_hints or "").strip() + if hints: + parts.append(f"Katalog-Hinweise Übungsanlage:\n{hints}") parts.append( "Die Übung muss die Stufe didaktisch erfüllen: klare Voraussetzungen, messbares Stufenziel, " "Bezug zum Gesamtpfad — keine generische Kraftübung ohne Technikbezug. " @@ -571,6 +592,7 @@ def build_gap_fill_offer( brief: Optional[PlanningSemanticBrief] = None, proposal: Optional[Mapping[str, Any]] = None, roadmap_snapshot: Optional[Mapping[str, Any]] = None, + catalog_exercise_hints: Optional[str] = None, ) -> Dict[str, Any]: source = spec.get("source") idx = int(spec.get("insert_after_index") or 0) @@ -603,6 +625,7 @@ def build_gap_fill_offer( step_a=step_a, step_b=step_b, roadmap_snapshot=enriched_snapshot or None, + catalog_exercise_hints=catalog_exercise_hints, ) ctx_preview = enriched_snapshot or None offer: Dict[str, Any] = { @@ -642,6 +665,7 @@ def apply_gap_fill_after_qa( max_ai_proposals: int = 3, auto_insert_proposals: bool = False, roadmap_snapshot: Optional[Mapping[str, Any]] = None, + catalog: Optional[ProgressionPlanningCatalogContext] = None, ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], List[Dict[str, Any]]]: """ Erzeugt gap_fill_offers für die UI; optional KI-Vorschläge einfügen. @@ -653,6 +677,7 @@ def apply_gap_fill_after_qa( out = list(steps) proposals: List[Dict[str, Any]] = [] offers: List[Dict[str, Any]] = [] + exercise_hints = catalog_exercise_hints_block(cur, catalog) if catalog else "" for spec in specs: source = spec.get("source") @@ -666,6 +691,7 @@ def apply_gap_fill_after_qa( brief=brief, spec=spec, steps=out, + catalog=catalog, ) offer = build_gap_fill_offer( spec=spec, @@ -674,6 +700,7 @@ def apply_gap_fill_after_qa( brief=brief, proposal=proposal, roadmap_snapshot=roadmap_snapshot, + catalog_exercise_hints=exercise_hints or None, ) offers.append(offer) if proposal and auto_insert_proposals: @@ -700,6 +727,7 @@ def apply_gap_fill_after_qa( brief=brief, proposal=None, roadmap_snapshot=roadmap_snapshot, + catalog_exercise_hints=exercise_hints or None, ) offers.append(offer) continue @@ -719,6 +747,7 @@ def apply_gap_fill_after_qa( gap=gap, title_hint=str(spec.get("title_hint") or ""), sketch_hint=str(spec.get("sketch") or ""), + catalog=catalog, ) offer = build_gap_fill_offer( @@ -728,6 +757,7 @@ def apply_gap_fill_after_qa( brief=brief, proposal=proposal, roadmap_snapshot=roadmap_snapshot, + catalog_exercise_hints=exercise_hints or None, ) offers.append(offer) diff --git a/backend/planning_exercise_path_builder.py b/backend/planning_exercise_path_builder.py index ccdb97f..377a0ab 100644 --- a/backend/planning_exercise_path_builder.py +++ b/backend/planning_exercise_path_builder.py @@ -24,9 +24,11 @@ from planning_catalog_context import ( load_catalog_context_from_graph_row, merge_catalog_context_into_target, ) +from catalog_prompt_slots import get_rematch_guard_for_catalog from planning_exercise_profiles import PlanningTargetProfile from planning_path_qa_pipeline import run_multistage_path_qa from planning_path_rematch import ( + apply_rematch_catalog_guard, collect_rematch_slot_indices, filter_rematch_slot_indices, prune_stripped_after_rematch, @@ -1737,6 +1739,13 @@ def _run_roadmap_rematch_loop( stripped_off_topic=current_stripped if round_idx == 0 else [], off_topic_steps=off_topic_before_strip if round_idx == 0 and use_initial_off_topic else [], ) + catalog_context = _resolve_planning_catalog_context(cur, body) + rematch_guard = get_rematch_guard_for_catalog(cur, catalog_context) + slot_indices = apply_rematch_catalog_guard( + slot_indices, + rematch_reasons, + rematch_guard=rematch_guard, + ) if not slot_indices: break @@ -2191,6 +2200,7 @@ def _run_evaluate_only_path_qa( max_ai_proposals=0, auto_insert_proposals=False, roadmap_snapshot=path_roadmap_snapshot, + catalog=catalog_context, ) multistage_qa = run_multistage_path_qa( @@ -4294,6 +4304,7 @@ def suggest_progression_path( max_ai_proposals=0, auto_insert_proposals=False, roadmap_snapshot=path_roadmap_snapshot, + catalog=catalog_context, ) if roadmap_gap_offers: diff --git a/backend/planning_intent_context.py b/backend/planning_intent_context.py index ce6e774..1ff0e16 100644 --- a/backend/planning_intent_context.py +++ b/backend/planning_intent_context.py @@ -49,9 +49,10 @@ class PlanningIntentContext: context_notes: str = "" topic_type: str = "general" technique_sibling_excludes: List[str] = field(default_factory=list) + catalog_exercise_hints: Optional[str] = None def to_api_dict(self) -> Dict[str, Any]: - return { + out = { "source_query": self.source_query, "primary_topic": self.primary_topic, "topic_type": self.topic_type, @@ -61,6 +62,10 @@ class PlanningIntentContext: "technique_sibling_excludes": self.technique_sibling_excludes[:16], "context_notes": self.context_notes[:1200] or None, } + hints = (self.catalog_exercise_hints or "").strip() + if hints: + out["catalog_exercise_hints"] = hints[:2000] + return out def build_planning_intent_context( @@ -70,6 +75,8 @@ def build_planning_intent_context( goal_analysis: Optional[Mapping[str, Any]] = None, extra_context: Optional[str] = None, primary_topic: Optional[str] = None, + cur=None, + catalog=None, ) -> PlanningIntentContext: """Intent aus Anfrage, Zielanalyse und optionalem Kontext — ohne Sonderregeln pro Thema.""" ga = dict(goal_analysis or {}) @@ -124,6 +131,12 @@ def build_planning_intent_context( if line not in path_success: path_success.insert(0, line) + catalog_hints = "" + if cur is not None and catalog is not None: + from planning_prompt_variables import catalog_exercise_hints_block + + catalog_hints = catalog_exercise_hints_block(cur, catalog) + return PlanningIntentContext( source_query=(goal_query or "").strip(), primary_topic=topic, @@ -133,6 +146,7 @@ def build_planning_intent_context( explicit_exclusions=explicit, technique_sibling_excludes=siblings[:16], context_notes=combined_notes[:1200], + catalog_exercise_hints=catalog_hints or None, ) diff --git a/backend/planning_path_rematch.py b/backend/planning_path_rematch.py index 4f47dfd..f98d533 100644 --- a/backend/planning_path_rematch.py +++ b/backend/planning_path_rematch.py @@ -7,6 +7,8 @@ from typing import Any, Dict, List, Mapping, Optional, Sequence, Set, Tuple from planning_progression_roadmap import ProgressionRoadmapContext, StageSpecArtifact +_UNFILLED_REMATCH_REASON = "Keine passende Übung für Roadmap-Stufe" + def _slot_priority_for_rematch( body, @@ -103,7 +105,7 @@ def collect_rematch_slot_indices( if isinstance(item, (list, tuple)) and len(item) >= 2: idx, spec = item[0], item[1] midx = getattr(spec, "major_step_index", idx) - _register(int(midx), "Keine passende Übung für Roadmap-Stufe") + _register(int(midx), _UNFILLED_REMATCH_REASON) elif isinstance(item, dict): midx = _resolve_major(item) if midx is not None: @@ -114,6 +116,25 @@ def collect_rematch_slot_indices( return indices, reasons +def apply_rematch_catalog_guard( + slot_indices: Set[int], + rematch_reasons: Mapping[int, str], + *, + rematch_guard: Optional[str], +) -> Set[int]: + """ + Bei gesetztem rematch_guard (Katalog-Slot): kein Auto-Rematch nur wegen leerer Roadmap-Stufe. + Off-Topic, explizite QS-Hinweise und Stufen-Verfeinerung bleiben aktiv. + """ + if not (rematch_guard or "").strip(): + return set(slot_indices) + return { + idx + for idx in slot_indices + if str(rematch_reasons.get(idx) or "").strip() != _UNFILLED_REMATCH_REASON + } + + def filter_rematch_slot_indices( steps: Sequence[Mapping[str, Any]], slot_indices: Set[int], @@ -372,6 +393,7 @@ def prune_stripped_after_rematch( __all__ = [ + "apply_rematch_catalog_guard", "collect_rematch_slot_indices", "filter_rematch_slot_indices", "prune_stripped_after_rematch", diff --git a/backend/planning_progression_roadmap.py b/backend/planning_progression_roadmap.py index 3bf3dcb..72bb917 100644 --- a/backend/planning_progression_roadmap.py +++ b/backend/planning_progression_roadmap.py @@ -1244,6 +1244,8 @@ def run_progression_roadmap_pipeline( resolved.target_state, ), primary_topic=goal_analysis.primary_topic, + cur=cur, + catalog=catalog, ) heuristic_specs = build_stage_specs( diff --git a/backend/planning_prompt_variables.py b/backend/planning_prompt_variables.py index fcec48e..89d6de5 100644 --- a/backend/planning_prompt_variables.py +++ b/backend/planning_prompt_variables.py @@ -34,6 +34,27 @@ _PLANNING_PROMPT_VARIABLE_PROVIDERS: tuple[PlanningPromptVariableProvider, ...] ) +def catalog_exercise_hints_block( + cur, + catalog: Optional[ProgressionPlanningCatalogContext] = None, + *, + slug: Optional[str] = None, +) -> str: + """Kombinierte hints_on_exercise aus aktivem Katalog-Kontext (Gap-Fill / Intent).""" + if cur is None or catalog is None: + return "" + from catalog_prompt_slots import CATALOG_KINDS, placeholder_key + + merged = merge_planning_prompt_variables(cur, {}, catalog=catalog, slug=slug) + lines: list[str] = [] + for cfg in CATALOG_KINDS: + key = placeholder_key(cfg.kind, "hints_on_exercise") + text = (merged.get(key) or "").strip() + if text: + lines.append(f"{cfg.label_de}: {text}") + return "\n".join(lines) + + def merge_planning_prompt_variables( cur, base_variables: Mapping[str, str], @@ -113,6 +134,7 @@ def planning_prompt_placeholder_catalog() -> dict: __all__ = [ + "catalog_exercise_hints_block", "merge_planning_prompt_variables", "planning_prompt_placeholder_catalog", ] diff --git a/backend/tests/test_planning_exercise_path_ai_fill.py b/backend/tests/test_planning_exercise_path_ai_fill.py index f39b3bf..bcf93a5 100644 --- a/backend/tests/test_planning_exercise_path_ai_fill.py +++ b/backend/tests/test_planning_exercise_path_ai_fill.py @@ -83,6 +83,20 @@ def test_strip_off_topic_steps_from_path(): assert [s["exercise_id"] for s in out] == [1, 2, 4] +def test_build_gap_fill_goal_text_includes_catalog_exercise_hints(): + from planning_exercise_semantics import build_semantic_brief + + brief = build_semantic_brief("Mae Geri") + text = build_gap_fill_goal_text( + goal_query="Mae Geri lernen", + brief=brief, + spec={"source": "roadmap_unfilled", "phase": "grundlage", "title_hint": "Stand"}, + catalog_exercise_hints="Primärfokus: Kihon und Partnerübungen mit Technikbezug.", + ) + assert "Katalog-Hinweise Übungsanlage" in text + assert "Kihon" in text + + def test_build_gap_fill_goal_text_includes_topic(): brief = build_semantic_brief("Mae Geri Perfektion") text = build_gap_fill_goal_text( diff --git a/backend/tests/test_planning_path_rematch.py b/backend/tests/test_planning_path_rematch.py index 2ee19f5..69b122b 100644 --- a/backend/tests/test_planning_path_rematch.py +++ b/backend/tests/test_planning_path_rematch.py @@ -336,3 +336,29 @@ def test_filter_rematch_skips_preserved_slots(): off_topic_steps=[], ) assert filtered == {1} + + +def test_apply_rematch_catalog_guard_skips_unfilled_only(): + from planning_path_rematch import apply_rematch_catalog_guard + + indices = {0, 1, 2} + reasons = { + 0: "QS-Tier-1", + 1: "Keine passende Übung für Roadmap-Stufe", + 2: "Passt nicht zur Haupttechnik", + } + filtered = apply_rematch_catalog_guard( + indices, + reasons, + rematch_guard="Keine leeren Slots erzwingen.", + ) + assert filtered == {0, 2} + + +def test_apply_rematch_catalog_guard_inactive_without_guard(): + from planning_path_rematch import apply_rematch_catalog_guard + + indices = {1} + reasons = {1: "Keine passende Übung für Roadmap-Stufe"} + assert apply_rematch_catalog_guard(indices, reasons, rematch_guard=None) == {1} + assert apply_rematch_catalog_guard(indices, reasons, rematch_guard="") == {1} diff --git a/backend/version.py b/backend/version.py index b81f759..9e27847 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,8 +1,8 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.237" +APP_VERSION = "0.8.238" BUILD_DATE = "2026-05-22" -DB_SCHEMA_VERSION = "20260607094" +DB_SCHEMA_VERSION = "20260607095" MODULE_VERSIONS = { "legal_documents": "1.4.0", # Admin: Live-Vorschau pro Abschnitt + modale Vollvorschau (Editor + Dokumentenliste) @@ -53,11 +53,20 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.238", + "date": "2026-05-22", + "changes": [ + "Planungs-KI H1.5: rematch_guard im Auto-Rematch (kein Rematch nur wegen leerer Roadmap-Stufe).", + "hints_on_exercise in Gap-Fill, Übungs-KI-Kontext und intent_context_json (Roadmap-Pipeline).", + "Migration 095: keine Migrations-Seeds in catalog_prompt_slots (Prod-sichere Katalogstruktur).", + ], + }, { "version": "0.8.237", "date": "2026-05-22", "changes": [ - "Migration 094: catalog_prompt_slots vollständig befüllt (Karate, SV, alle Trainingsstile/Zielgruppen).", + "Migration 094/095: Katalog-Prompt-Slots ohne Migrations-Seeds — Inhalte Admin oder Laufzeit-Fallback.", "catalog_slot_fallbacks: Namens-Fallback bis Admin-Override — gleiche Qualität wie H1-Registry.", ], }, diff --git a/docs/HANDOVER.md b/docs/HANDOVER.md index 6159fe6..3f7929b 100644 --- a/docs/HANDOVER.md +++ b/docs/HANDOVER.md @@ -1,7 +1,7 @@ # Shinkan Jinkendo – Entwicklungsstand & Handover -**Stand:** 2026-05-22 (F15 Graph-Match & getrennte Pfad-QS, lokal nach **0.8.233**) -**App-Version / DB-Schema:** App **`0.8.233`** (Planungs-KI F11–F14, Katalog-Kontext); **F15** siehe §2.8 — DB unverändert (`DB_SCHEMA_VERSION`, Migration **088**). +**Stand:** 2026-05-22 (Katalog-Prompt-Slots H2/H1.5, **0.8.238**) +**App-Version / DB-Schema:** App **`0.8.238`**; DB Migration **095** (keine Slot-Seeds; Prod-sichere Katalogstruktur). Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand**, **Medien-Meilenstein** und **sinnvolle nächste Schritte**. @@ -115,7 +115,8 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl | **F13** | **`planning_catalog_context`** (Fokus/Stil/TT/ZG) im Match + Graph-Artefakt | ✅ **0.8.233** | | **F14** | **`ProgressionGraphEditor`** — Slot-UI + Planungskontext-Dropdowns | ✅ **0.8.233** | | **F15** | Unified Slot-Review (Match-Dialog), getrennte Pfad-QS, `findings_stale` | ✅ lokal (nach 0.8.233) | -| **H1** | Katalog-Prompt-Snippets (modulare LLM-Anweisungen) | 🔲 Spec **`docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md`** | +| **H1–H2.1** | Katalog-Prompt-Slots (DB, Resolver, Admin-UI, granulare Prompts) | ✅ **0.8.234–238** — Spec **`docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md`** | +| **H1.5** | `rematch_guard`, `hints_on_exercise` in Rematch/Gap-Fill/Intent | ✅ **0.8.238** | **Architektur (verbindlich):** Drei Schichten — (1) **Katalog-Dimensionen** (DB, jetzt im Match verdrahtet; **H1:** zusätzlich Prompt-Snippets), (2) **Technik-Disambiguierung** (Code, nur bei `topic_type=technique`), (3) **Didaktik** (Roadmap + LLM-QS, nicht im Vokabular). Progressionsgraph = **Roadmap-first**, **keine Gruppenanalyse**. Bestehender Graph = **leichter Nachfolger-Bias** ab Schritt 2. Trainingsplanung = **eigene Pipeline** (Phase G) — Wiederverwendung der Bausteine, siehe Ist-Doku §16. @@ -148,7 +149,7 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl 5. Phase D′ — Auto KI-Gap-Fill bei persistent leeren Slots 6. **Trainingsplanung Phase G** — Gruppenkontext-Pack, Scopes `training_section` / `framework_slot` (Ist-Doku §16) 7. Technik-Katalog konfigurierbar (Backlog) -8. **H1** — Katalog-Prompt-Snippets (modulare LLM-Anweisungen) +8. **H1–H2.1** — Katalog-Prompt-Slots ✅ · **Prompt-Editor-Überarbeitung** → eigene Session #### Übungs-KI Formular / Schnellanlage (Stand **0.8.171**) diff --git a/docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md b/docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md index 19e29f6..85e8b02 100644 --- a/docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md +++ b/docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md @@ -1,7 +1,7 @@ # Planungs-KI — Katalog-Prompt-Slots (Snippets) **Stand:** 2026-05-22 -**Status:** **H2** umgesetzt (0.8.235) · **H2.1** Admin-UI + granulare Prompts (0.8.236) +**Status:** **H2.1** umgesetzt (0.8.236) · **H1.5** Rematch-Guard + Gap-Fill-Hints (0.8.238) · **Prompt-Editor-Überarbeitung** bewusst ausgelagert **Bezüge:** `PLANNING_PROGRESSION_GRAPH_KI.md` §4.4 · `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.4 · `planning_catalog_context.py` · `catalog_prompt_slots.py` --- @@ -173,12 +173,12 @@ Hardcodierte `SNIPPET_REGISTRY` — Proof of Concept für `catalog_guidance_bloc - [x] Slot-Editor an Fokusbereich / Trainingsstil / Zielgruppe / Stilrichtung (`CatalogPromptSlotsEditor`, Stammdaten-Katalog) - [x] Prompt-Templates mit granularen Platzhaltern (Migration 093) -- [ ] Platzhalter-Hilfe im KI-Prompt-Editor (erweitert) +- [ ] **Prompt-Editor komplett überarbeiten** (Platzhalter-Hilfe, UX) — **eigene Session** -### H1.5 +### H1.5 ✓ (0.8.238) -- [ ] `rematch_guard` im Rematch-Loop -- [ ] Intent-Prompts + Gap-Fill: `hints_on_exercise` +- [x] `rematch_guard` im Rematch-Loop (`apply_rematch_catalog_guard`) +- [x] Gap-Fill + Intent: `hints_on_exercise` via `catalog_exercise_hints_block` / `intent_context_json` ### H3 — Trainingsplanung (Phase G) @@ -211,6 +211,7 @@ Hardcodierte `SNIPPET_REGISTRY` — Proof of Concept für `catalog_guidance_bloc | Datum | Änderung | |-------|----------| +| 2026-05-22 | **H1.5** (0.8.238): rematch_guard; hints_on_exercise in Gap-Fill/Intent; Migration 095 ohne Seeds | | 2026-05-22 | **H2.1** (0.8.236): Admin-UI `CatalogPromptSlotsEditor`; Migration 093 granulare Prompt-Templates | | 2026-05-22 | **H2** (0.8.235): Slot-Typ-Register + `catalog_prompt_slots` DB, granulare Platzhalter, Admin-API | | 2026-05-22 | Konzept §4–§8: zwei Ebenen Slot-Typ vs. Slot-Wert; Platzhalter `{kind}_{slot_key}` | From 6c7c24e8878dcf6eeae428412c49f62ca8415825 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 14 Aug 2026 09:12:48 +0200 Subject: [PATCH 2/6] Add Jinkendo family design principles and entitlement model docs. Document cross-app architecture patterns, Mitai alignment, and family entitlement standards. Documentation only; no runtime changes. Co-authored-by: Cursor --- .../technical/DESIGN_PRINCIPLES_RELOCATED.md | 9 + .../DESIGN_PRINCIPLES_ALIGNMENT.md | 358 ++++++++++++++++++ .../FAMILY_ENTITLEMENT_MODEL.md | 316 ++++++++++++++++ docs/jinkendo-family/README.md | 48 +++ .../ACCESS_LAYER_DESIGN_PRINCIPLES.md | 170 +++++++++ .../AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md | 140 +++++++ .../AUTH_SESSION_DESIGN_PRINCIPLES.md | 114 ++++++ ...APABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md | 125 ++++++ .../CONTENT_REPORTS_DESIGN_PRINCIPLES.md | 117 ++++++ .../DASHBOARD_KPI_DESIGN_PRINCIPLES.md | 105 +++++ .../DESIGN_PRINCIPLES_INDEX.md | 143 +++++++ .../EXERCISE_CATALOG_DESIGN_PRINCIPLES.md | 128 +++++++ .../MATURITY_MODELS_DESIGN_PRINCIPLES.md | 117 ++++++ .../MEDIA_ASSETS_DESIGN_PRINCIPLES.md | 119 ++++++ .../MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md | 117 ++++++ .../NAVIGATION_IA_DESIGN_PRINCIPLES.md | 107 ++++++ .../RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md | 114 ++++++ .../SKILL_SCORING_DESIGN_PRINCIPLES.md | 107 ++++++ .../TRAINING_PLANNING_DESIGN_PRINCIPLES.md | 129 +++++++ .../WIKI_IMPORT_DESIGN_PRINCIPLES.md | 118 ++++++ 20 files changed, 2701 insertions(+) create mode 100644 .claude/docs/technical/DESIGN_PRINCIPLES_RELOCATED.md create mode 100644 docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md create mode 100644 docs/jinkendo-family/FAMILY_ENTITLEMENT_MODEL.md create mode 100644 docs/jinkendo-family/README.md create mode 100644 docs/jinkendo-family/design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/AUTH_SESSION_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/CONTENT_REPORTS_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/DASHBOARD_KPI_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md create mode 100644 docs/jinkendo-family/design-principles/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/MATURITY_MODELS_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/MEDIA_ASSETS_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/NAVIGATION_IA_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/SKILL_SCORING_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/TRAINING_PLANNING_DESIGN_PRINCIPLES.md create mode 100644 docs/jinkendo-family/design-principles/WIKI_IMPORT_DESIGN_PRINCIPLES.md diff --git a/.claude/docs/technical/DESIGN_PRINCIPLES_RELOCATED.md b/.claude/docs/technical/DESIGN_PRINCIPLES_RELOCATED.md new file mode 100644 index 0000000..0aae184 --- /dev/null +++ b/.claude/docs/technical/DESIGN_PRINCIPLES_RELOCATED.md @@ -0,0 +1,9 @@ +# Designprinzipien — verschoben + +Die **Jinkendo-Produktfamilie Designprinzipien** (Index + Shinkan-Serie) liegen nicht mehr hier. + +**Neuer Standort (Shinkan-Serie):** [docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md](../../../docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md) + +**Mitai-Serie:** [mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md) + +**Abgleich:** [docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md](../../../docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md) diff --git a/docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md b/docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md new file mode 100644 index 0000000..b686a18 --- /dev/null +++ b/docs/jinkendo-family/DESIGN_PRINCIPLES_ALIGNMENT.md @@ -0,0 +1,358 @@ +# Designprinzipien – Abgleich Mitai ↔ Shinkan + +**Status:** Review / Entscheidungsgrundlage +**Stand:** 2026-07-04 +**Zweck:** Widersprüche, bewusste Abweichungen und Implementierungslücken zwischen den Designprinzipien-Serien identifizieren — Basis für **Familien-Entscheidungen** und langfristige Konvergenz von Mitai und Shinkan. + +**Quellen:** + +| App | Index | +|-----|--------| +| Mitai (Foundation) | [mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md) — 9 Module | +| Shinkan | [design-principles/DESIGN_PRINCIPLES_INDEX.md](./design-principles/DESIGN_PRINCIPLES_INDEX.md) — 15 Module | + +--- + +## 1. Kurzfassung + +| Kategorie | Anzahl | Bedeutung | +|-----------|--------|-----------| +| **Familien-Konsens** | 12 Muster | In beiden Serien gleich oder kompatibel — **verbindlich für neue Apps** | +| **Bewusste Produkt-Abweichung** | 8 | Fachlich/Architektur begründet — **nicht angleichen**, aber im Familienmodell verankern | +| **Konzeptuelle Spannung** | 6 | Widersprüche oder gegenläufige Defaults — **Familien-Entscheidung nötig** | +| **Ist vs. Prinzip (Schuld)** | 14+ | Mindestens eine App verletzt eigene oder Schwester-Prinzipien — **Remediation** | +| **Nur Shinkan** | 6 Module | Mandanten-/Domänen-Bausteine ohne Mitai-Pendant | +| **Nur Mitai (reifer)** | 3 Muster | Data Layer, Widget-Dashboard, Universal Import — Shinkan vereinfacht oder fehlt | + +**Kernbefund:** Mitai und Shinkan teilen dieselbe **technische Basis** (Auth, Migration, Nav-SSoT, Registry-Denken, Probe→Enforce), divergieren aber strukturell bei **Entitlement-Subjekt** (Profil vs. Verein), **Berechnungsarchitektur** (generischer Data Layer vs. domänenspezifisches Scoring) und **KI-Reife** (Unified Executor vs. schmale Laufzeit). + +--- + +## 2. Familien-Konsens (für neue Produkte übernehmen) + +Diese Muster sind in beiden Serien explizit oder implizit tragfähig: + +| # | Muster | Mitai | Shinkan | +|---|--------|-------|---------| +| F1 | Server-Sessions + `Depends(require_auth)` | Auth #1–3 | Auth #1–2 | +| F2 | `profile_id` aus Session, nie aus Client-Header | Auth #3 | Auth #3 | +| F3 | Auth getrennt von Authorization/Entitlements | Auth #10 | Capabilities + Access Layer | +| F4 | Nummerierte SQL-Migrationen + Tracking beim Container-Start | Migration #1–5 | Migration #1–3 | +| F5 | Fail-fast, kein Auto-Rollback | Migration #5 | Migration #2 | +| F6 | `appNav` / zentrale Nav-Config als SSoT | Navigation #1 | Navigation #1 | +| F7 | Admin als eigener Hub/Realm | Navigation #6–7 | Navigation #2 | +| F8 | DB-konfigurierbare KI-Prompts (nicht hardcoded Prod) | Prompt #2 | AI Runtime #2 | +| F9 | Template vs. Kontext/Daten trennen | Prompt #5 | AI Runtime #4 | +| F10 | Ingest ≠ Interpretation beim Import | Import #2 | Wiki Import #1 | +| F11 | Preview/Dry-Run vor Massenimport | Import #3+ | Wiki Import #2 | +| F12 | 4-Phasen-Rollout Entitlements (Log → Enforce) | Feature #8 | Capability #2 | + +**Empfehlung:** Als **`JINKENDO_FOUNDATION_CHECKLIST`** in künftigen Apps verpflichtend; Details pro Modul in den Einzeldokumenten. + +--- + +## 3. Modul-Abgleich (9 vergleichbare Paare) + +Legende **Bewertung:** + +| Symbol | Bedeutung | +|--------|-----------| +| ✅ | Prinzipien aligned / kompatibel | +| ⚠️ | Teilweise aligned; Lücken in Implementierung oder Doku | +| 🔀 | Bewusste Produkt-Divergenz (kein Bug) | +| ❌ | Widerspruch oder gegenläufiges Konzept — Entscheidung nötig | +| 🏗️ | Ist-Stand verletzt dokumentierte Prinzipien (Architekturschuld) | + +--- + +### 3.1 Prompt Engine (Mitai #1) ↔ AI Prompt Runtime (Shinkan #5) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Single Entry Point | `execute_prompt` / Unified System | `ai_prompt_runtime` + verteilte Orchestratoren | ❌ Konzept | +| Prompt-Typen | base / pipeline / workflow | nur slug + Mustache | 🔀 Shinkan bewusst schlanker | +| Platzhalter-Registry | zentral, API-Verträge | Kontext-Arten (`AiPromptContextKind`), kein Registry-Katalog | ⚠️ | +| Data Layer-Anbindung | Layer 1 → Resolver | Domänen-Builder ad hoc | ⚠️ | +| Debug/Preview | ausgereift | Admin-Vorschau, weniger Runtime-Transparenz | ⚠️ | +| Feature-Gating an Execute | teils fehlend (Legacy) | Capability geplant, teils Probe | 🏗️ beide | + +**Widersprüche / gegenläufig:** + +- Mitai: **Ein Executor** ist Kernprinzip. Shinkan: **kein** vergleichbarer Executor — Planungs-KI umgeht teils die Laufzeit. +- Beide warnen vor **parallelen KI-Pfaden**; beide haben sie noch (Mitai `insights.py`, Shinkan Router-OpenRouter). + +**Familien-Entscheidung (Vorschlag):** + +| Option | Inhalt | +|--------|--------| +| **Zielbild** | Gemeinsame **`prompt_executor`-Fassade** (Package oder Copy mit Namespace); Shinkan-Kontext-Builder als Plugins | +| **Shinkan-Roadmap** | Planungs-Orchestrierung in Laufzeit ziehen; keine Workflow-Graphs vor Planungs-Kontext-Reife | +| **Nicht kopieren** | Mitai: doppelte Pipeline-Modelle, PLACEHOLDER_MAP-Duplikat, Roh-SQL im Executor | + +--- + +### 3.2 Data Layer (Mitai #2) ↔ Skill Scoring (Shinkan #8) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Berechnungs-SSoT | `data_layer/` Layer 0→1→2 | nur `skill_scoring.py` | ❌ Abdeckung | +| Router delegieren | explizites Prinzip #13 | Skill-Router ja; Planung teils nicht | ⚠️ | +| Confidence / data_points | Pflicht-Metadaten | nicht analog | 🔀 Domäne anders | +| Chart/KPI-Anbindung | Layer 2b Adapter | KPI-Dashboard ruft Router-Helfer | ⚠️ | +| Import-Grenze | keine Scores beim Insert | Wiki: explizit kein Scoring beim Insert | ✅ | + +**Widerspruch:** + +- Mitai postuliert **generische Berechnungsschicht** für die ganze App. Shinkan hat **kein** Data Layer — nur ein **domänenspezifisches** Scoring-Modul. Das ist keine Implementierungslücke allein, sondern **unterschiedliche Architektur-Tiefe**. + +**Familien-Entscheidung (Vorschlag):** + +| Option | Inhalt | +|--------|--------| +| **Familien-Prinzip** | „Berechnungen in benannter Schicht, nicht in Router/React“ — **Ja** | +| **Implementierung** | Mitai: `data_layer/` bleibt Referenz. Shinkan: Skill Scoring **ist** Layer-1-Vorbild; langfristig **`planning_metrics/`** o. ä. statt Router-SQL | +| **Nicht verallgemeinern** | Mitai-Formeln (TDEE, WHR) — nur Schichtenmodell übernehmen | + +--- + +### 3.3 Feature & Entitlement (Mitai #3) ↔ Capability & Club Features (Shinkan #2) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Subjekt | **Profil** + Tier | **Verein** (`club_id`) + Plan | ❌ Scope | +| Auflösungs-API | `check_feature_access` | `check_capability` + `club_features` + `/me/entitlements` | 🔀 | +| Rollout 4 Phasen | ja | ja (Env-Flags) | ✅ | +| Registry | DB `features` + Tiers | Code-Registry → DB Sync | ⚠️ | +| Capabilities vs. Features | Features only | Capabilities **und** Kontingente getrennt | 🔀 Shinkan feiner | +| Widget/Layout-Gating | zentral | Entitlements-API, kein Widget-Layout | ⚠️ | + +**Größter Familien-Konflikt:** + +Mitai-Dokument #3 „Nicht übernehmen“ Punkt 10: *„Profile as Entitlement-Subject — Multi-App-Familie braucht separates Identity/Subscription-Boundary.“* +Shinkan **ist** die Antwort mit Verein als Subjekt — aber es gibt **kein gemeinsames Familienmodell**, das Profil-Tier **und** Org-Limits kombiniert. + +**Familien-Entscheidung (Vorschlag):** + +``` +Entitlement-Subjekt (familie): + ├── account (profile_id) → Tier, persönliche Limits (Mitai) + └── tenant (club_id?) → Org-Plan, Capabilities (Shinkan, optional null) + +API: GET /me/entitlements?tenant_id= +Enforcement: eine resolve_entitlement(subject, capability|feature) +``` + +| App | Remediation | +|-----|-------------| +| Mitai | Org-Scope reservieren; Tier-Drift (#3 Schuld) bereinigen | +| Shinkan | `CLUB_FEATURE_ENFORCE=1` produktiv; Mitai-Legacy `check_feature_access` nicht nutzen (bereits Regel) | + +--- + +### 3.4 Registry / Plugin (Mitai #4) ↔ Rights Registry (Shinkan #3) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Registry-first | Platzhalter, Widgets, CSV-Module | Capabilities + Features only | ⚠️ Abdeckung | +| Validierung an Grenze | ja | ja (`register_*` wirft) | ✅ | +| Runtime + DB | Dual (Katalog + DB-Overrides) | Code → DB Upsert | ✅ | +| Dual Registry FE/BE | Widgets + `registerDashboardWidgets` | nicht vorhanden | 🔀 | +| Metadaten-Tiefe | nach Risiko (Matrix in Doc) | schlankere Dataclasses | ✅ | + +**Kein Widerspruch** — Shinkan Rights Registry ist **Teilmenge** des Mitai-Meta-Musters. + +**Familien-Entscheidung:** Mitai-Registry-Matrix (Platzhalter / UI-Plugin / Import-Modul / Rechte) als **Familien-Taxonomie**; Shinkan erweitert um Import-/Prompt-Registry wenn Wiki-Import generisch wird. + +--- + +### 3.5 Auth & Session (Mitai #5 ↔ Shinkan #4) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Session-Modell | opaque token | gleich (shared `auth.py`) | ✅ | +| Depends-Pattern | ja | ja (+ TenantContext) | ✅ | +| IDOR Profile-Header | dokumentierte Schwäche | Shinkan: Session-only betont | ⚠️ prüfen | +| RBAC | `role` admin/user | Portal-Rolle **+** Vereinsrollen | 🔀 | +| Rate Limiting | ja | (Mitai-spezifisch in Router) | ⚠️ | +| Feature-Flags in Session | Legacy-Spalten | Account-Lifecycle separat | 🏗️ Mitai | + +**Gegenläufig:** Shinkan **erweitert** Auth um Mandanten — darf Mandantenlogik **nicht** in `auth.py` legen (Shinkan-Prinzip „Nicht übernehmen“). + +**Familien-Entscheidung:** Gemeinsames Auth-Modul; **TenantContext** als optionales Add-on-Pattern für mandantenfähige Apps. + +--- + +### 3.6 Universal Import (Mitai #6) ↔ Wiki Import (Shinkan #11) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| Ingest ≠ Interpretation | ✅ | ✅ | ✅ | +| Modul-Registry | zentral | fehlt (wiki-hardcoded) | ⚠️ | +| SAVEPOINT pro Zeile | ja | nicht dokumentiert | ⚠️ | +| Vorlagen/Mappings | generisch | SMW-Kategorien via Env | 🔀 | +| Feature-Limits | an Import gebunden | Admin-only | ⚠️ | + +**Kein Konflikt** — unterschiedliche Reife. Shinkan ist **Spezialfall** des Mitai-Musters. + +**Familien-Entscheidung:** Neue Import-Quellen über **Universal-Import-Gerüst** (Mitai); Wiki als `import_type=mediawiki` registrieren. + +--- + +### 3.7 Dashboard Widgets (Mitai #7) ↔ Dashboard KPIs (Shinkan #14) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| UX-Modell | konfigurierbares Widget-Layout | festes KPI-Aggregat | ❌ UX-Konzept | +| Chatty Client vermeiden | via Widget-Daten | via `/dashboard/kpis` | ✅ Ziel | +| Entitlements | `allowed` pro Widget | TenantContext auf KPIs | ✅ | +| Data Layer | Widgets konsumieren Layer 1 | intern Router-Helfer | ⚠️ | + +**Gegenläufig:** Mitai: **Nutzer konfiguriert Dashboard**. Shinkan: **Produkt definiert feste Kacheln** — bewusste MVP-Vereinfachung. + +**Familien-Entscheidung:** + +| App-Typ | Dashboard-Pattern | +|---------|-------------------| +| Personal Tracking (Mitai) | Widget-Katalog + Layout-JSON | +| Trainer/Verein (Shinkan) | Aggregierte KPI-Endpoints ausreichend; Widget-System optional Phase 2 | +| Neue App | Aggregat-Endpoint **mindestens**; Widget-System wenn Personalisierung nötig | + +--- + +### 3.8 Navigation / IA (Mitai #8 ↔ Shinkan #12) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| appNav SSoT | ja | ja | ✅ | +| Admin-Hub | Shell + Hub-Gruppen | horizontale `AdminPageNav` | ⚠️ | +| Breakpoint 1024px | explizit | „prüfen“ | ⚠️ | +| Onboarding-Nav | — | reduziert ohne Verein | 🔀 Shinkan | +| adminNav.js SSoT | empfohlen | hardcoded Array in JSX | 🏗️ Shinkan | +| Safe Area PWA | ja | Design-System, weniger explizit | ⚠️ | + +**Familien-Entscheidung:** `appNav.js` + **`adminNav.js`** als Pflicht; Shinkan `AdminPageNav` refactoren. + +--- + +### 3.9 Migration & Deploy (Mitai #9 ↔ Shinkan #13) + +| Aspekt | Mitai | Shinkan | Bewertung | +|--------|-------|---------|-----------| +| XXX_*.sql + schema_migrations | ✅ | ✅ | ✅ | +| Startup vor App | ✅ | ✅ | ✅ | +| develop/main | ✅ | ✅ | ✅ | +| Feste Ports | ✅ | ✅ | ✅ | +| Immutabler Docker-Build | dokumentiert | nicht im Shinkan-Doc | ⚠️ Doku | +| Health-Check / PG wait | ausführlich | kürzer | ⚠️ Doku | + +**Aligned** — Shinkan-Dokument ist **Untermenge**; Implementierung vermutlich gleich (shared Infra). + +--- + +## 4. Nur Shinkan (6 Module) — Einordnung für die Familie + +| Modul | Familien-Relevanz | Mitai-Bezug | +|-------|-------------------|------------| +| **Access Layer & Tenant** | **Pflicht** für mandantenfähige Apps | Mitai #3 fordert Org-Boundary — hier ausformuliert | +| **Media Assets & Archiv** | Optional (Content-Apps) | — | +| **Exercise Catalog** | Shinkan-Domäne | — | +| **Training Planning** | Shinkan-Domäne | — | +| **Content Reports (P-13)** | Empfohlen für UGC/Plattform | — | +| **Maturity Models** | Optional (Kompetenz-Apps) | — | + +**Kein Widerspruch zu Mitai** — ergänzen das Familienmodell um **Mandant + Content-Governance**. + +--- + +## 5. Querschnitt: Ist-Stand vs. dokumentierte Prinzipien + +Gemeinsame **Architekturschuld** (beide Apps verletzen teils eigene „Nicht übernehmen“-Listen): + +| Thema | Mitai | Shinkan | +|-------|-------|---------| +| Parallele KI-Pfade | `insights.py` Legacy | OpenRouter direkt in Routern | +| Entitlement Enforcement | teils UI-only / Legacy-Spalten | Env Probe-only | +| Registry-Sync / Duplikat | PLACEHOLDER_MAP + Registry | Capabilities-Sync, kein Prompt-Registry | +| Frontend ohne Backend-Gate | teils | Capabilities Probe | +| Dokumentations-Drift | Tier vs. Enforcement-Docs | Endpoint-Audit unvollständig | +| Monolithische Pages/Client | God Pages, api.js | God Pages, api.js (Roadmap Phase 4) | +| Fehlende JSON-Schema-KI | TODO | TODO (explizit vermeiden) | + +--- + +## 6. Entscheidungs-Matrix (Priorisiert) + +| Prio | Entscheidung | Betroffene Apps | Empfohlene Familien-Regel | +|------|--------------|-----------------|---------------------------| +| **P0** | Entitlement-Subjekt: Profil **und** optional Tenant | Mitai, Shinkan, neu | Ein API-Shape `/me/entitlements`; zwei Subjekt-Ebenen | +| **P0** | Kein Client-`profile_id` für AuthZ | alle | Session-only; Tenant via Header + Membership | +| **P1** | KI: ein Executor pro App | Mitai (fertig), Shinkan (Ziel) | `execute_prompt(slug, context_dto)` | +| **P1** | Berechnungs-SSoT-Schicht | Shinkan erweitern | Mindestens ein `*/metrics.py` pro Domäne mit KPIs | +| **P1** | Enforcement produktiv | beide | Phase 4 Enforce in Prod für kritische Features | +| **P2** | Registry-Taxonomie vereinheitlichen | beide | Rechte / Platzhalter / Import / UI-Plugin | +| **P2** | Admin-Nav SSoT | Shinkan | `adminNav.js` wie Mitai | +| **P2** | Import: Universal + Spezialmodule | Shinkan | Wiki als registriertes Modul | +| **P3** | Dashboard: Aggregat vs. Widgets | produktabhängig | Entscheidungsbaum §3.7 | +| **P3** | Shared `auth.py` / `db_init` | beide | Monorepo-Package oder Sync-Disziplin | + +--- + +## 7. Konvergenz-Roadmap (langfristig) + +```mermaid +flowchart LR + subgraph foundation [Familien-Foundation] + M9[Migration] + M5[Auth] + F3[Entitlements 2-Ebenen] + R4[Registry Meta] + end + + subgraph mitai [Mitai] + DL[Data Layer] + PE[Prompt Engine] + DW[Dashboard Widgets] + end + + subgraph shinkan [Shinkan] + AL[Access Layer] + AR[AI Runtime → Executor] + SS[Skill Scoring → Layer 1] + end + + M9 --> mitai + M9 --> shinkan + M5 --> mitai + M5 --> shinkan + F3 --> mitai + F3 --> shinkan + R4 --> mitai + R4 --> shinkan + AL -.->|Mandanten-Apps| foundation + PE -.->|Konvergenz| AR + DL -.->|Schichtenmodell| SS +``` + +| Phase | Mitai | Shinkan | +|-------|-------|---------| +| **Kurz** | Legacy KI-Pfade entfernen; Enforcement-Doku vereinheitlichen | Access-Layer-Audit abschließen; `CAPABILITY_ENFORCE` | +| **Mittel** | Org-Scope in Entitlements vorbereiten | `ai_prompt_runtime` → Unified Executor; Router-Helfer statt KPI-Duplikat | +| **Lang** | SSO/Identity (Vision) | Data-Layer-ähnliche Module für Planung; optional Widget-Dashboard | + +--- + +## 8. Nächste Schritte + +1. **Review-Workshop:** Tabelle §6 P0–P1 durchgehen und Familien-Regeln verbindlich markieren. +2. ~~**`FAMILY_ENTITLEMENT_MODEL.md`** anlegen (P0)~~ → [FAMILY_ENTITLEMENT_MODEL.md](./FAMILY_ENTITLEMENT_MODEL.md) (Entwurf 2026-07-04) +3. **Shinkan-Index** und **Mitai-Foundation-README** auf dieses Dokument verlinken. +4. **`FAMILY_ENTITLEMENT_MODEL.md`** — Entwurf angelegt (§6 Produkt-Abweichungen, §8 Regeln neue Apps). +5. Pro **P1-Punkt** Issue/Remediation-Eintrag in jeweiliger `SCHULDEN_UND_REMEDIATION` / Mitai-Äquivalent. + +--- + +## 9. Changelog + +| Datum | Änderung | +|-------|----------| +| 2026-07-04 | Erstfassung Abgleich Mitai Foundation (9) ↔ Shinkan (15) | diff --git a/docs/jinkendo-family/FAMILY_ENTITLEMENT_MODEL.md b/docs/jinkendo-family/FAMILY_ENTITLEMENT_MODEL.md new file mode 100644 index 0000000..b5df696 --- /dev/null +++ b/docs/jinkendo-family/FAMILY_ENTITLEMENT_MODEL.md @@ -0,0 +1,316 @@ +# Familien-Modell: Entitlements & Limits + +**Status:** Entwurf / verbindliche Zielrichtung (mit bewussten Produkt-Ausnahmen) +**Stand:** 2026-07-04 +**Bezüge:** + +- [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) — Abgleich Mitai ↔ Shinkan +- Mitai: [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- Shinkan: [CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) + +--- + +## 1. Zweck + +Dieses Dokument definiert das **gemeinsame Entitlement-Modell** der Jinkendo-Produktfamilie: + +- **Was** für neue Apps und Refactors **Standard** ist +- **Welche Produkt-Profile** welche Scopes nutzen (Account, Tenant, beides, keins) +- **Wie** bewusste Abweichungen dokumentiert werden — Abweichung ist erlaubt, **Undokumentiertheit** nicht + +**Nicht enthalten:** Stripe/SSO (`auth.jinkendo.de`), vollständige Billing-Implementierung, app-spezifische Feature-IDs. + +--- + +## 2. Leitgedanke: Vier getrennte Fragen + +Jede geschützte Aktion durchläuft konzeptionell **vier unabhängige Prüfungen**. Nicht jede App implementiert alle vier — siehe §6. + +| # | Frage | Familien-Begriff | Typische Quelle | +|---|--------|------------------|-----------------| +| **A** | Wer ist eingeloggt? | **Auth** (Identität) | Session → `profile_id` | +| **B** | Darf diese Rolle die **Funktion** ausführen? | **Capability** (Permission) | Rollen-Matrix, `min_account_state` | +| **C** | Ist das **Kontingent** erschöpft? | **Feature / Limit** (Quota) | Plan, Tier, Usage-Zähler | +| **D** | Darf ich **dieses Objekt** lesen/ändern? | **Governance** (Object ACL) | `visibility`, `club_id`, Owner | + +``` +Request → Auth (A) → [Capability (B)] → [Feature-Limit (C)] → [Governance (D)] → Handler +``` + +**Familien-Regel:** B, C und D **nicht** in React-Widgets oder Router-Inline-Logik vermischen — jeweils eine Auflösungsfunktion pro Ebene. + +**Shinkan-Ergänzung:** Governance ist dort ausgebaut (`TenantContext`, Access Layer); Mitai fokussiert A+B+C auf Profil-Ebene. + +--- + +## 3. Familien-Standard: Zwei Subjekt-Ebenen + +Limits und Pläne können an **zwei Subjekte** hängen. Beide sind im Familienmodell **first-class** — Apps wählen, welche sie nutzen (§6). + +| Subjekt | ID | Typische Frage | Beispiel | +|---------|-----|----------------|----------| +| **Account** | `profile_id` | Was darf **ich** als Nutzer (Tarif)? | Mitai Free vs. Premium | +| **Tenant** | `tenant_id` (z. B. `club_id`) | Was darf **meine Organisation**? | Shinkan Vereinsplan, KI-Kontingent | + +### 3.1 Auflösungs-Reihenfolge (wenn beide Ebenen aktiv) + +Für eine Aktion mit Capability `X` und Feature `Y`: + +1. **Account-Lifecycle** — z. B. E-Mail verifiziert, Onboarding abgeschlossen +2. **Capability(B)** — Rolle darf Funktion (Account- und/oder Tenant-Rollen) +3. **Feature(C)** — Kontingent am **primären Billing-Subjekt** der App (siehe Produkt-Profil) +4. **Governance(D)** — Objekt sichtbar/bearbeitbar + +**AND-Verknüpfung:** Alle aktiven Ebenen müssen passieren. Ausnahmen nur in §6.3 dokumentiert. + +### 3.2 Primäres Billing-Subjekt pro App + +| Profil | Primäres Subjekt für Limits | Capability-Subjekt | +|--------|----------------------------|-------------------| +| Personal App (Mitai) | **Account** | Account | +| Mandanten-App (Shinkan) | **Tenant** | Account + Tenant-Rolle | +| Hybrid (Zukunft) | konfigurierbar | beide | + +--- + +## 4. Familien-Standard: Capabilities vs. Features + +| Konzept | Familien-Definition | Subjekt | Beispiel | +|---------|---------------------|---------|----------| +| **Capability** | Binäre oder rollenbasierte **Erlaubnis** („darf ich?“) | meist Account + Tenant-Kontext | `exercises.ai.suggest` | +| **Feature** | **Kontingent** oder Boolean-Limit („wie oft/noch?“) | Account **oder** Tenant | `ai_calls` / Monat | +| **Verknüpfung** | Capability kann `linked_feature_id` haben | — | KI-Capability → KI-Kontingent | + +**Familien-Regel:** + +- Capabilities **registry-first** registrieren (Code → DB-Sync, Shinkan-Muster). +- Feature-IDs **nicht** in UI hardcoden — nur aus Entitlements-Response. +- `NULL` Limit = unbegrenzt; `0` = deaktiviert (Mitai-Semantik, familienweit). + +--- + +## 5. Familien-Standard: API & Enforcement + +### 5.1 Ziel-API (neue Apps) + +Ein **einheitlicher Snapshot** für das Frontend: + +``` +GET /api/me/entitlements + ?tenant_id= + +Response (skizziert): +{ + "account": { + "profile_id": 1, + "account_state": "active_member", + "tier_id": "premium", // optional, Account-Apps + "features": { "ai_calls": { "allowed", "used", "limit", "remaining", "reset_at" } }, + "capabilities": { "analysis.run": { "allowed": true, "reason": null } } + }, + "tenant": { // null wenn App keinen Tenant kennt + "tenant_id": 42, + "tenant_type": "club", + "plan_id": "pro", + "features": { ... }, + "capabilities": { ... }, + "roles": ["trainer"] + }, + "enforcement": { + "capabilities": "enforce|probe", + "features": "enforce|probe" + } +} +``` + +**Familien-Regel:** UI liest **nur** diesen Snapshot (oder domänenspezifische Teilmenge) — keine parallelen `/subscription/me` + `/features/usage` + Ad-hoc-Checks in neuen Apps. + +### 5.2 Ist-API (bestehende Apps — Abweichung dokumentiert) + +| App | Endpoint heute | Familien-Ziel | +|-----|----------------|---------------| +| **Mitai** | `/subscription/me`, `/features/usage`, `check_feature_access` | Snapshot schrittweise; Account-Block reicht | +| **Shinkan** | `GET /api/me/entitlements?club_id=` | Tenant-Block + Capabilities; Account-Tier fehlt bewusst | + +Migration: **kein Big-Bang** — alte Endpoints als Facade auf Snapshot mappen. + +### 5.3 Vier-Phasen-Rollout (familienweit verbindlich) + +| Phase | Verhalten | Env-Beispiel | +|-------|-----------|--------------| +| **1** | Cleanup Legacy-Flags | — | +| **2** | **Probe** — JSON-Log, HTTP 200 | `*_ENFORCE=0` | +| **3** | Frontend-Gates aus Entitlements | — | +| **4** | **Enforce** — HTTP 403 | `*_ENFORCE=1` | + +**Familien-Regel:** Phase 4 für **neue** kritische Features von Anfang an planbar; Bestands-Apps dürfen in Phase 2–3 bleiben bis kalibriert. + +### 5.4 Enforcement-Priorität + +1. **API** — autoritativ (`403`) +2. **Frontend** — UX (Badges, disabled Buttons) +3. **Niemals** — nur UI ohne API-Gate + +--- + +## 6. Produkt-Profile & bewusste Abweichungen + +Abweichungen vom Familien-Standard sind **zulässig**, wenn sie in der Tabelle **§6.2** stehen und begründet sind. + +### 6.1 Profil-Matrix (Soll) + +| Profil | Apps | Account-Limits | Tenant-Limits | Capabilities | Governance (Objekt) | +|--------|------|----------------|---------------|--------------|---------------------| +| **P1 Personal** | Mitai | ✅ primär | ❌ | ✅ Account | minimal / privat | +| **P2 Mandant** | Shinkan | ⚠️ Lifecycle only | ✅ primär | ✅ Account + Tenant-Rollen | ✅ Access Layer | +| **P3 Minimal** | Miken, Ikigai (geplant) | optional | ❌ | optional | minimal | +| **P4 Hybrid** | (Reserve) | ✅ | ✅ | ✅ beide | ✅ | + +### 6.2 Registrierter Abweichungs-Katalog + +| ID | App | Abweichung vom Familien-Standard | Begründung | Review | +|----|-----|----------------------------------|------------|--------| +| **DEV-01** | Mitai | Kein `tenant`-Block in Entitlements | Persönliche Tracking-App; kein Verein | Beibehalten (P1) | +| **DEV-02** | Mitai | Kein Capability-Katalog (nur Features+Tier) | RBAC = admin/user ausreichend | Optional später `account.*` Capabilities | +| **DEV-03** | Mitai | Mehrere Nutzer-APIs statt einem Snapshot | Historisch gewachsen | Facade → Snapshot (mittelfristig) | +| **DEV-04** | Mitai | Legacy Session-Spalten (`ai_enabled`, …) parallel Features | Migrationsschuld | Bereinigen, nicht in neue Apps | +| **DEV-05** | Shinkan | Kein Account-Tier / `profiles.tier` | Verein zahlt, nicht Trainer | Beibehalten (P2) | +| **DEV-06** | Shinkan | Capabilities **und** Governance (zwei Achsen) | Trainer vs. Objekt-Rechte | Familien-Vorbild für P2/P4 | +| **DEV-07** | Shinkan | `check_feature_access` (Mitai-Legacy) explizit verboten | Falsches Subjekt | Beibehalten | +| **DEV-08** | Shinkan | Enforcement oft Probe-only | Rollout-Sicherheit | → Phase 4 bis Datum X | +| **DEV-09** | Shinkan | Inventar-Features live gezählt | Drift-Vermeidung | Abweichung OK; in P2 dokumentieren | +| **DEV-10** | Beide | Kein atomares Check+Increment | Race bei Parallel-Requests | Familien-Backlog; Workaround dokumentieren | +| **DEV-11** | Neue Apps | dürfen **nur** Account **oder** nur Tenant wählen | MVP | Eintrag hier anlegen vor Launch | + +**Neue Abweichung:** Zeile in §6.2 + ggf. ein Satz im App-`CLAUDE.md`. + +### 6.3 Dokumentierte Ausnahmen (Bypass) + +| Ausnahme | Apps | Regel | +|----------|------|-------| +| Plattform-Admin Audit | Shinkan | Quota-Bypass über Grants, nicht pauschal superadmin | +| Admin ohne Auto-Bypass | Mitai | Admins unterliegen Limits (Produktentscheid) | +| Öffentliche Routen ohne Auth | alle | Kein Entitlement-Check | + +--- + +## 7. Mapping: Familien-Begriff ↔ Implementierung + +### 7.1 Mitai (Profil P1) + +| Familien | Mitai-Implementierung | +|----------|----------------------| +| Account-Features | `features`, `tier_limits`, `user_feature_usage` | +| Account-Tier | `get_effective_tier()`, `access_grants` | +| Check | `check_feature_access(profile_id, feature_id)` | +| Increment | `increment_feature_usage(profile_id, …)` | +| UI | `UsageBadge`, Widget `allowed` | + +### 7.2 Shinkan (Profil P2) + +| Familien | Shinkan-Implementierung | +|----------|-------------------------| +| Tenant-Features | `club_plan_limits`, `club_feature_usage`, `club_features.py` | +| Tenant-Plan | `get_effective_club_plan(club_id)` | +| Capabilities | `check_capability`, `capabilities` + `rights_registry` | +| Snapshot | `build_me_entitlements()` → `GET /me/entitlements` | +| Governance | `TenantContext`, `club_tenancy` — **kein** Ersatz für Capabilities | + +### 7.3 Gemeinsame Muster (copy-ready) + +| Muster | Mitai | Shinkan | +|--------|-------|---------| +| Feature-Registry in DB | ✅ | ✅ (`app='shinkan'`) | +| Plan × Feature Matrix | `tier_limits` | `club_plan_limits` | +| Admin-Override | `user_feature_restrictions` | `club_feature_overrides` | +| Promo/Trial | `access_grants` | `club_access_grants` | +| 4-Phasen-Rollout | ✅ | ✅ | +| Registry-first neue IDs | ⚠️ teils hardcoded | ✅ `rights_registrations/` | + +--- + +## 8. Entscheidungsregeln für neue Produkte + +### 8.1 Pflicht (alle Apps mit Auth) + +- [ ] Session → `profile_id`; kein Client-Header als Autorität +- [ ] Entitlements-Auflösung **eine Funktion pro Ebene** (Capability, Feature) +- [ ] API-Enforcement vor UI-Gate +- [ ] 4-Phasen-Rollout dokumentiert +- [ ] Produkt-Profil (P1–P4) gewählt + Abweichungen in §6.2 + +### 8.2 Wenn Personal App (P1) + +- [ ] Primäres Subjekt = Account +- [ ] `tenant`-Block in API = `null` (DEV-01-Analog) +- [ ] Feature-Registry + Tier-Matrix + +### 8.3 Wenn Mandanten-App (P2) + +- [ ] Primäres Subjekt = Tenant +- [ ] `TenantContext` + Governance getrennt von Capabilities +- [ ] Capabilities registry-first +- [ ] Account nur Lifecycle (verified, member) — kein Tier nötig (DEV-05-Analog erlaubt) + +### 8.4 Wenn Minimal App (P3) + +- [ ] Explizit: „keine Limits“ oder nur Boolean-Features — in §6.2 eintragen +- [ ] Kein halbes Mitai-v9c kopieren + +--- + +## 9. Konvergenz-Roadmap (optional, nicht blockierend) + +| Schritt | Mitai | Shinkan | Familie | +|---------|-------|---------|---------| +| **Kurz** | Legacy Session-Flags entfernen | `CAPABILITY_ENFORCE` / `CLUB_FEATURE_ENFORCE` Prod | DEV-04, DEV-08 schließen | +| **Mittel** | `/me/entitlements` Account-Block | Snapshot um `tenant`-Typ metadata erweitern | Facade alte APIs | +| **Lang** | Optional `tenant_id` reservieren (null) | Optional Account-Tier für Cross-Sell | Shared package `jinkendo_entitlements` | +| **Vision** | SSO + zentraler Billing | Vereins-Abo Stripe | `CENTRAL_SUBSCRIPTION_SYSTEM` | + +**Wichtig:** Konvergenz ist **empfohlen**, nicht Pflicht — solange §6.2 aktuell bleibt. + +--- + +## 10. Anti-Patterns (familienweit verboten) + +1. Tier- oder Plan-Namen in React-Komponenten hardcoden +2. Limit-Logik nur im Frontend +3. Shinkan-Vereinslimits über Mitai `check_feature_access(profile_id)` +4. Capability-Check durch Governance ersetzen (oder umgekehrt) +5. Neue Feature-IDs nur in SQL-Migration ohne Registry +6. Enforcement Phase 4 „vergessen“ bei paid Features ohne dokumentierte Probe-Phase +7. Undokumentierte Produkt-Abweichung (nicht in §6.2) + +--- + +## 11. Offene Familien-Entscheidungen (Backlog) + +| ID | Frage | Optionen | Default wenn unentschieden | +|----|-------|----------|----------------------------| +| **FD-01** | Atomares check+increment | DB-Lock / Transaction / Queue | Status quo + Retry-Hinweis in Doku | +| **FD-02** | Shared Python-Modul | Monorepo-Paket vs. Copy+Sync | Copy+Sync mit gleicher API-Shape | +| **FD-03** | Capability-Namespace global | `jinkendo.*` vs. app-prefix | `{app}.{domain}.{action}` | +| **FD-04** | Mitai bekommt Capability-Layer? | ja/nein/später | nein (DEV-02) bis Bedarf | +| **FD-05** | Ein `features.app` für alle Apps | gemeinsame DB vs. pro Deploy | pro Deploy (heute) | + +--- + +## 12. Verwandte Dokumente + +| Dokument | Inhalt | +|----------|--------| +| [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) | Vollständiger Mitai ↔ Shinkan Abgleich | +| [design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) | Shinkan Ist-Prinzipien | +| [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md) | Governance (Ebene D) | +| Mitai Foundation #3 | Feature & Entitlement Ist Mitai | +| Shinkan `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | Vereins-Abo Detail | +| Shinkan `CAPABILITY_CATALOG.v1.md` | Capability-IDs | + +--- + +## 13. Changelog + +| Datum | Änderung | +|-------|----------| +| 2026-07-04 | Entwurf: Familien-Standard, Produkt-Profile P1–P4, Abweichungs-Katalog DEV-01–11 | diff --git a/docs/jinkendo-family/README.md b/docs/jinkendo-family/README.md new file mode 100644 index 0000000..564790c --- /dev/null +++ b/docs/jinkendo-family/README.md @@ -0,0 +1,48 @@ +# Jinkendo Produktfamilie – Dokumentation + +**Zweck:** Querschnitts-Dokumentation, die **über einzelne Apps hinaus** gilt (Mitai, Shinkan, künftige Schwester-Produkte). + +**Stand:** 2026-07-04 + +--- + +## Inhalt + +| Verzeichnis | Zweck | +|-------------|--------| +| [design-principles/](./design-principles/) | Extrahierte **Designprinzipien** pro Modul — Foundation für neue Apps und Familien-Review | + +--- + +## Designprinzipien + +**Einstieg Shinkan:** [design-principles/DESIGN_PRINCIPLES_INDEX.md](./design-principles/DESIGN_PRINCIPLES_INDEX.md) + +**Einstieg 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) + +**Abgleich & Entscheidungen:** [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) — Widersprüche, Abweichungen, Familien-Entscheidungs-Matrix + +Die Serie dokumentiert: + +- **Mitai** (9 Module) — Referenzimplementierung unter `mitai-jinkendo/.claude/docs/jinkendo-foundation/` +- **Shinkan** (15 Module) — in [design-principles/](./design-principles/) + +Geplanter nächster Schritt: **FD-***-Entscheidungen im [FAMILY_ENTITLEMENT_MODEL.md](./FAMILY_ENTITLEMENT_MODEL.md) durcharbeiten; Enforcement Phase 4 wo vorgesehen. + +| Dokument | Inhalt | +|----------|--------| +| [DESIGN_PRINCIPLES_ALIGNMENT.md](./DESIGN_PRINCIPLES_ALIGNMENT.md) | Mitai ↔ Shinkan Abgleich | +| [FAMILY_ENTITLEMENT_MODEL.md](./FAMILY_ENTITLEMENT_MODEL.md) | **Familien-Standard** Entitlements + Produkt-Abweichungen | + +--- + +## Bezug zu App-spezifischer Doku + +| Ebene | Ort (Beispiel Shinkan) | +|-------|-------------------------| +| **Familie** (Prinzipien) | `docs/jinkendo-family/` | +| **App-Architektur** | `docs/architecture/` | +| **Technische Specs** | `.claude/docs/technical/` | +| **Agent-Regeln** | `.claude/rules/`, `CLAUDE.md` | + +Implementierungsdetails und Domänen-Specs bleiben in den jeweiligen App-Repositories; Designprinzipien beschreiben **übertragbare Muster** und **bewusste Lücken**. diff --git a/docs/jinkendo-family/design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..17161a7 --- /dev/null +++ b/docs/jinkendo-family/design-principles/ACCESS_LAYER_DESIGN_PRINCIPLES.md @@ -0,0 +1,170 @@ +# Access Layer & Tenant – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Access Layer & Tenant Governance“ — keine Shinkan-Gesamtarchitektur, keine Kampfsport-Domänenlogik + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 1 von 15 +**Mitai-Vergleich:** kein direktes Gegenstück (Mitai ist profil-zentriert, kein Vereins-Mandant) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| TenantContext | `backend/tenant_context.py` | +| Governance-Helfer | `backend/club_tenancy.py` | +| Listenfilter SQL | `library_content_visibility_sql()` in `tenant_context.py` | +| Endpoint-Audit | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` | +| Cursor-Regel | `.cursor/rules/access-layer.mdc` | +| Heuristik-Check | `backend/scripts/check_access_layer_hints.py` | +| Normative Spec | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | + +--- + +## Modul + +**Access Layer & Tenant Governance** + +Zentraler Querschnitt für Mandanten-Kontext (`club_id`), Sichtbarkeit (`private`/`club`/`official`) und einheitliche Les-/Schreibregeln für Bibliotheksartefakte (Übungen, Medien, Rahmenprogramme, Vorlagen, Progressionsgraphen). + +--- + +## Fachliche Verantwortung + +1. **TenantContext pro HTTP-Request** — Auflösung aus Session + Header `X-Active-Club-Id` + Profilfeld `active_club_id`. +2. **Datenisolierung** — `club_id` als Grenze für vereinsgeteilte Inhalte; Cross-Verein ausgeschlossen. +3. **Einheitliche Sichtbarkeits-Semantik** — gleiche Enums und Prüflogik über alle Bibliotheksmodule. +4. **Governance-Transitionen** — Regeln beim Wechsel `private` → `club` → `official` und beim Löschen. +5. **Listenfilter** — SQL-Baustein statt „SELECT *“ in jedem Router. + +### Administrierte Konfigurationen + +| Konfiguration | Speicherort | Inhalt | +|---------------|-------------|--------| +| Aktiver Verein | `profiles.active_club_id` | Persistierter UI-Kontext | +| Request-Override | Header `X-Active-Club-Id` | Client-seitiger Mandantenwechsel | +| Sichtbarkeit | Spalte `visibility` je Objekt | `private`, `club`, `official` | +| Vereinszuordnung | Spalte `club_id` | Pflicht bei `club`-Inhalten | + +### Bewusst nicht hardcodiert + +- Welche Objekte welchen Verein haben (Daten) +- Individuelle Freigabeentscheidungen (Workflow) + +### Hardcodiert (Code) + +- Enum-Werte und Leseregeln in `club_tenancy.py` / `library_content_visibility_sql` +- Plattform-Admin-Ausnahmen (`is_platform_admin`, `is_superadmin`) +- Rollencodes für Schreib-/Löschregeln (`club_admin`, `trainer`, …) + +--- + +## Designprinzipien + +### 1. Ein Mandant pro Request (`TenantContext`) + +| | | +|---|---| +| **Prinzip** | `Depends(get_tenant_context)` liefert `profile_id`, `global_role`, `effective_club_id`, Mitgliedschaften — einmal pro Request. | +| **Begründung** | Kein verteiltes „Rates“ aus Headers; konsistente Filter in allen Routern. | +| **Quelle** | `tenant_context.py`; `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` §2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle Endpoints migriert; Audit-Tabelle zeigt Restbestand mit nur `require_auth`. | + +### 2. `club_id` als Datenisolierungsgrenze + +| | | +|---|---| +| **Prinzip** | Vereinsgeteilte Inhalte sind nur für aktive Mitglieder des Objekt-`club_id` lesbar — nie Cross-Verein. | +| **Begründung** | Mandantenfähigkeit für Vereinsplattform; Compliance bei geteilten Trainingsinhalten. | +| **Quelle** | `library_content_visibility_sql()`; Tests `test_access_layer*.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `division`-Verschärfung noch nicht durchgängig; reserviert im Plan. | + +### 3. Einheitliche Visibility-Semantik + +| | | +|---|---| +| **Prinzip** | `private` \| `club` \| `official` mit gleicher Bedeutung für Übungen, Medien, Rahmen, Module, Graphen. | +| **Begründung** | Nutzer und Trainer verstehen ein Freigabemodell; UI-Feld „Freigabelevel“ durchgängig. | +| **Quelle** | `club_tenancy.py`; `FACHLICHE_NUTZERFUNKTIONEN.md` §4.7 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `community`-Stufe nur dokumentiert, nicht implementiert. | + +### 4. Zentraler SQL-Filter für Bibliothekslisten + +| | | +|---|---| +| **Prinzip** | Listen nutzen `library_content_visibility_sql(alias, profile_id, role, effective_club_id)` — nicht handgeschriebene WHERE-Kopien. | +| **Begründung** | Drift-Vermeidung; ein Fix gilt für alle Kataloge. | +| **Quelle** | `tenant_context.py`; Router `exercises.py`, `training_framework_programs.py`, … | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne Legacy-Queries können noch abweichen. | + +### 5. Governance-Transitionen explizit prüfen + +| | | +|---|---| +| **Prinzip** | Wechsel von `visibility`/`club_id` über `assert_library_content_governance_transition` + `assert_valid_governance_visibility`. | +| **Begründung** | „Privat → Verein teilen“ und „official herabstufen“ sind sicherheitsrelevante Aktionen. | +| **Quelle** | `club_tenancy.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht jedes Modul ruft Transition-Helper bei PATCH auf. | + +### 6. Löschregeln nach Visibility-Stufe + +| | | +|---|---| +| **Prinzip** | `assert_library_content_deletable`: privat → Ersteller/Vereinsadmin-Kontext; club → Vereinsadmin; official → Plattform-Admin. | +| **Begründung** | Schutz vor versehentlichem Löschen fremder oder offizieller Inhalte. | +| **Quelle** | `club_tenancy.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Medien-Lifecycle hat zusätzliche Stufen (Papierkorb) — Schnittmenge beachten. | + +### 7. Aktiver Verein: Header + Profil synchron + +| | | +|---|---| +| **Prinzip** | Frontend sendet `X-Active-Club-Id`; Backend validiert gegen Mitgliedschaft; Profil speichert `active_club_id`. | +| **Begründung** | Einheitlicher Mandanten-Kontext über API und UI. | +| **Quelle** | `frontend/src/api/client.js` (`mergeActiveClubHeader`); `profiles` Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Onboarding-Nutzer ohne Verein: eingeschränkter Nav-Modus. | + +### 8. Plattform-Admin als Audit-Pfad, nicht als Bypass + +| | | +|---|---| +| **Prinzip** | Plattform-Admins sehen fremde `club`-Inhalte nur mit expliziter Regel (Mitgliedschaft oder Audit-Ausnahme in SQL). | +| **Begründung** | Superuser-Zugriff ohne Mandanten-Leak in normalen Trainer-Flows. | +| **Quelle** | `library_content_visibility_sql` — `club_ok_plat`-Zweig | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Feinheiten zwischen `admin` und `superadmin` (z. B. `official`, Legal Hold) separat geregelt. | + +### 9. Endpoint-Audit als lebendes Inventar + +| | | +|---|---| +| **Prinzip** | Jeder sicherheitsrelevante Endpoint-Eintrag in `ACCESS_LAYER_ENDPOINT_AUDIT.md`; PR-Checkliste verlangt Update. | +| **Begründung** | Sichtbarkeit des Migrationsstands; kein stilles Ausweichen auf `require_auth` allein. | +| **Quelle** | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`; `check_access_layer_hints.py` | +| **Tragfähigkeit** | **hoch** (prozessual) | +| **Einschränkung** | CI-Strict-Modus optional, nicht überall aktiv. | + +--- + +## Nicht übernehmen + +1. **Endpoints nur mit `require_auth`** bei tenant-sensitiven Daten — führt zu IDOR und fehlenden Listenfiltern. +2. **Visibility-Logik pro Router duplizieren** — historische Drift zwischen Übungen und Planung. +3. **`division` vor stabiler Vereins-Isolation** — Reihenfolge im Plan: erst Stufe C, dann D. +4. **Community-Freigabe ohne additive Felder** — würde `club`-Isolation brechen. +5. **Client-seitige Mandantenfilter ohne Server-Enforcement** — UI-Hiding reicht nicht. + +--- + +## Verwandte Dokumentation + +- [ACCESS_LAYER_AND_GOVERNANCE_PLAN.md](../../../.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md) +- [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../../../.claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..3a05cbd --- /dev/null +++ b/docs/jinkendo-family/design-principles/AI_PROMPT_RUNTIME_DESIGN_PRINCIPLES.md @@ -0,0 +1,140 @@ +# AI Prompt Runtime – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „AI Prompt Runtime“ — Shinkan-KI-Schicht, keine Planungs-Gesamtarchitektur + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 5 von 15 +**Mitai-Vergleich:** [PROMPT_ENGINE_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/PROMPT_ENGINE_DESIGN_PRINCIPLES.md) (#1) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Laufzeit | `backend/ai_prompt_runtime.py`, `backend/prompt_resolver.py` | +| Domänen-Orchestrierung | `backend/exercise_ai.py`, `backend/planning_exercise_*.py` | +| OpenRouter | `backend/openrouter_chat.py` | +| Admin | `backend/routers/ai_prompts_admin.py` | +| Zielbild | `.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md` | +| Job-Kontext | `backend/ai_prompt_job.py`, `backend/ai_prompt_context.py` | + +--- + +## Modul + +**AI Prompt Runtime** + +Schmale Ausführungsschicht für admin-konfigurierbare Prompts in `ai_prompts`: Laden, Mustache-Rendering, Kontext-Arten, OpenRouter-Aufruf. **Kein** vollständiges Unified Prompt System wie Mitai (keine Workflows/Pipelines in Produktion). + +--- + +## Fachliche Verantwortung + +1. **Prompt-Laden aus DB** — `load_ai_prompt_row`, `load_and_render_ai_prompt` +2. **Platzhalter-Ersetzung** — Mustache `{{key}}` via `prompt_resolver.py` +3. **Kontext-Arten** — `AiPromptContextKind` trennt Übungs-KI vs. Planungs-KI +4. **Domänen-Builder** — `exercise_ai`, Planungs-Pipelines bauen Variablen-Maps +5. **Admin CRUD + Preview** — ohne LLM in Preview-Pfaden wo vorgesehen + +--- + +## Designprinzipien + +### 1. Eine Laufzeit-Fassade für DB-Prompts + +| | | +|---|---| +| **Prinzip** | Produktive Aufrufe laden Slugs über `ai_prompt_runtime` — nicht Roh-SQL auf `ai_prompts` in Routern. | +| **Begründung** | Einheitliches inactive-Handling, Modell-Feld, Render-Metadaten. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.1; `ai_prompt_runtime.py` | +| **Tragfähigkeit** | **hoch** (Zielrichtung) | +| **Einschränkung** | Planungs-KI hat noch verteilte Orchestratoren; kein einzelner `execute_prompt` wie Mitai. | + +### 2. Konfigurierbare Bibliothek in `ai_prompts` + +| | | +|---|---| +| **Prinzip** | Template-Texte in DB; Admins ändern ohne Deploy (`ai_prompts_admin`). | +| **Begründung** | Gleiches Familien-Muster wie Mitai Prompt-Bibliothek. | +| **Quelle** | Migration 069+; Admin-UI | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine Pipeline/Workflow-Typen; Slugs hardcoded in `context_kind_for_slug`. | + +### 3. Kontext-Namespaces statt globaler Platzhalter-Soup + +| | | +|---|---| +| **Prinzip** | `AiPromptContextKind` (z. B. `exercise_form_ai`, `planning_exercise_search`) begrenzt erlaubte Builder. | +| **Begründung** | Planungs-Kontext wächst ohne Kollision mit Übungs-Keys. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Noch keine zentrale Platzhalter-Registry wie Mitai — Mustache ad hoc pro Builder. | + +### 4. Trennung Template vs. Domänen-Kontext + +| | | +|---|---| +| **Prinzip** | UI/Router liefern Pydantic-DTOs → Builder erzeugen `variables`-Map → `render_mustache_template`. | +| **Begründung** | Prompt-Autoren ändern Text, nicht Python in Routern. | +| **Quelle** | `prompt_resolver.py`; `ExerciseFormAiPromptContext` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Planungs-Kontexte noch nicht vollständig über DTOs. | + +### 5. Transport (OpenRouter) getrennt von Semantik + +| | | +|---|---| +| **Prinzip** | `openrouter_chat.py` für HTTP; Validierung/JSON-Parsing in Domänen-Schicht. | +| **Begründung** | Modellwechsel ohne Router-Anpassung. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §2.2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Modell teils global Env, teils Spalte `openrouter_model` — Konvergenz offen. | + +### 6. Reset-to-default für System-Prompts + +| | | +|---|---| +| **Prinzip** | `default_template` + Admin-Reset — kein vollständiges Versionsmodell. | +| **Begründung** | Familien-Muster aus Mitai; Schutz vor Fehlkonfiguration. | +| **Quelle** | Migration 069 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Keine Historie benutzerdefinierter Änderungen. | + +### 7. Admin-only Schreiben, authentifiziertes Ausführen + +| | | +|---|---| +| **Prinzip** | Prompt-CRUD nur Admin; Ausführung mit Capability + Feature-Kontingent. | +| **Begründung** | Systemkonfiguration vs. Nutzung; Kostenkontrolle. | +| **Quelle** | `ai_prompts_admin.py`; `exercises.ai.suggest` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Enforcement teils noch Probe-Phase. | + +### 8. Skill-Retrieval orthogonal zu Prompts + +| | | +|---|---| +| **Prinzip** | `ai_skill_retrieval_profiles` steuert Katalog für `{{skills_catalog}}` — unabhängig vom Prompt-Text. | +| **Begründung** | Anweisung vs. Kontextfenster trennbar konfigurierbar. | +| **Quelle** | `AI_PROMPT_TARGET_ARCHITECTURE.md` §3.3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +--- + +## Nicht übernehmen (Shinkan-Ist / Mitai-Vermeidung) + +1. **Direkte OpenRouter-Calls in Routern** ohne Laufzeit-Schicht — historische Schuld, abbauen. +2. **Hardcodierte Prompt-Strings in Produktion** — nur Fallback/Dev. +3. **Mitai-Workflow-Graph vorreifen** — Shinkan braucht erst Planungs-Kontext-Reife. +4. **Globale Platzhalter-Map ohne Namespace** — Mitai-Lektion `PLACEHOLDER_MAP`-Duplikat. +5. **Fehlende JSON-Schema-Validierung** bei `output_format=json` — Mitai-TODO übernehmen vermeiden. + +--- + +## Verwandte Dokumentation + +- [AI_PROMPT_TARGET_ARCHITECTURE.md](../../../.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md) +- [AI_PROMPT_SYSTEM_SPEC.md](../../../.claude/docs/technical/AI_PROMPT_SYSTEM_SPEC.md) +- [PLANNING_PROGRESSION_GRAPH_KI.md](../../architecture/PLANNING_PROGRESSION_GRAPH_KI.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/AUTH_SESSION_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/AUTH_SESSION_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..ca3b3ad --- /dev/null +++ b/docs/jinkendo-family/design-principles/AUTH_SESSION_DESIGN_PRINCIPLES.md @@ -0,0 +1,114 @@ +# Auth & Session – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Auth & Session“ — gemeinsame Mitai-Basis in Shinkan + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 4 von 15 +**Mitai-Vergleich:** [AUTH_SESSION_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/AUTH_SESSION_DESIGN_PRINCIPLES.md) (#5) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Auth-Kern | `backend/auth.py` | +| Router | `backend/routers/auth.py`, `profiles.py` | +| Frontend | `frontend/src/context/AuthContext.jsx`, `frontend/src/api/client.js` | +| Account-Lifecycle | `backend/account_lifecycle.py` | + +--- + +## Modul + +**Auth & Session** + +Token-basierte Server-Sessions (`sessions`-Tabelle), bcrypt-Passwörter, FastAPI-Dependencies `require_auth` / `require_admin`. Geteilter Code mit Mitai (App-Familie). + +--- + +## Fachliche Verantwortung + +1. **Login/Logout/Session-Lebensdauer** +2. **Passwort-Hashing** (bcrypt, Legacy-SHA256-Upgrade) +3. **Auth-Dependencies** für Router +4. **Account-States** (Verifizierung, Onboarding-Gates) + +--- + +## Designprinzipien + +### 1. Server-Side Sessions mit Token + +| | | +|---|---| +| **Prinzip** | `X-Auth-Token` Header → Lookup in `sessions` mit Ablaufzeit. | +| **Begründung** | Widerrufbar; kein JWT-Drift zwischen Apps. | +| **Quelle** | `auth.py` `get_session`, `require_auth` | +| **Tragfähigkeit** | **hoch** (Familien-Standard) | +| **Einschränkung** | Kein Refresh-Token-Rotation-Modell. | + +### 2. `require_auth` als separater Depends-Parameter + +| | | +|---|---| +| **Prinzip** | `session: dict = Depends(require_auth)` — nie in Header-Default eingebettet. | +| **Begründung** | Bekannter FastAPI-Footgun führt zu ungeschützten Endpoints. | +| **Quelle** | `CLAUDE.md` Kritische Regeln | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Code-Review/Lint erzwingt das nicht automatisch. | + +### 3. Profile-ID immer aus Session + +| | | +|---|---| +| **Prinzip** | `profile_id` aus `session['profile_id']`, nie aus Client-Header als Autorität. | +| **Begründung** | IDOR-Vermeidung. | +| **Quelle** | Architektur-Regeln; Shinkan ergänzt `TenantContext.profile_id` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Mitai-Dokument nennt Profile-Header-Schwäche — in Shinkan prüfen ob analog. | + +### 4. bcrypt für alle Passwort-Operationen + +| | | +|---|---| +| **Prinzip** | `hash_pin` / `verify_pin` mit bcrypt; SHA256 nur Legacy-Verify + Upgrade. | +| **Begründung** | Familien-konsistente Kryptografie. | +| **Quelle** | `auth.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Portal-Rollen vs. Vereinsrollen trennen + +| | | +|---|---| +| **Prinzip** | `profiles.role` (admin/superadmin/user) ≠ `club_member_roles` im Verein. | +| **Begründung** | Shinkan-Mandantenmodell; Plattform-Admin ≠ Vereins-Trainer. | +| **Quelle** | `club_tenancy.py`; `TenantContext.global_role` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI muss beide Ebenen korrekt anzeigen. | + +### 6. Account-Lifecycle als Capability-Voraussetzung + +| | | +|---|---| +| **Prinzip** | `min_account_state` auf Capabilities (z. B. verifiziertes Mitglied). | +| **Begründung** | Gates vor sensiblen Aktionen ohne Sonderchecks in Routern. | +| **Quelle** | `account_lifecycle.py`; `capabilities.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alle Flows nutzen Lifecycle einheitlich. | + +--- + +## Nicht übernehmen + +1. **Auth-Parameter in Header-Defaults vermischen** — dokumentierter Anti-Pattern. +2. **Client-gesteuerte `profile_id`** für Autorisierung. +3. **Shinkan-spezifische Mandantenlogik in `auth.py`** — gehört in `tenant_context.py`. + +--- + +## Verwandte Dokumentation + +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- Mitai: [AUTH_SESSION_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/AUTH_SESSION_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..0ea9728 --- /dev/null +++ b/docs/jinkendo-family/design-principles/CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md @@ -0,0 +1,125 @@ +# Capability & Club Features – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Capabilities & Vereins-Feature-Kontingente“ — nicht Billing/Stripe + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 2 von 15 +**Mitai-Vergleich:** [FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/FEATURE_ENTITLEMENT_DESIGN_PRINCIPLES.md) (#3) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Capabilities | `backend/capabilities.py` | +| Vereins-Features | `backend/club_features.py` | +| Entitlements-API | `backend/entitlements.py`, `backend/routers/me_entitlements.py` | +| Quota-Bypass | `backend/club_quota_bypass.py` | +| Spez | `CAPABILITY_CATALOG.v1.md`, `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | + +--- + +## Modul + +**Capability & Club Feature Entitlements** + +Zwei Schichten: **Capabilities** (darf Nutzer X im Verein Y?) und **Club Features** (Kontingente/Limits pro Verein, Subjekt `club_id`). Zusammenführung in `GET /api/me/entitlements`. + +--- + +## Fachliche Verantwortung + +1. **Capability-Checks** — `check_capability`, `probe_capability`, `require_capability` mit Env `CAPABILITY_ENFORCE`. +2. **Vereins-Kontingente** — `probe_club_feature_access`, `consume_club_feature_with_usage` mit Env `CLUB_FEATURE_ENFORCE`. +3. **Entitlements-Snapshot** — Frontend erhält `capabilities` + `features` + Plan für UI-Gating ohne Tier-Logik in Widgets. +4. **Account-Lifecycle** — `min_account_state` blockiert Capabilities vor Verifizierung. + +### Unterschied zu Mitai + +| Aspekt | Mitai | Shinkan | +|--------|-------|---------| +| Limit-Subjekt | Profil / Subscription | **Verein** (`club_id`) | +| Rollen | Tier + Features | Vereinsrollen + Portal-Rolle | +| Legacy | `check_feature_access` (001) | Explizit **nicht** für Shinkan-Limits nutzen | + +--- + +## Designprinzipien + +### 1. Eine Entitlements-API für das Frontend + +| | | +|---|---| +| **Prinzip** | `GET /api/me/entitlements?club_id=` liefert Capabilities-Map + Feature-Kontingente + Plan. | +| **Begründung** | Keine Tier-Logik in React-Komponenten; ein Roundtrip pro Mandantenwechsel. | +| **Quelle** | `entitlements.py`, `me_entitlements.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht alle UI-Stellen nutzen Entitlements konsequent. | + +### 2. 4-Phasen-Rollout (Probe → Enforce) + +| | | +|---|---| +| **Prinzip** | Phase 2: JSON-Log ohne Block; Phase 3+: `CAPABILITY_ENFORCE=1` / `CLUB_FEATURE_ENFORCE=1` → HTTP 403. | +| **Begründung** | Sicheres Einführen ohne Produktions-Crash; Audit vor Hard-Block. | +| **Quelle** | `capabilities.py`, `club_features.py`; Mitai-Vorbild in Spec | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Env-Flags müssen pro Umgebung bewusst gesetzt werden. | + +### 3. Capabilities verknüpft mit Features + +| | | +|---|---| +| **Prinzip** | Capability kann `linked_feature_id` haben — Kontingent-Check vor Ausführung. | +| **Begründung** | Recht und Limit bleiben getrennt modelliert aber gemeinsam enforcebar. | +| **Quelle** | `capabilities`-Tabelle; `check_capability` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Nicht jede Capability hat linked Feature. | + +### 4. Enforcement an der API, nicht in der UI + +| | | +|---|---| +| **Prinzip** | Router rufen `require_capability` / `probe_club_feature_access` — UI blendet nur vor. | +| **Begründung** | API ist Source of Truth; UI-Gating allein ist umgehbar. | +| **Quelle** | `exercise_ai.py`, Planungs-KI-Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Teilweise noch Probe-only in Prod. | + +### 5. Bestands-Features als Live-Zählung + +| | | +|---|---| +| **Prinzip** | Inventar-Features (`exercises`, `training_groups`, …) zählen live in DB, nicht nur `club_feature_usage`. | +| **Begründung** | Keine Drift zwischen tatsächlichem Bestand und Usage-Tabelle. | +| **Quelle** | `_INVENTORY_FEATURES` in `club_features.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Performance bei großen Vereinen — ggf. Cache nötig. | + +### 6. Quota-Bypass für Plattform-Rollen + +| | | +|---|---| +| **Prinzip** | Konfigurierbare Bypass-Capabilities (`domain=quota_bypass`) für Support/Admin ohne harte Limits. | +| **Begründung** | Betrieb und Demos ohne Plan-Upgrade. | +| **Quelle** | `club_quota_bypass.py`; `entitlements.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Missbrauchsrisiko — nur dokumentierte Grants. | + +--- + +## Nicht übernehmen + +1. **Mitai `auth.check_feature_access` für Shinkan-Vereinslimits** — profil-zentriert, falscher Subjekt-Scope. +2. **Tier-Logik in Frontend-Widgets** — gehört in Entitlements-Response. +3. **Enforcement ohne Probe-Phase** — bricht bestehende Vereine ohne Vorwarnung. +4. **Capabilities ohne DB-Sync aus Registry** — siehe Rights-Registry-Dokument. + +--- + +## Verwandte Dokumentation + +- [CAPABILITY_CATALOG.v1.md](../../../.claude/docs/technical/CAPABILITY_CATALOG.v1.md) +- [CLUB_MEMBERSHIP_AND_FEATURES.v1.md](../../../.claude/docs/technical/CLUB_MEMBERSHIP_AND_FEATURES.v1.md) +- [RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md](./RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/CONTENT_REPORTS_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/CONTENT_REPORTS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..0441592 --- /dev/null +++ b/docs/jinkendo-family/design-principles/CONTENT_REPORTS_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Content Reports (P-13) – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Content Reports“ — Meldeverfahren, Posteingang, Legal Hold + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 10 von 15 +**Mitai-Vergleich:** — (Compliance-spezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/content_reports.py` | +| Legal Hold | `backend/media_legal_hold.py` | +| Inbox-Integration | `GET /api/me/inbox/content-reports` | +| Frontend | `InboxPage.jsx` | +| Migration | 052, 053 | + +--- + +## Modul + +**Content Reports & Compliance (P-13)** + +Meldeverfahren für problematische Inhalte (Medien, Übungen), Admin-Posteingang mit Statusworkflow, Priorisierung sensibler Gründe, Anbindung Legal Hold und Medien-Audit. + +--- + +## Designprinzipien + +### 1. Eine Tabelle, ein Workflow — keine separate Admin-Queue + +| | | +|---|---| +| **Prinzip** | `content_reports` + bestehende Inbox-UI für Änderungsanfragen und Meldungen. | +| **Begründung** | Admin-Arbeit an einem Ort; weniger Navigations-Fragmentierung. | +| **Quelle** | `content_reports.py` Docstring | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Zwei Vorgangstypen in einer UI — klare Typ-Kennzeichnung nötig. | + +### 2. Melden optional ohne Auth (eingeschränkt) + +| | | +|---|---| +| **Prinzip** | Anonym/offline Meldung für `official`-Medien erlaubt; sonst Auth empfohlen. | +| **Begründung** | DSA-Anforderungen; öffentliche Plattform-Inhalte meldbar. | +| **Quelle** | Router-Berechtigungen | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Missbrauchsschutz (Rate Limit) prüfen. | + +### 3. Priorität bei sensiblen Gründen + +| | | +|---|---| +| **Prinzip** | `minors`, `illegal_content`, `youth_protection` → HIGH_PRIORITY automatisch. | +| **Begründung** | SLA und Admin-Aufmerksamkeit fachlich korrekt. | +| **Quelle** | `HIGH_PRIORITY_REASONS` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Rollengetrennte Sicht (Plattform vs. Verein) + +| | | +|---|---| +| **Prinzip** | Plattform-Admin: alle; Club-Admin: nur Vereinsmedien-Meldungen. | +| **Begründung** | Mandanten-Grenze auch im Compliance-Kontext. | +| **Quelle** | Router-Listenfilter | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Legal Hold nur Superadmin aus Meldung + +| | | +|---|---| +| **Prinzip** | Mapping `report_reason` → `legal_hold reason_code`; `set_legal_hold` superadmin-geschützt. | +| **Begründung** | Hochrisiko-Aktion; Anschluss P-11. | +| **Quelle** | `_REASON_TO_HOLD_CODE`; `media_legal_hold.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Audit-Spur bei Medien-Meldungen + +| | | +|---|---| +| **Prinzip** | `media_asset_audit_log` Event `content_report_filed` bei media_asset-Bezug. | +| **Begründung** | Lifecycle-Entscheidungen nachvollziehbar. | +| **Quelle** | Migration 053; `write_audit_log_entry` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 7. E-Mail best-effort, kein Hard-Fail + +| | | +|---|---| +| **Prinzip** | Bestätigung an Melder + Admin-Benachrichtigung; SMTP-Fehler blockieren Speichern nicht. | +| **Begründung** | Meldung geht nicht verloren wenn Mail down. | +| **Quelle** | Router-Implementierung | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Admins müssen Posteingang auch ohne Mail prüfen. | + +--- + +## Nicht übernehmen + +1. **Separate Compliance-Queue-App** — Inbox-Reuse ist bewusst. +2. **Legal Hold durch Club-Admin** — Superadmin-only. +3. **Meldungen ohne Statusworkflow/Archiv** — Wiedereröffnen muss möglich sein. +4. **Fehlende Verknüpfung Medien-Lifecycle** — Hold muss Purge blockieren. + +--- + +## Verwandte Dokumentation + +- [MEDIA_ASSETS_DESIGN_PRINCIPLES.md](./MEDIA_ASSETS_DESIGN_PRINCIPLES.md) +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/DASHBOARD_KPI_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/DASHBOARD_KPI_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..cbe47ff --- /dev/null +++ b/docs/jinkendo-family/design-principles/DASHBOARD_KPI_DESIGN_PRINCIPLES.md @@ -0,0 +1,105 @@ +# Dashboard KPIs – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Dashboard KPI Aggregation“ — vereinfacht vs. Mitai Widgets + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 14 von 15 +**Mitai-Vergleich:** [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) (#7, vereinfacht) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/dashboard.py` — `GET /api/dashboard/kpis` | +| Frontend | Dashboard/Übersicht-Page | +| Refaktor-Kontext | `docs/architecture/SCHULDEN_UND_REMEDIATION.md` A3, B1 | + +--- + +## Modul + +**Dashboard KPI Aggregation** + +Ein Backend-Roundtrip liefert Übungs-KPIs, YTD-Einheiten, Trainings-Home (nächste Termine, Vermerke, offene Rückschau) — Ersatz für mehrere parallele Client-Listen-Calls. + +--- + +## Designprinzipien + +### 1. Aggregierter Endpoint statt Chatty Client + +| | | +|---|---| +| **Prinzip** | `GET /dashboard/kpis` ruft intern `list_exercises_like_get` + `list_training_units` mit gleichen Filtern wie zuvor im UI. | +| **Begründung** | Weniger Latenz; eine TenantContext-Auflösung; Refaktor Phase 1 Dashboard. | +| **Quelle** | `dashboard.py`; SCHULDEN A3/B1 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Noch keine konfigurierbaren Widgets wie Mitai. | + +### 2. Gleiche Filtersemantik wie Einzel-Endpoints + +| | | +|---|---| +| **Prinzip** | KPI-Zählungen nutzen dieselben Helfer wie `/exercises` und `/training-units` — keine zweite Query-Logik. | +| **Begründung** | Zahlen auf Dashboard = Zahlen in Fachmodulen. | +| **Quelle** | Import aus `exercises`, `training_planning` Routern | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Interne Funktionsaufrufe statt HTTP — Kopplung an Router-Helfer. | + +### 3. TenantContext für Mandanten-KPIs + +| | | +|---|---| +| **Prinzip** | `Depends(get_tenant_context)` — assigned_to_me, created_by_me respektieren Verein/Rolle. | +| **Begründung** | Keine globalen KPIs für Trainer fremder Vereine. | +| **Quelle** | `get_dashboard_kpis` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Festes Dashboard-Layout (kein Widget-Katalog) + +| | | +|---|---| +| **Prinzip** | Shinkan-Übersicht zeigt definierte Kacheln — nicht nutzerkonfigurierbares Layout-JSON. | +| **Begründung** | MVP-Fokus Trainer-Verein; weniger Komplexität als Mitai. | +| **Quelle** | Produktentscheidung vs. Mitai #7 | +| **Tragfähigkeit** | **mittel** (bewusste Vereinfachung) | +| **Einschränkung** | Erweiterung braucht Backend+Frontend-Change, nicht Admin-Config. | + +### 5. Profil nicht redundant laden + +| | | +|---|---| +| **Prinzip** | Dashboard soll Auth-Profil nutzen — kein zweites `/profiles/me` nach Login+Reload (E2E Test 8). | +| **Begründung** | Architekturschuld A3 explizit adressiert. | +| **Quelle** | `tests/dev-smoke-test.spec.js`; Roadmap | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Umsetzung muss mitziehen. | + +### 6. Slice-Logik für Trainings-Home im Backend + +| | | +|---|---| +| **Prinzip** | `_slice_training_home_notes` filtert Einheiten mit Vermerken — max. N Stück serverseitig. | +| **Begründung** | Kleiner Payload; klare Semantik. | +| **Quelle** | `dashboard.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Grenzwert hardcoded — ggf. Query-Param später. | + +--- + +## Nicht übernehmen + +1. **Drei parallele fast gleiche `listTrainingUnits`-Calls** im Client — behoben durch KPI-Endpoint. +2. **Mitai Widget-Dual-Registry vorreifen** ohne Produktbedarf — Over-Engineering für Shinkan MVP. +3. **KPI-Berechnung im Frontend** aus Volllisten — skaliert nicht. +4. **Dashboard ohne Tenant-Filter** — Mandanten-Leak. + +--- + +## Verwandte Dokumentation + +- [docs/architecture/SCHULDEN_UND_REMEDIATION.md](../../architecture/SCHULDEN_UND_REMEDIATION.md) +- Mitai: [DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DASHBOARD_WIDGETS_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md b/docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md new file mode 100644 index 0000000..b9f65fd --- /dev/null +++ b/docs/jinkendo-family/design-principles/DESIGN_PRINCIPLES_INDEX.md @@ -0,0 +1,143 @@ +# 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:** [mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/](file:///c:/Dev/mitai-jinkendo/.claude/docs/jinkendo-foundation/design-principles/README.md) +**Abgleich Mitai ↔ Shinkan:** [DESIGN_PRINCIPLES_ALIGNMENT.md](../DESIGN_PRINCIPLES_ALIGNMENT.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 | Verweis auf [FAMILY_ENTITLEMENT_MODEL.md](../FAMILY_ENTITLEMENT_MODEL.md) | +| 2026-07-04 | Verweis auf [DESIGN_PRINCIPLES_ALIGNMENT.md](../DESIGN_PRINCIPLES_ALIGNMENT.md) | +| 2026-07-04 | Shinkan-Serie nach `docs/jinkendo-family/design-principles/` verschoben (Familien-Foundation) | +| 2026-07-04 | Index angelegt; Serie 1–15 aus Shinkan-Ist-Stand extrahiert | diff --git a/docs/jinkendo-family/design-principles/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..96f2e7d --- /dev/null +++ b/docs/jinkendo-family/design-principles/EXERCISE_CATALOG_DESIGN_PRINCIPLES.md @@ -0,0 +1,128 @@ +# Exercise Catalog – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Exercise Catalog“ — Kernobjekt Übung, Varianten, Graphen, Kombination + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 7 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/exercises.py`, `exercise_progression_graphs.py` | +| Rich-Text | `backend/exercise_rich_text.py` | +| KI | `backend/exercise_ai.py` | +| Frontend | `frontend/src/pages/Exercises*.jsx`, Tab-Formular | +| Specs | `EXERCISES_ARCHITECTURE.md`, `EXERCISES_API_SPEC.md`, Kombinations-Spec | + +--- + +## Modul + +**Exercise Catalog** + +Shinkans Kernobjekt: Übungen mit mehrdimensionaler Einordnung (Skills, Fokus, Stile), Varianten, Progressionsgraphen, Kombinationsübungen (`method_archetype`, Stationen), Governance und Medien-Anbindung. + +--- + +## Designprinzipien + +### 1. Übung als zentrales Aggregate Root + +| | | +|---|---| +| **Prinzip** | Varianten, Medien, Skills, Graph-Knoten hängen an `exercises.id` — Owner/Governance auf Eltern-Übung. | +| **Begründung** | Eine Freigabe- und Lösch-Semantik; Varianten ohne eigenen Owner. | +| **Quelle** | `FACHLICHE_NUTZERFUNKTIONEN.md` §4.1, §4.7 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Monolith-Router/Seiten — Refaktor-Roadmap. | + +### 2. Tab-Formular statt Scroll-Monolith + +| | | +|---|---| +| **Prinzip** | Register: Stammdaten · Anleitung · Einordnung · Kombination · Varianten · Medien — Varianten/Medien erst nach erstem Save. | +| **Begründung** | UX für komplexe Objekte; klare Abhängigkeiten (IDs für Medien/Varianten). | +| **Quelle** | Nutzerfunktionen §4.1 | +| **Tragfähigkeit** | **hoch** (UI-Muster) | +| **Einschränkung** | Frontend-God-Page-Schuld dokumentiert. | + +### 3. Mehrdimensionale Filter-SSoT + +| | | +|---|---| +| **Prinzip** | Suche/Filter über Skills, Fokus, Stil, Zielgruppe, Status, Freigabelevel — Backend-Query + gespeicherte Präferenzen. | +| **Begründung** | Trainer finden Inhalte in großen Vereins-Katalogen. | +| **Quelle** | `SEARCH_FILTER_SPEC.md`; `exercises.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Performance schwere Listen — Baseline/Roadmap. | + +### 4. Varianten mit Voraussetzungskette + +| | | +|---|---| +| **Prinzip** | `exercise_variants` mit Reihenfolge, optional `prerequisite_variant_id`. | +| **Begründung** | Didaktische Abstufung innerhalb einer Übung. | +| **Quelle** | Migration 030; Planung nutzt Varianten-ID pro Eintrag | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Progressionsgraph als gerichtete Übungs-Beziehungen + +| | | +|---|---| +| **Prinzip** | Knoten = Übungen/Varianten; Kanten = „weiter“-Beziehungen; eigener Router + UI in Übungswelt. | +| **Begründung** | Didaktik und Planungs-KI nutzen denselben Graph. | +| **Quelle** | `exercise_progression_graphs.py`; `PLANNING_PROGRESSION_GRAPH_KI.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Graph-Editor-Komplexität; KI-Artefakte separat. | + +### 6. Kombinationsübungen als Sonderform im gleichen Katalog + +| | | +|---|---| +| **Prinzip** | `exercise_type=combination` mit Stationen, `method_archetype`, optionalem `method_profile`. | +| **Begründung** | In Planung wie normale Übung; Coach zeigt Stations-Layer. | +| **Quelle** | Migration 056/057; Kombinations-Spec V2 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Archetyp-Stufen B/C noch ausbaubar. | + +### 7. Governance integriert (nicht separates CMS) + +| | | +|---|---| +| **Prinzip** | `visibility`, `status` (draft/review/…), Access-Layer-Lösch/Transition-Regeln. | +| **Begründung** | Trainer-Workflow ohne externes Freigabe-Tool. | +| **Quelle** | `club_tenancy.py`; Content Change Requests → Posteingang | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Formales Review-Workflow noch leichtgewichtig. | + +### 8. Rich-Text-Felder mit Inline-Medien + +| | | +|---|---| +| **Prinzip** | Einheitliche Platzhalter/Render für `summary`, `goal`, `execution`, … | +| **Begründung** | Medienreicher Inhalt ohne iframe-Split. | +| **Quelle** | `exercise_rich_text.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +--- + +## Nicht übernehmen + +1. **Varianten mit eigenem Owner/Freigabe** — widerspricht Domänenmodell. +2. **Übungsliste ohne Tenant-Filter** — Access-Layer-Pflicht. +3. **KI-generierte Übungen ohne Governance-Felder** — immer draft/private Default. +4. **Progressionsgraph-Logik im Frontend allein** — Server validiert Kanten. + +--- + +## Verwandte Dokumentation + +- [EXERCISES_ARCHITECTURE.md](../../../.claude/docs/technical/EXERCISES_ARCHITECTURE.md) +- [MEDIA_ASSETS_DESIGN_PRINCIPLES.md](./MEDIA_ASSETS_DESIGN_PRINCIPLES.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/MATURITY_MODELS_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/MATURITY_MODELS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..1ea4c1b --- /dev/null +++ b/docs/jinkendo-family/design-principles/MATURITY_MODELS_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Maturity Models – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Maturity Models / Fähigkeitsmatrix“ — kontextsensitive Auflösung + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 15 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/maturity_models.py`, `matrix_editor.py`, `matrix_stack_bundle.py` | +| Admin-UI | `/admin/maturity-models` | +| Import | Wiki-Import Typ Modelle; Matrix-Stack Export/Import | +| Spec | `.claude/docs/technical/SKILLS_MATRIX_SPEC.md` | + +--- + +## Modul + +**Maturity Models & Matrix Stack** + +Matrixbasierte Reifegradmodelle mit Stufen und Zelltexten; kontextsensitive Auflösung über Bindings (Fokusbereich, Stilrichtung, Zielgruppe); Admin-Export/Import einzelner Modelle und Komplett-Stack. + +--- + +## Designprinzipien + +### 1. Kontext-Bindings M:N (leer = überall) + +| | | +|---|---| +| **Prinzip** | Modell verknüpft mit Fokus/Stil/Zielgruppe; leere Bindings = global gültig. | +| **Begründung** | Ein Stack deckt mehrere Trainingskontexte ab. | +| **Quelle** | `maturity_models.py` `_attach_context` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Auflösungs-Priorität bei mehreren Treffern dokumentieren. | + +### 2. Resolve-API für Laufzeit-Nutzung + +| | | +|---|---| +| **Prinzip** | Authentifizierte Nutzer listen/auflösen; Admin-only für Roh-ID-GET in Admin-UI. | +| **Begründung** | Trainer sehen passende Matrix; Rohdaten-Edit geschützt. | +| **Quelle** | Router-Docstring; Rollen-Checks | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 3. Matrix-Editor als separates Admin-Tool + +| | | +|---|---| +| **Prinzip** | `matrix_editor` Router für Zellbearbeitung — nicht im Trainer-Flow. | +| **Begründung** | Komplexe UI; Plattform-Redaktionsaufgabe. | +| **Quelle** | Admin-Nav „Fähigkeitsmatrix“ | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Frontend-Komplexität — eigene Schuld-Kategorie. | + +### 4. Stack-Bundle Export/Import + +| | | +|---|---| +| **Prinzip** | `matrix_stack_bundle` — Komplett-Stack zwischen Umgebungen (Dev→Prod, Backup). | +| **Begründung** | Analog Prompt Import/Export — Konfiguration versionierbar außerhalb DB. | +| **Quelle** | Admin-Werkzeuge; Wiki-Import ergänzt | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Kein Diff/Merge wie Mitai Prompt-Import. | + +### 5. Plattform-Admin-Schreibschutz + +| | | +|---|---| +| **Prinzip** | Schreiben nur `admin`/`superadmin`; Lesen breiter für authentifizierte Nutzer (Resolve). | +| **Begründung** | Offizielle Kompetenzrahmen zentral gepflegt. | +| **Quelle** | `_require_admin` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Integration Wiki-Import für Modelle + +| | | +|---|---| +| **Prinzip** | SMW-Kategorie Modelle → Import-Pfad neben Übungen/Skills. | +| **Begründung** | Bestehende Wissensbasis karatetrainer.net nutzen. | +| **Quelle** | `import_wiki.py` `CATEGORY_MODELS` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Gap-Analyse SMW — nicht alle Wiki-Felder gemappt. | + +### 7. Orthogonal zu Skill Scoring + +| | | +|---|---| +| **Prinzip** | Matrix = beschreibende Stufen; Skill Scoring = gewichtete Übungs-Aggregation — getrennte Module. | +| **Begründung** | Keine Vermischung von Kompetenz-Raster und Trainings-KPI. | +| **Quelle** | Domänen-Trennung in Specs | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI kann beides nebenan zeigen — klare Labels nötig. | + +--- + +## Nicht übernehmen + +1. **Matrix-Zellen in Übungs-Score-Formel mischen** — ohne fachliche Spec. +2. **Trainer-Edit globaler offizieller Matrizen** — Admin-only. +3. **Import ohne Stack-Integrität** — Bundle-Validierung beachten. +4. **Resolve ohne Kontext-Parameter** wenn Mehrdeutigkeit — falsche Matrix. + +--- + +## Verwandte Dokumentation + +- [SKILLS_MATRIX_SPEC.md](../../../.claude/docs/technical/SKILLS_MATRIX_SPEC.md) +- [WIKI_IMPORT_DESIGN_PRINCIPLES.md](./WIKI_IMPORT_DESIGN_PRINCIPLES.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/MEDIA_ASSETS_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/MEDIA_ASSETS_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..7e0f185 --- /dev/null +++ b/docs/jinkendo-family/design-principles/MEDIA_ASSETS_DESIGN_PRINCIPLES.md @@ -0,0 +1,119 @@ +# Media Assets & Archiv – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Media Assets & Archiv“ — physische Medien, Lifecycle, Inline + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 6 von 15 +**Mitai-Vergleich:** — (Mitai hat kein vergleichbares Medien-Archiv) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| API | `backend/routers/media_assets.py`, `platform_media_storage.py` | +| Speicher | `backend/media_storage.py`, `MEDIA_ROOT` | +| Rechte/Audit | `backend/media_rights.py`, `media_legal_hold.py` | +| Inline Rich-Text | `backend/exercise_rich_text.py` | +| Retention-Job | `backend/scripts/media_retention_job.py` | +| Spec | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | + +--- + +## Modul + +**Media Assets & Archiv** + +Zentrale Verwaltung physischer Dateien (`media_assets`), Verknüpfung zu Übungen (`exercise_media`), mehrstufiger Lifecycle (Papierkorb, Legal Hold), Inline-Einbettung in Rich-Text. + +--- + +## Designprinzipien + +### 1. Physisches Asset einmal, mehrfach verknüpft + +| | | +|---|---| +| **Prinzip** | Datei in `media_assets`; Übungen referenzieren via `exercise_media.media_asset_id`. | +| **Begründung** | Keine Dubletten auf Platte; Wiederverwendung im Archiv. | +| **Quelle** | `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §1 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Pfade ohne Asset-ID können noch existieren. | + +### 2. Gleiche Visibility-Semantik wie Übungen + +| | | +|---|---| +| **Prinzip** | `private`/`club`/`official` + `club_id` — Access Layer für Download und Liste. | +| **Begründung** | Ein Freigabemodell für alle Bibliotheksartefakte. | +| **Quelle** | Spec §4; `media_rights.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Promotion Übung→official muss Assets mithheben — UI-Dialog Pflicht. | + +### 3. Lifecycle getrennt von Übungs-Verknüpfung + +| | | +|---|---| +| **Prinzip** | Verknüpfung in Übung lösen ≠ Asset physisch löschen; Papierkorb-Stufen separat. | +| **Begründung** | Trainer dürfen Link entfernen ohne Archiv-Löschrecht. | +| **Quelle** | Spec §5 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Retention-Job muss in Betrieb überwacht werden. | + +### 4. Legal Hold blockiert automatisierten Lifecycle + +| | | +|---|---| +| **Prinzip** | `legal_hold` schützt Asset vor Purge; Anbindung an Content Reports (Superadmin). | +| **Begründung** | Compliance bei Meldungen (P-11/P-13). | +| **Quelle** | `media_legal_hold.py`; `content_reports.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 5. Inline-Medien: kanonisches Markup + Validierung + +| | | +|---|---| +| **Prinzip** | `{{exerciseMedia:id}}` → ``; IDs müssen zur Übung gehören. | +| **Begründung** | Ein Render-Pfad; keine broken References nach Medien-Löschung. | +| **Quelle** | `exercise_rich_text.py`; Spec §11 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Beim Erst-Anlegen der Übung keine Inline-Refs (Chicken-Egg). | + +### 6. Speicher-Abstraktion (local + konfigurierbarer Root) + +| | | +|---|---| +| **Prinzip** | `get_effective_media_root()` + `library/…`-Pfadkonvention; kein hardcodierter `/app/media` in Routern. | +| **Begründung** | NAS/externer Speicher vorbereitet. | +| **Quelle** | `media_storage.py`; `platform_media_storage` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | S3-Backend noch nicht vollständig. | + +### 7. Audit-Log für sensitive Aktionen + +| | | +|---|---| +| **Prinzip** | `media_asset_audit_log` bei Meldungen, Hold, kritischen Lifecycle-Events. | +| **Begründung** | Nachvollziehbarkeit für Admins und Compliance. | +| **Quelle** | `media_rights.write_audit_log_entry` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht jede Admin-Aktion geloggt. | + +--- + +## Nicht übernehmen + +1. **Download nur mit Übungs-ID ohne Asset-Governance** — Seitenkanal-Risiko. +2. **Copyright leer bei `official`** — Spec verbietet das fachlich. +3. **Physisches Löschen bei Referenzanzahl > 0** — Spec §5.3. +4. **Embed-URLs durch Lifecycle-Purge** — Embeds haben anderen Lebenszyklus. + +--- + +## Verwandte Dokumentation + +- [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../../../.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) +- [ACCESS_LAYER_DESIGN_PRINCIPLES.md](./ACCESS_LAYER_DESIGN_PRINCIPLES.md) +- [CONTENT_REPORTS_DESIGN_PRINCIPLES.md](./CONTENT_REPORTS_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..699ff08 --- /dev/null +++ b/docs/jinkendo-family/design-principles/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md @@ -0,0 +1,117 @@ +# Migration & Deploy – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Migration & Deploy“ — Schema-Evolution, Container-Start + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 13 von 15 +**Mitai-Vergleich:** [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) (#9) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| DB-Init | `backend/db_init.py` | +| Migrationen | `backend/migrations/XXX_*.sql` | +| Version | `backend/version.py` (`DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) | +| Docker | `docker-compose.yml`, `docker-compose.dev-env.yml` | +| Deploy | develop → dev.shinkan · main → shinkan (Pi) | + +--- + +## Modul + +**Migration & Deploy** + +Nummerierte SQL-Migrationen beim Container-Start, Tracking in `schema_migrations`, Git-Branch → Umgebung, fail-fast ohne Auto-Rollback. + +--- + +## Designprinzipien + +### 1. Nummerierte Migrationen `XXX_*.sql` + +| | | +|---|---| +| **Prinzip** | Nur nummerierte Dateien in `backend/migrations/`; lexikographische Reihenfolge. | +| **Begründung** | Familien-Standard Mitai/Shinkan; vorhersagbare Anwendung. | +| **Quelle** | `db_init.py`; `CLAUDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Manuelle Nummern-Kollisionen vermeiden — Team-Disziplin. | + +### 2. Startup vor App — Migrationen blockieren Start bei Fehler + +| | | +|---|---| +| **Prinzip** | `db_init.py` wartet auf Postgres, wendet fehlende Migrationen an, dann FastAPI. | +| **Begründung** | Keine App mit veraltetem Schema. | +| **Quelle** | Container-Entrypoint / startup | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein automatisches Rollback — manuelle Recovery. | + +### 3. `schema_migrations` Tracking-Tabelle + +| | | +|---|---| +| **Prinzip** | Jede angewendete Datei wird persistiert; Wiederholung überspringt Bekannte. | +| **Begründung** | Idempotenz über Deploys hinweg. | +| **Quelle** | `ensure_migration_table`, `get_applied_migrations` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Geänderte Migration nach Apply — nicht neu ausführen (neue Nummer). | + +### 4. `DB_SCHEMA_VERSION` als dokumentierter Stand + +| | | +|---|---| +| **Prinzip** | `version.py` führt Schema-Version; MODULE_VERSIONS für Subsysteme. | +| **Begründung** | Support und Handover wissen erwarteten Stand. | +| **Quelle** | `backend/version.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Manuell pflegen bei Migration — Drift möglich. | + +### 5. develop/main → Dev/Prod mit festen Ports + +| | | +|---|---| +| **Prinzip** | Dev 3098/8098 · Prod 3003/8003 — nie ändern ohne explizite Freigabe. | +| **Begründung** | Deploy-Infrastruktur auf Pi/Synology stabil. | +| **Quelle** | `CLAUDE.md` Deployment | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Neue Spalten nur via Migration + +| | | +|---|---| +| **Prinzip** | Kein ad-hoc ALTER in Routern; Coding Rules. | +| **Begründung** | Reproduzierbare Umgebungen. | +| **Quelle** | `.claude/rules/CODING_RULES.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 7. IF NOT EXISTS / defensive SQL wo sinnvoll + +| | | +|---|---| +| **Prinzip** | Migrationen tolerant bei Wiederanlauf in Dev — aber Tracking verhindert Doppel-Apply. | +| **Begründung** | Recovery in Entwicklung erleichtern. | +| **Quelle** | Mitai-Migrations-Muster | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alles idempotent — komplexe Migrationen brauchen Transaktion. | + +--- + +## Nicht übernehmen + +1. **Manuelle psql-Schritte in Prod** ohne nummerierte Migration im Repo. +2. **Schema-Drift nur in schema.sql** ohne Migration — Init vs. Upgrade verwechseln. +3. **Auto-Rollback bei fehlgeschlagener Migration** — fail-fast, manuell fixen. +4. **Port-Änderung ohne Infra-Update** — bricht Fritz!Box/NAS-Routing. + +--- + +## Verwandte Dokumentation + +- [DATABASE_SCHEMA.md](../../../.claude/docs/technical/DATABASE_SCHEMA.md) +- Mitai: [MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/MIGRATION_DEPLOY_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/NAVIGATION_IA_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/NAVIGATION_IA_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..3a640c9 --- /dev/null +++ b/docs/jinkendo-family/design-principles/NAVIGATION_IA_DESIGN_PRINCIPLES.md @@ -0,0 +1,107 @@ +# Navigation / IA – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Navigation & Information Architecture“ + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 12 von 15 +**Mitai-Vergleich:** [NAVIGATION_IA_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/NAVIGATION_IA_DESIGN_PRINCIPLES.md) (#8) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Hauptnav | `frontend/src/config/appNav.js` | +| Admin-Nav | `frontend/src/components/AdminPageNav.jsx` | +| Shells | `RequireAdmin`, App-Layout in `App.jsx` | +| Return-Kontext | `.claude/docs/technical/NAV_RETURN_CONTEXT_SPEC.md` | +| Styles | `frontend/src/app.css` (`.admin-top-nav`, Bottom-Nav) | + +--- + +## Modul + +**Navigation / IA** + +Single Source of Truth für Hauptnavigation (Mobile Bottom + Desktop Sidebar), separater Admin-Hub, Onboarding-Nav ohne Vereinsfeatures, rollen- und kontextabhängige Einblendung (Posteingang, Admin). + +--- + +## Designprinzipien + +### 1. `appNav.js` als SSoT für Hauptnavigation + +| | | +|---|---| +| **Prinzip** | `getMainNavItems(isAdmin, opts)` liefert Route, Label, Icon — eine Liste für Mobile und Desktop. | +| **Begründung** | Gleiches Familien-Muster wie Mitai `appNav`; keine divergierenden Nav-Arrays. | +| **Quelle** | `appNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Tiefe Unterrouten (Übung bearbeiten) nicht in Top-Nav — Shell/Back. | + +### 2. Admin als separater Hub + +| | | +|---|---| +| **Prinzip** | `/admin/*` mit horizontaler `AdminPageNav` — Plattform-Werkzeuge gebündelt. | +| **Begründung** | Trainer-Nav bleibt schlank; Admin-IA skaliert unabhängig. | +| **Quelle** | `AdminPageNav.jsx` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Admin-Nav hardcoded Array — kein `adminNav.js` SSoT wie Mitai ideal. | + +### 3. Onboarding-Nav reduziert + +| | | +|---|---| +| **Prinzip** | `getOnboardingNavItems()` — nur Verein + Einstellungen ohne Übungen/Planung. | +| **Begründung** | Nutzer ohne Vereinsmitgliedschaft nicht in leere Bereiche führen. | +| **Quelle** | `appNav.js` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Kontextabhängige Items (Posteingang) + +| | | +|---|---| +| **Prinzip** | `showInbox` Flag steuert Posteingang-Eintrag — Berechtigung aus Entitlements/Rolle. | +| **Begründung** | Kein toter Nav-Link für Trainer ohne Inbox-Recht. | +| **Quelle** | `baseItems({ showInbox })` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Logik zur `showInbox`-Setzung in App.jsx pflegen. | + +### 5. Responsive: Bottom-Nav Mobile, Sidebar Desktop + +| | | +|---|---| +| **Prinzip** | Gleiche Items, unterschiedliche Präsentation; CSS-Variablen für Abstände. | +| **Begründung** | PWA-typisches Muster; 80px Bottom-Padding für Nav. | +| **Quelle** | `app.css`; Design-System in `CLAUDE.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Breakpoint-Konsistenz mit Mitai (1024px) prüfen beim Familien-Review. | + +### 6. Return-Kontext für tiefe Bearbeitung + +| | | +|---|---| +| **Prinzip** | Spez `NAV_RETURN_CONTEXT_SPEC` — zurück zur Herkunftsliste mit Filter-State. | +| **Begründung** | Übungs-Editor aus Suche/Planung ohne Navigations-Verlust. | +| **Quelle** | Return-Context Spec | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Nicht alle Flows implementiert. | + +--- + +## Nicht übernehmen + +1. **Zwei unterschiedliche Nav-Arrays** für Mobile vs. Desktop. +2. **Admin-Routen in Haupt-Bottom-Nav** mischen (außer ein Admin-Einstieg). +3. **Hardcodierte Nav in jeder Page** — zentral in `appNav.js`. +4. **Fehlender Onboarding-Gate** — volle Nav ohne Verein verwirrt. + +--- + +## Verwandte Dokumentation + +- [NAV_RETURN_CONTEXT_SPEC.md](../../../.claude/docs/technical/NAV_RETURN_CONTEXT_SPEC.md) +- Mitai: [NAVIGATION_IA_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/NAVIGATION_IA_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..4fdf12f --- /dev/null +++ b/docs/jinkendo-family/design-principles/RIGHTS_REGISTRY_DESIGN_PRINCIPLES.md @@ -0,0 +1,114 @@ +# Rights Registry – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Rights Registry“ — Capabilities & Features zur Laufzeit registrieren + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 3 von 15 +**Mitai-Vergleich:** [REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/REGISTRY_PLUGIN_DESIGN_PRINCIPLES.md) (#4) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Registry-Kern | `backend/rights_registry.py` | +| Modul-Registrierungen | `backend/rights_registrations/` (`exercises.py`, `planning.py`, `platform.py`, `club_creation.py`) | +| Startup-Sync | Import in `backend/main.py` | +| Tests | `backend/tests/test_rights_registry.py` | + +--- + +## Modul + +**Rights Registry (Registry-first für Capabilities & Features)** + +Module deklarieren bei Implementierung, welche Rechte und Kontingente sie anbieten. Beim App-Start werden Definitionen in die DB synchronisiert (`capabilities`, `features`, Default-Grants). + +--- + +## Fachliche Verantwortung + +1. **Runtime-Registrierung** — `register_capability()`, `register_feature()` vor DB-Sync. +2. **Modul-Ownership** — Jedes Feature/Capability trägt `module`-Feld für Admin-Filter „Rollen & Rechte“. +3. **Default Club Grants** — Rollen → Capability-Mapping bei Erst-Sync. +4. **Kein vollständiger Vorab-Katalog in SQL-Migration** — nur was Module wirklich liefern. + +--- + +## Designprinzipien + +### 1. Registry-first statt Migrations-Monolith + +| | | +|---|---| +| **Prinzip** | Neue Rechte erscheinen durch Code-Registrierung + Startup-Upsert — nicht durch manuelle 079-Katalog-Migration pro Feature. | +| **Begründung** | Modul und Recht entstehen zusammen; weniger vergessene Katalog-Einträge. | +| **Quelle** | `rights_registry.py`; Docstring; `rights_registrations/` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Erste Basismigration seedet noch initiale Zeilen. | + +### 2. Modul-Datei pro Domäne + +| | | +|---|---| +| **Prinzip** | `rights_registrations/exercises.py` registriert nur Übungs-Rechte; Planung/Platform analog. | +| **Begründung** | Ownership klar; Merge-Konflikte lokalisiert. | +| **Quelle** | `rights_registrations/__init__.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Import-Reihenfolge muss in `main.py` garantiert sein. | + +### 3. Frozen Dataclass-Definitionen + +| | | +|---|---| +| **Prinzip** | `CapabilityRegistration` / `FeatureRegistration` als immutable `@dataclass(frozen=True)`. | +| **Begründung** | Keine nachträgliche Mutation nach Registrierung. | +| **Quelle** | `rights_registry.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 4. Validierung an der Registrierungsgrenze + +| | | +|---|---| +| **Prinzip** | `register_*` wirft bei fehlendem `id` oder `module`. | +| **Begründung** | Fehler beim Import/Startup, nicht erst im Admin-UI. | +| **Quelle** | `rights_registry.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine Schema-Validierung für `limit_type`/`reset_period` zur Compile-Zeit. | + +### 5. DB als persistierter Katalog, Code als SSoT für neue IDs + +| | | +|---|---| +| **Prinzip** | Startup `sync_rights_registry_to_db()` upsertet aus In-Memory-Registry. | +| **Begründung** | Admin-UI liest DB; Entwickler erweitern Code-Registry. | +| **Quelle** | `rights_registry.py`; `main.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Deaktivierte Capabilities in DB vs. fehlende im Code — Reconcile-Policy dokumentieren. | + +### 6. Default Grants als Code-Daten + +| | | +|---|---| +| **Prinzip** | `default_club_grants: (role_code, capability_id)` pro Capability. | +| **Begründung** | Neue Module bringen sinnvolle Standard-Rollen mit. | +| **Quelle** | `rights_registrations/exercises.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Admin-Overrides in DB können bei Re-Sync überschrieben werden — ON CONFLICT-Verhalten beachten. | + +--- + +## Nicht übernehmen + +1. **Capabilities nur in SQL-Migration pflegen** — driftet vom implementierten Modul weg. +2. **Registrierung ohne Endpoint-Verdrahtung** — Spec: „nur Rechte mit echter Endpoint-Verdrahtung“. +3. **Zweite Registry-Philosophie** für Custom Roles — gleiche Capability-IDs wiederverwenden (Plan Stufe E). + +--- + +## Verwandte Dokumentation + +- [CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md](./CAPABILITY_ENTITLEMENT_DESIGN_PRINCIPLES.md) +- [CAPABILITY_CATALOG.v1.md](../../../.claude/docs/technical/CAPABILITY_CATALOG.v1.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/SKILL_SCORING_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/SKILL_SCORING_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..c5fb249 --- /dev/null +++ b/docs/jinkendo-family/design-principles/SKILL_SCORING_DESIGN_PRINCIPLES.md @@ -0,0 +1,107 @@ +# Skill Scoring – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Skill Scoring & Profile“ — gewichtete Fähigkeiten-KPIs + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 8 von 15 +**Mitai-Vergleich:** [DATA_LAYER_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/DATA_LAYER_DESIGN_PRINCIPLES.md) (#2, analog: Berechnungs-SSoT) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Kern | `backend/skill_scoring.py` | +| Profile-API | `backend/routers/skill_profiles.py` | +| Planungs-Vorschläge | Planung-Router + Fähigkeiten-Seite | +| Spec | `.claude/docs/technical/SKILL_SCORING_SPEC.md` | + +--- + +## Modul + +**Skill Scoring & Profiles** + +Regelbasierte Aggregation von `exercise_skills` über Artefakte (Module, Rahmenprogramme, Pläne, Graphen) zu gewichteten Profilen mit Peer-Vergleich innerhalb desselben Artefakttyps. + +--- + +## Designprinzipien + +### 1. Berechnung in einer Schicht (`skill_scoring.py`) + +| | | +|---|---| +| **Prinzip** | Scores, Gewichte, Peer-Perzentile — nicht in React oder Router-SQL duplizieren. | +| **Begründung** | Analog Mitai Data Layer: Charts, Listen-KPIs, Planungs-Vorschläge nutzen dieselbe Logik. | +| **Quelle** | `SKILL_SCORING_SPEC.md`; `skill_scoring.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Einzelne UI-Fallbacks können noch vereinfacht rechnen. | + +### 2. Gewichtung aus Trainings-Signalen + +| | | +|---|---| +| **Prinzip** | Dauer, Vorkommen, Intensität (`niedrig`/`mittel`/`hoch`), Stufen-Spanne — explizite Multiplikatoren. | +| **Begründung** | Nachvollziehbares Ranking ohne Black-Box-ML. | +| **Quelle** | `_INTENSITY_MULT`, `_level_range_multiplier` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | `is_primary` / `development_contribution` bewusst ignoriert. | + +### 3. Peer-Vergleich nur unter gleichem Artefakttyp + +| | | +|---|---| +| **Prinzip** | Modul vs. Modul, Rahmen vs. Rahmen — nie Modul vs. Plan gemischt. | +| **Begründung** | Fachlich sinnvoller Vergleich; vermeidet irreführende Prozentwerte. | +| **Quelle** | Phase 3 Lieferung; Nutzerfunktionen §4.2 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI muss Typ-Kontext klar labeln. | + +### 4. Sichtbarkeits-filterte Peer-Menge + +| | | +|---|---| +| **Prinzip** | Peer-Pool = nur für Nutzer sichtbare Artefakte (Access Layer). | +| **Begründung** | Keine Leaks über Scores fremder Vereins-Inhalte. | +| **Quelle** | `skill_scoring.py` + Tenant-Filter in Aufrufern | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Performance bei großen Pools. | + +### 5. Planungs-Vorschläge aus Profil-Delta + +| | | +|---|---| +| **Prinzip** | Fähigkeiten-Schwerpunkte → sortierte Vorschläge für Module/Rahmen/Regressionspfade. | +| **Begründung** | Schließt Loop zwischen Katalog und Planung. | +| **Quelle** | Fähigkeiten-Seite Phase 3 | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | KI-Suche über Volltext — Backlog. | + +### 6. Default-Minuten für fehlende Dauer + +| | | +|---|---| +| **Prinzip** | `DEFAULT_ITEM_MINUTES` / `GRAPH_DEFAULT_ITEM_MINUTES` als explizite Konstanten. | +| **Begründung** | Deterministische Scores bei unvollständigen Planungsdaten. | +| **Quelle** | `skill_scoring.py` | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Fachlich kalibrierbar — dokumentieren statt verstecken. | + +--- + +## Nicht übernehmen + +1. **Score-Berechnung im Frontend** für Listen-KPIs. +2. **Peer-Vergleich über Artefakttypen hinweg** — irreführend. +3. **ML-Black-Box statt regelbasierter Gewichte** — ohne explizite Produktentscheidung. +4. **Ignorieren der Tenant-Sichtbarkeit** im Peer-Pool. + +--- + +## Verwandte Dokumentation + +- [SKILL_SCORING_SPEC.md](../../../.claude/docs/technical/SKILL_SCORING_SPEC.md) +- [EXERCISE_CATALOG_DESIGN_PRINCIPLES.md](./EXERCISE_CATALOG_DESIGN_PRINCIPLES.md) +- [TRAINING_PLANNING_DESIGN_PRINCIPLES.md](./TRAINING_PLANNING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/TRAINING_PLANNING_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/TRAINING_PLANNING_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..32b4645 --- /dev/null +++ b/docs/jinkendo-family/design-principles/TRAINING_PLANNING_DESIGN_PRINCIPLES.md @@ -0,0 +1,129 @@ +# Training Planning – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „Training Planning“ — Einheiten, Phasen, Rahmen, Coach + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 9 von 15 +**Mitai-Vergleich:** — (domänenspezifisch) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Kalender-Einheiten | `backend/routers/training_planning.py` | +| Module | `backend/routers/training_modules.py` | +| Rahmen | `backend/routers/training_framework_programs.py` | +| Phasen/Streams | Migration 063; `PARALLEL_TRAINING_STREAMS_SPEC.md` | +| Frontend | `TrainingPlanningPage`, `TrainingCoachPage`, `TrainingUnitRunPage` | +| Utils | `frontend/src/utils/trainingPlanUtils.js` | + +--- + +## Modul + +**Training Planning & Frameworks** + +Planbare Trainingseinheiten mit Sektionen, **Phasen** (Ganzgruppe/Parallel) und **Streams**, Bibliotheks-**Rahmenprogramme** (Ziele/Slots), **Trainingsmodule**, Materialisierung aus Slots, Durchführungs- und Coaching-Ansichten. + +--- + +## Designprinzipien + +### 1. Einheit als planbares Aggregate + +| | | +|---|---| +| **Prinzip** | `training_units` + `training_unit_sections` + Items; Kopf: Gruppe, Datum, Trainer, Status. | +| **Begründung** | Klare Grenze Kalender vs. Bibliothek. | +| **Quelle** | Domain Model; `training_planning.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Große Router-Datei — Refaktor-Schuld. | + +### 2. Phasen/Streams als explizites Modell (nicht Marker-Sektionen) + +| | | +|---|---| +| **Prinzip** | `training_unit_phases` + `training_unit_parallel_streams`; Sektionen an Phase oder Stream gebunden. | +| **Begründung** | Breakout-Trainings fachlich korrekt; Coach/Rejoin-Logik. | +| **Quelle** | `PARALLEL_TRAINING_STREAMS_SPEC.md` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Legacy-Einheiten → Default-Ganzgruppenphase; Vorlagen-Phasen teils offen. | + +### 3. API: verschachtelte `phases` + flache `sections` + +| | | +|---|---| +| **Prinzip** | GET liefert beides; PUT akzeptiert `phases` atomar; höchstens eines von phases/sections/exercises pro Request. | +| **Begründung** | Frontend normalisiert; Server validiert CHECK-Regeln. | +| **Quelle** | Spec §4; Planning-Router | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Server-Spiegelung neuer Abschnitte in phases — Handover offen. | + +### 4. Rahmen-Bibliothek: Slot = Blueprint-Unit + +| | | +|---|---| +| **Prinzip** | `framework_slot_id` auf Blueprint-`training_units`; Materialisierung → Kalender-Einheit für Gruppe. | +| **Begründung** | Wiederverwendbare Programme ohne Duplikat-Logik pro Slot-Typ. | +| **Quelle** | Migration 035–037 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | UI „Aus Rahmen übernehmen“ nicht flächendeckend. | + +### 5. Trainingsmodule als wiederverwendbare Übungsfolgen + +| | | +|---|---| +| **Prinzip** | Bibliotheks-Objekt mit Skill-Profil; Übernahme in geplante Einheit. | +| **Begründung** | Trainer-Bausteine zwischen Einzelübung und Rahmen. | +| **Quelle** | `training_modules.py`; Skill Scoring Phase 3 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Drei Durchführungsmodi getrennt + +| | | +|---|---| +| **Prinzip** | Planung (edit) · Plan & Ablauf (run) · Coaching (step timeline, Stream-Picks, Nachbereitung). | +| **Begründung** | Unterschiedliche UX und Payloads; Coach speichert → Run-Ansicht. | +| **Quelle** | Nutzerfunktionen §4.4; `TrainingCoachPage` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Stream-Tabs in Run-Ansicht optional offen. | + +### 7. Governance über Gruppe/Verein, keine neuen Mandanten-Entitäten + +| | | +|---|---| +| **Prinzip** | Einheit → `training_group` → Verein; Access Layer für Bibliotheks-Rahmen/Module. | +| **Begründung** | Planung erbt Organisations-Kontext. | +| **Quelle** | `PARALLEL_TRAINING_STREAMS_SPEC.md` §4 | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Stream-Trainer-Zuweisung UI unvollständig. | + +### 8. Kombinationsübungen in Planung transparent + +| | | +|---|---| +| **Prinzip** | Items ohne Variante; Coach zeigt Stations-Kandidaten + Archetyp-Hinweise. | +| **Begründung** | Gleiche Item-Schicht für Standard- und Kombi-Übungen. | +| **Quelle** | Migration 057; Kombinations-Spec Anhang A | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Archetyp-Stufen B/C ausbaubar. | + +--- + +## Nicht übernehmen + +1. **Parallele Phasen als reine UI-Konvention ohne DB-Phasen** — Minimalvariante verworfen. +2. **Rahmen-Slots als separate Exercise-Join-Tabelle** — Legacy `training_framework_slot_exercises` abgelöst. +3. **Planungs-KI direkt in Router-Strings** — AI Prompt Runtime nutzen. +4. **Blueprint-Einheiten in Kalenderlisten** — Filter `framework_slot_id IS NOT NULL`. + +--- + +## Verwandte Dokumentation + +- [PARALLEL_TRAINING_STREAMS_SPEC.md](../../../.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md) +- [TRAINING_FRAMEWORK_SPEC.md](../../../.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md) +- [SKILL_SCORING_DESIGN_PRINCIPLES.md](./SKILL_SCORING_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) diff --git a/docs/jinkendo-family/design-principles/WIKI_IMPORT_DESIGN_PRINCIPLES.md b/docs/jinkendo-family/design-principles/WIKI_IMPORT_DESIGN_PRINCIPLES.md new file mode 100644 index 0000000..267c6d8 --- /dev/null +++ b/docs/jinkendo-family/design-principles/WIKI_IMPORT_DESIGN_PRINCIPLES.md @@ -0,0 +1,118 @@ +# Wiki Import – Designprinzipien (Extraktion) + +**Status:** Analyse / Arbeitspapier +**Stand:** 2026-07-04 +**Geltungsbereich:** Modul „MediaWiki Import“ — SMW-Ingest, Mapping, Tracking + +**Serie:** Designprinzipien für Produktfamilie · Shinkan Dokument 11 von 15 +**Mitai-Vergleich:** [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) (#6) + +**Kernkomponenten:** + +| Bereich | Pfade | +|---------|-------| +| Router | `backend/routers/import_wiki.py`, `import_wiki_admin.py` | +| Client | `backend/smw_client.py` | +| Mapper | `backend/smw_mapper.py` | +| Tracking | `wiki_import_log`, `wiki_import_references` | +| Spec | `.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md` | + +--- + +## Modul + +**Wiki Import (Semantic MediaWiki)** + +Import von Übungen, Fähigkeiten, Methoden und Reifegradmodellen aus externem Wiki via API — Preview, Dry-Run, Duplikat-Erkennung, Admin-only. + +--- + +## Designprinzipien + +### 1. Ingest ≠ Interpretation + +| | | +|---|---| +| **Prinzip** | `SmwClient` holt Rohdaten; `smw_mapper` mappt auf Shinkan-Modelle — getrennte Schichten. | +| **Begründung** | Analog Mitai Import: Transport/Parser ≠ Domänen-Insert. | +| **Quelle** | `import_wiki.py`; Mitai Universal Import | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Keine generische Import-Registry wie Mitai CSV — wiki-spezifisch. | + +### 2. Preview und Dry-Run vor Execute + +| | | +|---|---| +| **Prinzip** | `/preview` zeigt Kandidaten; `dry_run=true` ohne DB-Schreiben. | +| **Begründung** | Admin sieht Auswirkungen; sichere Iteration. | +| **Quelle** | `ImportExecuteRequest` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 3. Duplikat-Tracking über Wiki-Referenzen + +| | | +|---|---| +| **Prinzip** | `wiki_import_references` speichert Wiki-Titel ↔ Shinkan-ID für Re-Import. | +| **Begründung** | Idempotenz und Update statt blindem Duplicate. | +| **Quelle** | Domain Model Import-Tabellen | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Gap-Analyse in `SMW_IMPORTER_GAP_ANALYSIS.md` beachten. | + +### 4. Import-Typ als expliziter Parameter + +| | | +|---|---| +| **Prinzip** | `import_type`: `exercise` \| `skill` \| `method` \| Modelle — eigener Mapper-Pfad. | +| **Begründung** | Klare Verantwortung pro Ziel-Entität. | +| **Quelle** | `map_wiki_to_*` in `smw_mapper.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | Kein Plug-in-Registry-Pattern wie Mitai Module-Registry. | + +### 5. Superadmin/Admin-only Execute + +| | | +|---|---| +| **Prinzip** | `require_admin` auf Execute — Massenimport ist Plattform-Risiko. | +| **Begründung** | Governance und Datenqualität. | +| **Quelle** | `import_wiki.py` | +| **Tragfähigkeit** | **hoch** | +| **Einschränkung** | — | + +### 6. Kategorie aus Env mit Fallback + +| | | +|---|---| +| **Prinzip** | `MEDIAWIKI_CATEGORY_*` Env-Variablen; leere Query → Default je Typ. | +| **Begründung** | Wiki-Struktur konfigurierbar ohne Code-Deploy. | +| **Quelle** | `CATEGORY_EXERCISES` etc. | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Hardcoded Wiki-URL in Doku — Umgebungsspezifisch halten. | + +### 7. Background Tasks für lange Imports + +| | | +|---|---| +| **Prinzip** | FastAPI `BackgroundTasks` für Execute — HTTP nicht blockieren. | +| **Begründung** | Große Kategorien ohne Timeout. | +| **Quelle** | Execute-Endpoint | +| **Tragfähigkeit** | **mittel** | +| **Einschränkung** | Kein Job-Status-Polling-UI wie Mitai — Log-Tabelle nutzen. | + +--- + +## Nicht übernehmen + +1. **Rohe Wiki-HTML ungemappt in DB** — immer Mapper. +2. **Import ohne Log/Re-Import-Referenz** — Duplikat-Chaos. +3. **Trainer-self-service Wiki-Import** — Admin-only. +4. **Skill-Scoring beim Insert** — Scores gehören in `skill_scoring`-Schicht. + +--- + +## Verwandte Dokumentation + +- [MEDIAWIKI_IMPORT_SPEC.md](../../../.claude/docs/technical/MEDIAWIKI_IMPORT_SPEC.md) +- [EXERCISE_CATALOG_DESIGN_PRINCIPLES.md](./EXERCISE_CATALOG_DESIGN_PRINCIPLES.md) +- Mitai: [UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md](file:///c:/Dev/mitai-jinkendo/.claude/docs/technical/UNIVERSAL_IMPORT_DESIGN_PRINCIPLES.md) +- [DESIGN_PRINCIPLES_INDEX.md](./DESIGN_PRINCIPLES_INDEX.md) From 32b28a93f379dd39bc99c89f6d463724e2c28b61 Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 13 Oct 2026 07:45:53 +0200 Subject: [PATCH 3/6] Harden login, sessions, and media access before the dev deploy. Limit brute-force attempts, require admin TOTP, and keep session tokens out of media URLs. Co-authored-by: Cursor --- .env.example | 7 +- backend/account_onboarding_gate.py | 2 + backend/auth.py | 74 ++- backend/main.py | 2 +- backend/media_grant.py | 49 ++ .../migrations/096_login_lockout_and_totp.sql | 29 ++ backend/models.py | 23 +- backend/password_reset_mail.py | 4 +- backend/routers/auth.py | 431 ++++++++++++++++-- backend/routers/content_reports.py | 1 + backend/routers/exercises.py | 2 +- backend/routers/media_assets.py | 2 +- backend/routers/profiles.py | 33 +- backend/tests/test_auth_hardening.py | 51 +++ backend/totp_auth.py | 63 +++ backend/version.py | 18 +- docker-compose.dev-env.yml | 6 +- docker-compose.yml | 5 +- frontend/src/App.jsx | 28 ++ .../ExerciseAttachmentMediaStrip.jsx | 5 +- .../ExerciseInlineFileMediaModal.jsx | 5 +- .../src/components/ExerciseMediaEmbed.jsx | 5 +- .../src/components/ExerciseMediaThumbTile.jsx | 5 +- frontend/src/components/TotpSetupCard.jsx | 101 ++++ .../exercises/ExerciseFormPageRoot.jsx | 7 +- frontend/src/context/AuthContext.jsx | 18 + frontend/src/pages/AccountSettingsPage.jsx | 77 +++- frontend/src/pages/LoginPage.jsx | 76 ++- frontend/src/pages/MediaLibraryPage.jsx | 8 +- frontend/src/utils/api.js | 46 +- frontend/src/utils/exerciseMediaUrl.js | 64 ++- 31 files changed, 1146 insertions(+), 101 deletions(-) create mode 100644 backend/media_grant.py create mode 100644 backend/migrations/096_login_lockout_and_totp.sql create mode 100644 backend/tests/test_auth_hardening.py create mode 100644 backend/totp_auth.py create mode 100644 frontend/src/components/TotpSetupCard.jsx diff --git a/.env.example b/.env.example index ad3344b..9ae5272 100644 --- a/.env.example +++ b/.env.example @@ -53,7 +53,12 @@ SMTP_FROM=noreply@jinkendo.de SMTP_SSL= SMTP_STARTTLS= -AUTO_ADMIN_FIRST_USER=true +# Produktion: false. true nur für eine bewusst leere Erstinstallation. +AUTO_ADMIN_FIRST_USER=false +# Plattform-Admins müssen TOTP einrichten. 0 schaltet die Pflicht ab. +REQUIRE_ADMIN_TOTP=true +# Optional. Sonst wird das Medien-Token aus DB_PASSWORD abgeleitet. +# MEDIA_TOKEN_SECRET= ADMIN_BOOTSTRAP_EMAILS= APP_URL=https://shinkan.jinkendo.de diff --git a/backend/account_onboarding_gate.py b/backend/account_onboarding_gate.py index b643b10..c962fd9 100644 --- a/backend/account_onboarding_gate.py +++ b/backend/account_onboarding_gate.py @@ -33,6 +33,8 @@ AUTH_INFRA_PREFIXES = ( "/api/auth/status", "/api/auth/pin", "/api/auth/resend-verification", + "/api/auth/totp", + "/api/auth/media-grant", "/api/profiles/me", "/api/me/entitlements", ) diff --git a/backend/auth.py b/backend/auth.py index 7e3b18e..b7ec6b4 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -12,6 +12,11 @@ from fastapi import Header, Query, HTTPException import bcrypt from db import get_db, get_cursor +from media_grant import profile_id_from_media_grant + +# Fester bcrypt-Hash, damit ein unbekannter Login dieselbe Rechenzeit hat wie ein Treffer. +_LOGIN_TIMING_HASH = "$2b$12$GYdnAmeiqaZg/.5J9E4f4.xhz70piViqXIpqKCY/FY609qj65sc9i" +MAX_SESSION_DAYS = 30 def hash_pin(pin: str) -> str: @@ -33,6 +38,34 @@ def verify_pin(pin: str, stored_hash: str) -> bool: return stored_hash == hashlib.sha256(pin.encode()).hexdigest() +def verify_pin_for_login(pin: str, stored_hash: Optional[str]) -> bool: + """Immer ein bcrypt-Vergleich, auch wenn das Konto fehlt (kein Zeit-Orakel).""" + if stored_hash and str(stored_hash).startswith("$2"): + return verify_pin(pin, stored_hash) + verify_pin(pin or "x", _LOGIN_TIMING_HASH) + if not stored_hash: + return False + return verify_pin(pin, stored_hash) + + +def session_lifetime_days(profile: Optional[dict]) -> int: + raw = profile.get("session_days") if profile else None + try: + days = int(raw) if raw is not None else MAX_SESSION_DAYS + except (TypeError, ValueError): + days = MAX_SESSION_DAYS + if days < 1: + days = 1 + if days > MAX_SESSION_DAYS: + days = MAX_SESSION_DAYS + return days + + +def revoke_profile_sessions(cur, profile_id: int) -> None: + """Alle Sitzungen eines Profils entfernen, inklusive Reset- und 2FA-Zwischentokens.""" + cur.execute("DELETE FROM sessions WHERE profile_id = %s", (profile_id,)) + + def make_token() -> str: """Generate a secure random token for sessions.""" return secrets.token_urlsafe(32) @@ -51,12 +84,39 @@ def get_session(token: str): cur.execute( "SELECT s.*, p.role, p.name " "FROM sessions s JOIN profiles p ON s.profile_id=p.id " - "WHERE s.token=%s AND s.expires_at > CURRENT_TIMESTAMP", + "WHERE s.token=%s AND s.expires_at > CURRENT_TIMESTAMP " + "AND COALESCE(s.purpose, 'full') = 'full'", (token,) ) return cur.fetchone() +def get_limited_session(token: str, purpose: str): + """Sitzung mit festem Zweck (totp_enroll, totp_challenge). Kein Fach-API-Zugriff.""" + if not token or purpose not in ("totp_enroll", "totp_challenge"): + return None + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "SELECT s.*, p.role, p.name, p.email " + "FROM sessions s JOIN profiles p ON s.profile_id=p.id " + "WHERE s.token=%s AND s.expires_at > CURRENT_TIMESTAMP " + "AND s.purpose = %s", + (token, purpose), + ) + return cur.fetchone() + + +def _profile_session_from_id(profile_id: int) -> Optional[dict]: + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "SELECT id AS profile_id, role, name FROM profiles WHERE id = %s", + (profile_id,), + ) + return cur.fetchone() + + def require_auth(x_auth_token: Optional[str] = Header(default=None)): """ FastAPI dependency - requires valid authentication. @@ -80,8 +140,8 @@ def require_auth_flexible(x_auth_token: Optional[str] = Header(default=None), ss """ FastAPI dependency - auth via header OR query parameter. - Used for endpoints accessed by tags and SSE connections that can't send headers. - Query parameter is 'ssetoken' to avoid conflicts with endpoint 'token' parameters. + Dateiabruf für /