feat: enhance access layer governance and visibility checks
- Added new documentation references for access layer governance in CLAUDE.md, including multi-tenancy and endpoint audit guidelines. - Updated ACCESS_LAYER_AND_GOVERNANCE_PLAN.md to include cursor and heuristic checks for access layer compliance. - Enhanced ACCESS_LAYER_ENDPOINT_AUDIT.md to clarify endpoint visibility and governance requirements, including exemptions for certain routers. - Introduced library_content_visible_to_profile function in club_tenancy.py to streamline visibility checks for library content. - Updated exercise progression graphs router to utilize the new visibility function, improving access control. - Bumped application version to 0.8.27 and updated changelog to reflect these changes.
This commit is contained in:
parent
5aee9c52fc
commit
abee6171df
|
|
@ -96,6 +96,8 @@ Ausgangslage im Code: `private` \| `club` \| `official` (siehe `club_tenancy`).
|
||||||
|
|
||||||
| Mechanismus | Inhalt |
|
| Mechanismus | Inhalt |
|
||||||
|-------------|--------|
|
|-------------|--------|
|
||||||
|
| **Cursor / IDE** | Projektregel `.cursor/rules/access-layer.mdc` (Router); Agents sollen nicht auf „nur require_auth“ ausweichen. |
|
||||||
|
| **Heuristik-Check** | `python backend/scripts/check_access_layer_hints.py`; CI optional mit `ACCESS_LAYER_STRICT=1`. |
|
||||||
| **PR-Checkliste** | Neuer/changed Endpoint: TenantContext verwendet? Governance für Listen + Detail? Tests für zweiten Verein? |
|
| **PR-Checkliste** | Neuer/changed Endpoint: TenantContext verwendet? Governance für Listen + Detail? Tests für zweiten Verein? |
|
||||||
| **Single Source of Truth** | Sichtbarkeitsregeln nur in Zugriffsmodul(en), nicht in Routers dupliziert. |
|
| **Single Source of Truth** | Sichtbarkeitsregeln nur in Zugriffsmodul(en), nicht in Routers dupliziert. |
|
||||||
| **Änderungen am Enum** | Nur zusammen mit Migration + Kurzbeschreibung in diesem Dokument (Datum/Changelog-Zeile). |
|
| **Änderungen am Enum** | Nur zusammen mit Migration + Kurzbeschreibung in diesem Dokument (Datum/Changelog-Zeile). |
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,17 @@ Fortlaufend gemäß `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe A–C.
|
||||||
| exercise_progression_graphs | `/api/exercise-progression-graphs*` | ja | `get_tenant_context` | Liste wie Bibliothek; Schreiben Ersteller/Plattform-Admin | Kanten: Lesen wenn Graph lesbar |
|
| exercise_progression_graphs | `/api/exercise-progression-graphs*` | ja | `get_tenant_context` | Liste wie Bibliothek; Schreiben Ersteller/Plattform-Admin | Kanten: Lesen wenn Graph lesbar |
|
||||||
| training_planning | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Vorlagen-Liste wie Übungen; POST Vorlage Default club_id |
|
| training_planning | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Vorlagen-Liste wie Übungen; POST Vorlage Default club_id |
|
||||||
| training_framework_programs | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Liste + POST Default club_id |
|
| training_framework_programs | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Liste + POST Default club_id |
|
||||||
| admin_users | `GET /api/admin/users` | Plattform | optional | Admin-Rolle | |
|
| admin_users | `GET /api/admin/users` | Plattform | `require_auth` | Admin-Rolle | EXEMPT `check_access_layer_hints.py` |
|
||||||
| Sonstige | skills, methods, catalogs | zu klären | — | oft global | Zeilen ergänzen |
|
| auth | `/api/auth/*` | nein | — | Login/Session | EXEMPT |
|
||||||
|
| catalogs | Katalog-CRUD | nein (global) | `require_auth` | Admin/Trainer je Endpoint | EXEMPT; bei späterem `club_id` nachziehen |
|
||||||
|
| skills | `/api/skills*` | nein (global) | `require_auth` | je Endpoint | EXEMPT |
|
||||||
|
| maturity_models | Admin-Matrix | nein (global) | `require_auth` | Admin | EXEMPT |
|
||||||
|
| matrix_stack_bundle | Export/Import Bundles | Plattform/Test | `require_auth` | Admin | EXEMPT |
|
||||||
|
| import_wiki / import_wiki_admin | Wiki-Import | Werkzeug | `require_auth`/Admin | Admin | EXEMPT |
|
||||||
|
|
||||||
**Legende:** „zu klären“ = keine Vereinsdaten oder globales Lesen; bei neuem Bezug zu `club_id`/`visibility` nachziehen.
|
**Legende:** Router auf der EXEMPT-Liste des Scripts sind globale oder Auth-only-Pfade; sobald ein Router Vereinsdaten oder Bibliotheks-Sichtbarkeit erhält, EXEMPT entfernen und `get_tenant_context` einführen.
|
||||||
|
|
||||||
Letzte Änderung: 2026-05-05 — Vereins-/Mitgliedschafts-/Antrags-Router und Profil-PUT auf `get_tenant_context`; Progressionsgraphen Sichtbarkeit wie Bibliothek.
|
Letzte Änderung: 2026-05-05 — Cursor-Regel + Architektur-/Coding-Pflicht + Script `backend/scripts/check_access_layer_hints.py`; Katalog-Router im Audit als global dokumentiert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,21 @@ return {"error": "not found"}
|
||||||
return {"message": "Fehler", "success": False}
|
return {"message": "Fehler", "success": False}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER)
|
||||||
|
|
||||||
|
**Verbindlicher Rahmen:** `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
|
||||||
|
**Fortlaufendes Inventar:** `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`
|
||||||
|
|
||||||
|
**Definition of Done für neue oder geänderte geschützte APIs**, sobald Daten **Verein**, **Sichtbarkeit** oder **mandantenbezogene Listen** betreffen:
|
||||||
|
|
||||||
|
1. Request-Kontext: `Depends(get_tenant_context)` (oder dokumentierte Ausnahme mit Kommentar `# ACCESS_LAYER exempt:` + Audit).
|
||||||
|
2. Lesen: gleiche Sichtbarkeitslogik wie vergleichbare Bibliotheks-/Planungsartefakte (`library_content_visibility_sql`, `exercise_visible_to_profile` / zentrale Helfer — nicht ad-hoc „alles aus der Tabelle“).
|
||||||
|
3. Schreiben: `assert_valid_governance_visibility` wo `visibility` / `club_id` gesetzt oder geändert wird.
|
||||||
|
4. Audit-Tabelle und bei Bedarf die EXEMPT-Liste im Script `backend/scripts/check_access_layer_hints.py` aktualisieren.
|
||||||
|
5. Optional vor Commit: `python backend/scripts/check_access_layer_hints.py` (mit `ACCESS_LAYER_STRICT=1` schlägt bei neuen Verstößen fehl).
|
||||||
|
|
||||||
|
Router ohne Vereinsbezug (z. B. globale Kataloge, Auth-Login) bleiben bewusst ohne `get_tenant_context`; sie stehen im Script auf der **EXEMPT**-Liste.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Versionskontrollsystem
|
## 2. Versionskontrollsystem
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,32 @@ Diese Regeln IMMER befolgen. Sie basieren auf Erfahrungen aus der Entwicklung.
|
||||||
|
|
||||||
## Backend
|
## Backend
|
||||||
|
|
||||||
### 1. Auth auf jeden Endpoint
|
### 1. Auth und Mandantenkontext (Shinkan)
|
||||||
|
|
||||||
|
**Jeder geschützte Endpoint braucht Auth.** Sofern der Endpoint **Vereinsdaten**, **visibility/club_id** oder **mandanten-gefilterte Listen** betrifft, zusätzlich **`TenantContext`** — nicht nur `require_auth` allein.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Jeder neue Endpoint braucht Auth:
|
from tenant_context import TenantContext, get_tenant_context
|
||||||
@router.get("/neuer-endpoint")
|
|
||||||
def neuer_endpoint(session: dict = Depends(require_auth)):
|
@router.get("/beispiel")
|
||||||
pid = session['profile_id']
|
def beispiel(tenant: TenantContext = Depends(get_tenant_context)):
|
||||||
|
pid = tenant.profile_id
|
||||||
|
role = tenant.global_role
|
||||||
|
club_ctx = tenant.effective_club_id # kann None sein (z. B. Plattform-Admin)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Profile-ID aus Session – nie aus Header
|
- **Bibliotheks-/Planungslisten:** Filter wie bestehende Module (`library_content_visibility_sql` oder gleiche Leseprüfung); keine vollständige Tabelle für normale Nutzer.
|
||||||
|
- **Schreiben:** `assert_valid_governance_visibility` aus `club_tenancy`, wenn `visibility` / `club_id` gesetzt werden.
|
||||||
|
- **Dokumentation:** Änderungen in `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` festhalten.
|
||||||
|
- **Ausnahmen** (z. B. reiner Login, globale Kataloge): Kommentar `# ACCESS_LAYER exempt: …` und ggf. Eintrag in `backend/scripts/check_access_layer_hints.py`.
|
||||||
|
|
||||||
|
Reine Plattform-Admin-Router (ohne Vereinskontext) können bei Bedarf weiter `Depends(require_auth)` nutzen — dann im Audit als „Plattform“ kennzeichnen.
|
||||||
|
|
||||||
|
### 2. Profile-ID aus TenantContext oder Session — nie aus freiem Header
|
||||||
|
|
||||||
```python
|
```python
|
||||||
pid = session['profile_id'] # ✅
|
pid = tenant.profile_id # ✅ bei Depends(get_tenant_context)
|
||||||
|
# oder session['profile_id'] nur wenn Endpoint ausdrücklich ohne TenantContext (Ausnahme dokumentieren)
|
||||||
# Nicht: request.headers.get('X-Profile-Id') ❌
|
# Nicht: request.headers.get('X-Profile-Id') ❌
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
36
.cursor/rules/access-layer.mdc
Normal file
36
.cursor/rules/access-layer.mdc
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
description: Mandanten & Governance — TenantContext, Sichtbarkeit, keine Schnellpfade
|
||||||
|
globs: backend/routers/*.py
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Zugriffsschicht (Shinkan)
|
||||||
|
|
||||||
|
Vor neuen oder geänderten Endpoints in `backend/routers/` kurz prüfen:
|
||||||
|
|
||||||
|
1. **Pflichtlektüre** (bei inhaltsbezogenen APIs): `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` und `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`.
|
||||||
|
|
||||||
|
2. **Auth**: kein geschützter Endpoint ohne `Depends(require_auth)` bzw. eingebettet in `Depends(get_tenant_context)` (der holt die Session bereits).
|
||||||
|
|
||||||
|
3. **Verein / Sichtbarkeit** (`visibility`, `club_id`, Mitglieder-Inhalte):
|
||||||
|
`tenant: TenantContext = Depends(get_tenant_context)` verwenden; Profile-ID aus `tenant.profile_id`, Rolle aus `tenant.global_role`.
|
||||||
|
|
||||||
|
4. **Bibliothekslisten** (Übungen, Vorlagen, Rahmenprogramme, Progressionsgraphen, gleiche Semantik): Filter über `library_content_visibility_sql(...)` bzw. gleiche Leseregel wie bestehende Module — nicht „alle Zeilen aus SELECT“.
|
||||||
|
|
||||||
|
5. **Schreiben mit Governance**: bei `visibility`/`club_id`-Änderungen `assert_valid_governance_visibility`; bei `club` ohne `club_id` im Body → `tenant.effective_club_id` oder klare 400-Hinweise (wie bei Übungen).
|
||||||
|
|
||||||
|
6. **Ausnahmen** nur mit kurzem Kommentar im Code: `# ACCESS_LAYER exempt: …` und Eintrag im Audit oder in `backend/scripts/check_access_layer_hints.py` (EXEMPT-Liste).
|
||||||
|
|
||||||
|
7. **Nach Merge**: eine Zeile `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` anpassen, wenn sich Tenant oder Governance ändert.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ❌ Schnellpfad: nur Session, obwohl Vereinsdaten betroffen
|
||||||
|
@router.get("/foo")
|
||||||
|
def foo(session=Depends(require_auth)):
|
||||||
|
...
|
||||||
|
|
||||||
|
# ✅ Konsistent zu clubs/exercises/planning
|
||||||
|
@router.get("/foo")
|
||||||
|
def foo(tenant: TenantContext = Depends(get_tenant_context)):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
> VOR jeder Implementierung lesen:
|
> VOR jeder Implementierung lesen:
|
||||||
> | Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` |
|
> | Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` |
|
||||||
> | Coding-Regeln | `.claude/rules/CODING_RULES.md` |
|
> | Coding-Regeln | `.claude/rules/CODING_RULES.md` |
|
||||||
|
> | Zugriffsschicht (Multi-Tenancy, Governance) | `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` |
|
||||||
|
> | Endpoint-Audit (Tenant/Governance) | `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` |
|
||||||
|
> | Cursor-Regel Zugriffsschicht | `.cursor/rules/access-layer.mdc` |
|
||||||
> | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` |
|
> | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` |
|
||||||
> | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
> | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
|
||||||
> | Anforderungen | `.claude/docs/functional/SHINKAN_REQUIREMENTS.md` |
|
> | Anforderungen | `.claude/docs/functional/SHINKAN_REQUIREMENTS.md` |
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,20 @@ def assert_valid_governance_visibility(
|
||||||
assert_club_member(cur, profile_id, club_id)
|
assert_club_member(cur, profile_id, club_id)
|
||||||
|
|
||||||
|
|
||||||
|
def library_content_visible_to_profile(
|
||||||
|
cur,
|
||||||
|
profile_id: int,
|
||||||
|
visibility: str,
|
||||||
|
content_club_id: Optional[int],
|
||||||
|
created_by: Optional[int],
|
||||||
|
global_role: Optional[str],
|
||||||
|
) -> bool:
|
||||||
|
"""Leserechte wie Übungen für alle Objekte mit visibility/club_id/created_by (Bibliothek & Co.)."""
|
||||||
|
return exercise_visible_to_profile(
|
||||||
|
cur, profile_id, visibility, content_club_id, created_by, global_role
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def exercise_visible_to_profile(
|
def exercise_visible_to_profile(
|
||||||
cur,
|
cur,
|
||||||
profile_id: int,
|
profile_id: int,
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ from db import get_db, get_cursor, r2d
|
||||||
from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql
|
from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql
|
||||||
from club_tenancy import (
|
from club_tenancy import (
|
||||||
assert_valid_governance_visibility,
|
assert_valid_governance_visibility,
|
||||||
exercise_visible_to_profile,
|
|
||||||
is_platform_admin,
|
is_platform_admin,
|
||||||
|
library_content_visible_to_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
from routers.training_planning import _has_planning_role
|
from routers.training_planning import _has_planning_role
|
||||||
|
|
@ -106,7 +106,7 @@ def _assert_graph_readable(cur, row: dict, profile_id: int, role: str) -> None:
|
||||||
cr = row.get("created_by")
|
cr = row.get("created_by")
|
||||||
if cr is not None:
|
if cr is not None:
|
||||||
cr = int(cr)
|
cr = int(cr)
|
||||||
if not exercise_visible_to_profile(cur, profile_id, vis, cid, cr, role):
|
if not library_content_visible_to_profile(cur, profile_id, vis, cid, cr, role):
|
||||||
raise HTTPException(status_code=403, detail="Keine Berechtigung für diesen Progressionsgraph")
|
raise HTTPException(status_code=403, detail="Keine Berechtigung für diesen Progressionsgraph")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
76
backend/scripts/check_access_layer_hints.py
Normal file
76
backend/scripts/check_access_layer_hints.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Heuristik-Check: Router mit FastAPI-Routen und Auth sollten bei mandantenrelevanten APIs
|
||||||
|
get_tenant_context nutzen — siehe ACCESS_LAYER_AND_GOVERNANCE_PLAN.md.
|
||||||
|
|
||||||
|
Lauf aus Repo-Root:
|
||||||
|
python backend/scripts/check_access_layer_hints.py
|
||||||
|
|
||||||
|
Mit Fehler-Exit bei Verstößen (z. B. CI):
|
||||||
|
set ACCESS_LAYER_STRICT=1 # Windows: set ACCESS_LAYER_STRICT=1
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Router, die bewusst keinen TenantContext verwenden (global / Auth-only / Admin-Tools).
|
||||||
|
# Neuen Eintrag nur mit kurzer Begründung im Commit/Audit ergänzen.
|
||||||
|
EXEMPT_ROUTERS: frozenset[str] = frozenset(
|
||||||
|
{
|
||||||
|
"auth.py",
|
||||||
|
"admin_users.py",
|
||||||
|
"catalogs.py",
|
||||||
|
"skills.py",
|
||||||
|
"maturity_models.py",
|
||||||
|
"matrix_stack_bundle.py",
|
||||||
|
"import_wiki.py",
|
||||||
|
"import_wiki_admin.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
strict = os.environ.get("ACCESS_LAYER_STRICT", "").strip().lower() in ("1", "true", "yes")
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
routers = root / "routers"
|
||||||
|
if not routers.is_dir():
|
||||||
|
print("check_access_layer_hints: routers/ nicht gefunden", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
issues: list[str] = []
|
||||||
|
for path in sorted(routers.glob("*.py")):
|
||||||
|
name = path.name
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
if "@router." not in text:
|
||||||
|
continue
|
||||||
|
if name in EXEMPT_ROUTERS:
|
||||||
|
continue
|
||||||
|
if "get_tenant_context" in text:
|
||||||
|
continue
|
||||||
|
if "require_auth" not in text:
|
||||||
|
continue
|
||||||
|
issues.append(
|
||||||
|
f" {path.relative_to(root.parent)}: Routen + require_auth, "
|
||||||
|
f"aber kein get_tenant_context — TenantContext ergänzen oder in EXEMPT_ROUTERS eintragen "
|
||||||
|
f"(mit Begründung / Audit)."
|
||||||
|
)
|
||||||
|
|
||||||
|
if not issues:
|
||||||
|
print("check_access_layer_hints: OK (keine auffälligen Router außerhalb EXEMPT).")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print("check_access_layer_hints: mögliche ACCESS_LAYER-Abweichungen:\n", file=sys.stderr)
|
||||||
|
for line in issues:
|
||||||
|
print(line, file=sys.stderr)
|
||||||
|
print(
|
||||||
|
"\nHinweis: Heuristik — false positives möglich. "
|
||||||
|
"Bei echter Ausnahme Datei zu EXEMPT_ROUTERS hinzufügen.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1 if strict else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
# Shinkan Jinkendo Version Information
|
# Shinkan Jinkendo Version Information
|
||||||
|
|
||||||
APP_VERSION = "0.8.26"
|
APP_VERSION = "0.8.27"
|
||||||
BUILD_DATE = "2026-05-05"
|
BUILD_DATE = "2026-05-05"
|
||||||
DB_SCHEMA_VERSION = "20260505041"
|
DB_SCHEMA_VERSION = "20260505041"
|
||||||
|
|
||||||
MODULE_VERSIONS = {
|
MODULE_VERSIONS = {
|
||||||
"auth": "1.2.1", # Login-Rate-Limit 30/minute pro IP (vorher 5/minute)
|
"auth": "1.2.1", # Login-Rate-Limit 30/minute pro IP (vorher 5/minute)
|
||||||
"profiles": "1.4.1", # PUT /profiles*, Legacy /profile: Depends(get_tenant_context); profile_document für internes Laden
|
"profiles": "1.4.1", # PUT /profiles*, Legacy /profile: Depends(get_tenant_context); profile_document für internes Laden
|
||||||
"tenant_context": "1.0.1", # Vereine/Mitglieder/Vereinsanträge/Profil-PUT nutzen get_tenant_context (Header-Membership)
|
"tenant_context": "1.0.2", # Dokumentation: ACCESS_LAYER Pflicht + Script check_access_layer_hints.py
|
||||||
"clubs": "0.4.1", # Alle geschützten Endpoints Depends(get_tenant_context); profile_id/role aus TenantContext
|
"clubs": "0.4.1", # Alle geschützten Endpoints Depends(get_tenant_context); profile_id/role aus TenantContext
|
||||||
"club_memberships": "1.0.1", # Depends(get_tenant_context)
|
"club_memberships": "1.0.1", # Depends(get_tenant_context)
|
||||||
"club_join_requests": "1.0.1", # Depends(get_tenant_context)
|
"club_join_requests": "1.0.1", # Depends(get_tenant_context)
|
||||||
|
|
@ -15,7 +15,7 @@ MODULE_VERSIONS = {
|
||||||
"groups": "0.1.0",
|
"groups": "0.1.0",
|
||||||
"skills": "0.1.0",
|
"skills": "0.1.0",
|
||||||
"methods": "0.1.0",
|
"methods": "0.1.0",
|
||||||
"exercises": "2.6.2", # Progressionsgraphen: library_content_visibility_sql + Lesen wie Bibliothek; Schreiben Ersteller/Admin; Governance club_id wie Übungen
|
"exercises": "2.6.3", # Progressionsgraphen: library_content_visible_to_profile aus club_tenancy
|
||||||
"training_units": "0.1.0",
|
"training_units": "0.1.0",
|
||||||
"training_programs": "0.1.0",
|
"training_programs": "0.1.0",
|
||||||
"planning": "0.8.0", # TenantContext auf allen Planungs-Endpunkten; Vorlagen-Liste wie Übungen nach aktivem Verein
|
"planning": "0.8.0", # TenantContext auf allen Planungs-Endpunkten; Vorlagen-Liste wie Übungen nach aktivem Verein
|
||||||
|
|
@ -27,6 +27,15 @@ MODULE_VERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
{
|
||||||
|
"version": "0.8.27",
|
||||||
|
"date": "2026-05-05",
|
||||||
|
"changes": [
|
||||||
|
"ACCESS_LAYER Governance-Disziplin: .cursor/rules/access-layer.mdc; ARCHITECTURE 1.4 + CODING_RULES Tenant-Pfad; CLAUDE Pflichtlektüre Zugriffsschicht",
|
||||||
|
"backend/scripts/check_access_layer_hints.py — Router ohne get_tenant_context außerhalb EXEMPT melden (optional ACCESS_LAYER_STRICT=1)",
|
||||||
|
"club_tenancy.library_content_visible_to_profile; Audit-Tabelle um globale Router (EXEMPT) ergänzt",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.8.26",
|
"version": "0.8.26",
|
||||||
"date": "2026-05-05",
|
"date": "2026-05-05",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Shinkan Jinkendo Frontend Version
|
// Shinkan Jinkendo Frontend Version
|
||||||
|
|
||||||
export const APP_VERSION = "0.8.26"
|
export const APP_VERSION = "0.8.27"
|
||||||
export const BUILD_DATE = "2026-05-05"
|
export const BUILD_DATE = "2026-05-05"
|
||||||
|
|
||||||
export const PAGE_VERSIONS = {
|
export const PAGE_VERSIONS = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user