"""Identität: einheitliches Principal-Modell, unabhängig vom Anmeldeweg (→ Konzept 5.1). Fachliche Berechtigungen hängen nur am Principal, nie an der Demo-Anmeldung. Ein späterer OIDC-/Keycloak-Provider liefert denselben Principal. """ from dataclasses import dataclass, field @dataclass(frozen=True) class Principal: user_id: int username: str display_name: str is_admin: bool = False team_ids: frozenset[int] = field(default_factory=frozenset) # Solange gesetzt, ist nur das Ändern des Passworts erlaubt must_change_password: bool = False # Sitzung (für Abmelden und CSRF); leer außerhalb von Web-Anfragen, z. B. im Worker session_id: str = "" csrf: str = ""