Globaler Tenant-Kontext (IDEA-027)
Architektur-Entscheidung fuer Alexium Pages / Alexium Studio: jede View, jeder Controller, jedes Repository operiert implizit auf einem aktiven Tenant pro Session.
Kern-Idee
Statt dass jede View einen eigenen Tenant-Dropdown hat und Sysadmin-Spezialbehandlung baut, lebt der aktive Tenant in $_SESSION['active_tenant_id']. Sysadmin und Sysdesigner koennen ihn per Topbar-Switcher wechseln; alle anderen Rollen sind an ihren primaeren Tenant gepinnt.
Service-API
Service\Context\TenantContext (statisch):
| Methode | Zweck |
|---|---|
active(): ?int | Aktueller Tenant aus Session (null fuer ungesetzten Sysadmin) |
setActive(int $tid) | Switcher schreibt; Pinned-Sessions werfen LogicException |
pin(int $tid) | Tenant-/Unit-Rollen werden an ihren Tenant gepinnt (Login) |
isPinned(): bool | true wenn Pin gesetzt (kein Switch erlaubt) |
clear() | Bei Logout |
Pin-Verhalten beim Login
| Rolle | Verhalten |
|---|---|
system_admin, system_designer | Kein Pin. active() startet null, Switcher setzt |
tenant_* | pin($user['tenant_id']) |
unit_* | pin($user['tenant_id']) (Tenant der primaeren Unit) |
Was die Migration aufraeumt
Vorher (heute):
if ($user['isSystemAdmin']) {
$rows = $repo->findAllAcrossTenants();
} else {
$rows = $repo->findByTenant($user['tenant_id']);
}Nachher:
$rows = $repo->findByTenant(TenantContext::active());Sysadmin sieht “alles” nicht mehr durch Branching, sondern durch Switchen.
Inventur (Stand 2026-04-26)
- 12 von 19 Partial-Views mit Tenant-Filter
- 7 Controller mit Sysadmin-Branching (~30 Stellen allein im WorkflowController)
- 38 Repo-Methoden mit
tenantId-Param - 16 JS-Tenant-Dropdowns (12 entfallen, 4 bleiben fachlich)
- 10 tenant-agnostische Views (Audit-Log, Host-Bindings, System-Settings, Profile etc.)
Detail: project/superpowers/specs/2026-04-26-tenant-context-inventory.md
Stand (2026-04-26): Sprint 3 abgeschlossen
Vollstaendig umgesetzt im Branch feature/tenant-context:
- Service-API:
TenantContext(Session) +TenantContextLookup(DB-Helper) +TenantContextBootstrap(Per-Request-Validation) - Migration 063:
account.last_active_tenant_idmit FK + ON DELETE SET NULL - Topbar-Switcher-UI: Pill rechts vom Brand mit Dropdown (Sysadmin) bzw. read-only Badge (Tenant-User)
- Smart-Default: beim Sysadmin-Login wird
last_active_tenant_idmit Fallback auf kleinstetenant_idgesetzt - Controller-Sweep: WorkflowController (30 Stellen), UserController, MediaController, EventController
- View-Sweep: Editor-Views ueber
EditorContextResolver, 4 Workflow-Partials, Users/Units/Events - JS-Sweep: Tenant-Selects entfernt, JS liest aktiven Tenant aus
root.dataset.tenantIdoderboot.data.tenant_active - Sicherheit:
EventController::resolveContextTenantIdakzeptiert kein$_GET['tenant_id']-Override mehr
34 Unit-Tests gruen, 20/20 Stage-Smoke-Views 200 OK.
Kritische Stellen (Vorsicht)
WorkflowController::resolveTeamTenantId— Drei Quellen (Boot, Override, Template-Fallback). Nicht 1:1 ersetzbar.step-config/*.php— Step-Tenant ist eine eigene Dimension, nicht Session-Tenant. Nicht anfassen.EventController::resolveContextTenantId— Heute GET-Override fuer Sysadmin-Debug. Entfaellt nach Migration; Support-Workflows aendern sich.- Host-Bindings, Audit-Log, System-Settings — tenant-agnostisch, Switcher ausgrauen wenn aktiv.
Zusammenhaenge
- IDEA-027 Globaler Tenant-Kontext — Backlog-Item
- Alexium Pages — Multi-Tenant-Plattform, erste Mandantin Provio
- Vier-Schichten-Modell — orthogonale Architekturentscheidung (rendering vs. context)
- ADR-077 Globaler Tenant-Kontext — kommt mit Sprint 3