Interne Arbeitsfläche

KDS OS

Nicht eingeloggt

Read-Access-Test · AP065

Schutzmatrix & Rollenfreigaben

AP065 – Schutzmatrix transparent prüfen. Keine neue Seite geschützt. Kein Route-Schutz, kein Redirect, keine middleware.ts. Schreibfunktionen global blockiert.

1 · Aktueller Kontext

Eingeloggt
Nein
DB-User gefunden
Nein
Workspace gefunden
Nein
Aktive Rolle
Keine

AP170-D – Restore and Archive UI

Archivierte Datensätze optional sichtbar gemacht. Restore Actions für alle Soft-Delete-Module. Wiederherstellen-UI für ADMIN/MANAGER. Standardlisten bleiben archivfrei.

Restore Server Actions

restoreCompany(): ✅ status → ACTIVE (company-actions.ts)
restoreContact(): ✅ status → ACTIVE (actions.ts)
restoreLead(): ✅ status → NEW (lead-actions.ts)
restoreProject(): ✅ status → PLANNING (projects/actions.ts)
restoreOffer(): ✅ status → DRAFT (offers/actions.ts)
restoreTask(): ✅ status → OPEN (tasks/actions.ts)
restoreNote(): ✅ deletedAt → null (notes/actions.ts)
Rollenprüfung: ✅ ADMIN/MANAGER – alle 7 Actions
workspaceId-Filter: ✅ updateMany mit workspaceId in allen Actions
Status-Guard: ✅ nur Datensätze mit status=ARCHIVED werden restored

Archiv-Toggle + UI-Komponenten

Archiv-Toggle: ✅ ?showArchived=1 auf /crm, /tasks, /projects, /offers
Nur für ADMIN/MANAGER: ✅ Toggle + Archiv-Sektion nur bei canWrite
RestoreButton: ✅ Shared Client Component (src/components/RestoreButton.tsx)
ArchivedCrmSection: ✅ Company + Contact + Lead in einer Sektion
ArchivedTaskList: ✅ src/features/tasks/components/ArchivedTaskList.tsx
ArchivedProjectList: ✅ src/features/projects/components/ArchivedProjectList.tsx
ArchivedOfferList: ✅ src/features/offers/components/ArchivedOfferList.tsx
Standardlisten: ✅ Unverändert – kein Regressions­risiko

Notes + Offene Punkte

Notes Action: ✅ restoreNote() implementiert
Notes Archiv-UI: ⚠️ Folge-AP – 5 Detailseiten zu ändern
DB-Migration: — Keine
Deployment: — Keines
pnpm build: ✅ Erfolgreich, 0 Fehler
Massenwiederherstellen: — Nicht implementiert (außer Scope)
DSGVO-Purge: — Nicht implementiert (eigener AP)

2 · Zugriff für aktuelle Rolle

Dashboard

Kein eingeloggter User gefunden.

Nicht eingeloggt

CRM

Kein eingeloggter User gefunden.

Nicht eingeloggt

Projekte

Kein eingeloggter User gefunden.

Nicht eingeloggt

Aufgaben

Kein eingeloggter User gefunden.

Nicht eingeloggt

Angebote

Kein eingeloggter User gefunden.

Nicht eingeloggt

Dateien

Kein eingeloggter User gefunden.

Nicht eingeloggt

Hosting

Kein eingeloggter User gefunden.

Nicht eingeloggt

Automatisierungen

Kein eingeloggter User gefunden.

Nicht eingeloggt

KI-Review

Kein eingeloggter User gefunden.

Nicht eingeloggt

Einstellungen

Kein eingeloggter User gefunden.

Nicht eingeloggt

Die Matrix zeigt, was bei der aktuellen Rolle lesbar wäre. Fachseiten werden durch AP065 nicht verändert oder gesperrt.

3 · Rollenvergleich Phase 1 – Soll-Matrix

Statische Auswertung der bestehenden READ_MATRIX aus permissions.ts – keine Änderung.

ModulAdminManagerMemberEinordnung
DashboardJaJaNeinKernmodul
CRMJaJaNeinKernmodul
ProjekteJaJaJaKernmodul
AufgabenJaJaJaKernmodul
AngeboteJaJaNeinKernmodul
DateienJaNeinNeinNebenmodul Admin-only
HostingJaNeinNeinNebenmodul Admin-only
AutomatisierungenJaNeinNeinNebenmodul Admin-only
KI-ReviewJaNeinNeinNebenmodul Admin-only
EinstellungenJaNeinNeinSettings / Admin

VIEWER und GUEST sind als Typen definiert, aber in Phase 1 nicht aktiv freigeschalten. Manager und Member haben dieselben Leserechte gemäß aktueller Matrix.

4 · Nebenmodule-Fokus

Diese Module sind aktuell ausschließlich für Admin lesbar.

Dateien

/files – geschützt seit AP061

Admin-onlyServerseitig geschützt

Hosting

/hosting – geschützt seit AP062

Admin-onlyServerseitig geschützt

Automatisierungen

/automations – geschützt seit AP064

Admin-onlyServerseitig geschützt

KI-Review

/ai-review – geschützt seit AP066

Admin-onlyServerseitig geschützt

Einstellungen

/settings – geschützt seit AP068

Admin-onlyServerseitig geschützt

Schreibfunktionen

canWriteModule() gibt für alle Rollen und alle Module false zurück. Schreibzugriff ist in Phase 1 global blockiert. Aktivierung folgt in einem späteren AP.

AP069 – Alle fünf Nebenmodule geschützt

Nach AP068 sind alle fünf Admin-only-Nebenmodule serverseitig geschützt. Konsolidierte Prüfung in Abschnitt 7 dieser Seite.

5 · AP066 – AP068 abgeschlossen

Alle fünf Admin-only-Nebenmodule sind serverseitig geschützt.

/ai-review (AP066), /settings (AP068) sowie /files (AP061), /hosting (AP062) und /automations (AP064) nutzen getReadPageAccess() und PageAccessBlock. Die Phase-1-Nebenmodul-Schutzstrecke ist abgeschlossen.

6 · Settings-Schutzstrategie – AP067

AP067 schützt keine neue Seite. Ziel: Strategie transparent vorbereiten für AP068.

/settingsOffenAdmin-only laut MatrixNicht geschützt in AP067
/settings/read-access-testOffenBewusst erreichbarBleibt offen
/settings/protected-testOffenBewusst erreichbarBleibt offen
/settings/auth-testOffenBewusst erreichbarBleibt offen
/settings/workspace-testOffenBewusst erreichbarBleibt offen
/settings/permissions-testOffenBewusst erreichbarBleibt offen

AP068-Vorschlag

AP068 kann ausschließlich src/app/(dashboard)/settings/page.tsx mit getReadPageAccess('settings') und PageAccessBlock schützen. Die Testseiten unter /settings/... bleiben dabei unverändert, solange keine Middleware oder Layout-Sperre eingeführt wird.

Sicherheitswarnung

Keine middleware.ts für Settings verwenden. Keine Layout-weite Sperre für /settings einführen, solange Testseiten benötigt werden. Nur settings/page.tsx anpassen – Unterseiten bleiben eigenständig offen.

7 · Konsolidierte Schutzprüfung – AP069

AP069 schützt keine neue Seite. Ziel: alle geschützten Nebenmodule, offene Testseiten und offene Kernseiten transparent zusammenführen.

7.1 · Geschützte Admin-only-Nebenmodule

RouteSchutzmethodeSchutz seitStatus
/filesgetReadPageAccess('files') + PageAccessBlockAP061Geschützt
/hostinggetReadPageAccess('hosting') + PageAccessBlockAP062Geschützt
/automationsgetReadPageAccess('automations') + PageAccessBlockAP064Geschützt
/ai-reviewgetReadPageAccess('aiReview') + PageAccessBlockAP066Geschützt
/settingsgetReadPageAccess('settings') + PageAccessBlockAP068Geschützt

7.2 · Bewusst offene Settings-Testseiten

RouteStatusHinweis
/settings/read-access-testOffenPrüf-/Diagnoseseite – nicht durch Middleware oder Layout-Sperre blockieren
/settings/protected-testOffenPrüf-/Diagnoseseite – nicht durch Middleware oder Layout-Sperre blockieren
/settings/auth-testOffenAuth-Prüfseite – nicht durch Middleware oder Layout-Sperre blockieren
/settings/workspace-testOffenWorkspace-Prüfseite – nicht durch Middleware oder Layout-Sperre blockieren
/settings/permissions-testOffenPermissions-Prüfseite – nicht durch Middleware oder Layout-Sperre blockieren

7.3 · Bewusst offene Kernseiten

RouteStatusHinweis
/dashboardOffenKernmodul – Phase 1 offen – später kontrolliert einzeln bewerten, kein breiter Route-Schutz
/crmOffenKernmodul – Phase 1 offen – später kontrolliert einzeln bewerten, kein breiter Route-Schutz
/projectsOffenKernmodul – Phase 1 offen – später kontrolliert einzeln bewerten, kein breiter Route-Schutz
/tasksOffenKernmodul – Phase 1 offen – später kontrolliert einzeln bewerten, kein breiter Route-Schutz
/offersOffenKernmodul – Phase 1 offen – später kontrolliert einzeln bewerten, kein breiter Route-Schutz

7.4 · Sicherheitsgrenzen AP069

Keine neue Seite geschützt
Keine middleware.ts
Keine Layout-Sperre
Kein Schutz von /settings/*
Kein breiter Route-Schutz
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert

Empfehlung für AP070

Nach AP069 sollte entschieden werden, ob als nächstes die Kernseiten-Schutzstrategie vorbereitet wird oder ob zuerst der Auth-/Workspace-/Rollenstatus in einer finalen Kontrollseite zusammengeführt wird. Dashboard / CRM / Projects / Tasks / Offers sollten nicht automatisch breit geschützt werden – jede Seite verdient eine eigene, bewusste Entscheidung.

8 · AP070 – Auth-/Workspace-/Rollen-Kontrollübersicht

Kompakte Gesamtübersicht: Identitätsstatus, Zugriffszusammenfassung, Modulrechte, Schutzstatus und AP071-Entscheidungsvorlage.

8.1 · Identitätsstatus

Auth-StatusNicht eingeloggt
DB-UserNicht gefunden
WorkspaceNicht gefunden
RolleKeine aktive Rolle

8.2 · Zugriffszusammenfassung

5

Geschützte Nebenmodule

5

Offene Settings-Testseiten

5

Offene Kernseiten

Global blockiert

Schreibrechte

Nicht vorhanden

middleware.ts

Nicht verwendet

Layout-Sperre

Nicht verwendet

Breiter Route-Schutz

8.3 · Modulrechte Phase 1

ModulRouteAdminMgrMbrSchutz
Dashboard/dashboardJaJaNeinOffen
CRM/crmJaJaNeinOffen
Projekte/projectsJaJaJaOffen
Aufgaben/tasksJaJaJaOffen
Angebote/offersJaJaNeinOffen
Dateien/filesJaNeinNeinAP061
Hosting/hostingJaNeinNeinAP062
Automatisierungen/automationsJaNeinNeinAP064
KI-Review/ai-reviewJaNeinNeinAP066
Einstellungen/settingsJaNeinNeinAP068

8.4 · Schutzstatus der echten Seiten

RouteModulKeyStatusSeitMethodeZugriff
/filesfilesGeschütztAP061getReadPageAccess + PageAccessBlockAdmin
/hostinghostingGeschütztAP062getReadPageAccess + PageAccessBlockAdmin
/automationsautomationsGeschütztAP064getReadPageAccess + PageAccessBlockAdmin
/ai-reviewaiReviewGeschütztAP066getReadPageAccess + PageAccessBlockAdmin
/settingssettingsGeschütztAP068getReadPageAccess + PageAccessBlockAdmin

8.5 · Bewusst offene Prüfseiten

/settings/read-access-testPrüf-/Diagnoseseite – nicht per middleware.ts oder Layout-Sperre blockieren
/settings/protected-testPrüf-/Diagnoseseite – nicht per middleware.ts oder Layout-Sperre blockieren
/settings/auth-testAuth-Prüfseite – nicht per middleware.ts oder Layout-Sperre blockieren
/settings/workspace-testWorkspace-Prüfseite – nicht per middleware.ts oder Layout-Sperre blockieren
/settings/permissions-testPermissions-Prüfseite – nicht per middleware.ts oder Layout-Sperre blockieren

8.6 · AP071-Entscheidungsvorlage

Option A – Kernseiten-Schutzstrategie

/dashboard, /crm, /projects, /tasks und /offers als nächste Kandidaten bewerten. Jede Seite einzeln entscheiden. Kein breiter Route-Schutz. Keine middleware.ts. Kein CRUD.

Option B – Kontrollseite weiter verbessern

Kompaktere Übersicht, bessere visuelle Ampeln, spätere Admin-Diagnose-Seite. Weiterhin ohne CRUD. Keine neue Seite schützen.

Empfehlung für AP071

Vor dem Schutz echter Kernseiten zuerst die Kernseiten-Schutzstrategie vorbereiten (Option A). Nicht direkt /dashboard oder /crm schützen – erst bewerten, welche Rolle wirklich abgeschlossen werden soll, und ob Manager/Member ausgeschlossen werden dürfen. Kein breiter Route-Schutz.

9 · AP071 – Kernseiten-Schutzstrategie

AP071 schützt keine Kernseite. Ziel: Bewertung und Reihenfolge für spätere Schutz-APs transparent vorbereiten. Jede Kernseite braucht eine bewusste Einzelentscheidung.

9.1 · Ziel AP071

  • Keine Kernseite wird in AP071 geschützt.
  • Es geht nur um Bewertung und Reihenfolge für spätere APs.
  • Kernseiten dürfen nicht automatisch breit geschützt werden.
  • Jede Kernseite braucht eine bewusste, explizit freigegebene Einzelentscheidung.
  • Kein breiter Route-Schutz. Keine middleware.ts. Keine Layout-Sperre.

9.2 · Kernseiten-Übersicht

RouteModulKeyStatusRollenfreigabeRisiko sofortigPriorität
/dashboarddashboardOffenAdmin / Manager / MemberHoch – zentrale EinstiegsseiteNiedrig–Mittel
/crmcrmOffenAdmin / Manager / MemberHoch – Liste + Detailseiten betroffenHoch (vorbereitet)
/projectsprojectsOffenAdmin / Manager / MemberMittel – echte ProjektübersichtMittel
/taskstasksOffenAdmin / Manager / MemberMittel – operativer BereichMittel
/offersoffersOffenAdmin / Manager / MemberMittel–Hoch – Angebotsdaten sensibelHoch

Alle Kernseiten sind aktuell bewusst offen. Kein Schutz in AP071 – nur Bewertung.

9.3 · Empfohlene Reihenfolge für spätere Schutz-APs

APVorgeschlagene Aufgabe
AP072CRM-Schutzstrategie vorbereiten – CRM-Liste und Detailseiten gemeinsam bewerten.
AP073Offers-Leseseite kontrolliert schützen oder zuerst Offers-Strategie prüfen.
AP074Projects-Leseseite kontrolliert schützen.
AP075Tasks-Leseseite kontrolliert schützen.
AP076Dashboard-Strategie vorbereiten oder Dashboard kontrolliert schützen.

Hinweis: Keine dieser Seiten wird in AP071 geschützt. Die Reihenfolge ist ein Vorschlag – keine Umsetzung. Jede AP benötigt explizite Freigabe durch Andre.

9.4 · Besondere Warnung: CRM

/crm ist nicht nur eine einzelne Seite

/crm – CRM-Liste
/crm/companies/[id] – Unternehmens-Detailseite
/crm/contacts/[id] – Kontakt-Detailseite
/crm/leads/[id] – Lead-Detailseite

CRM darf nicht nur oberflächlich geschützt werden, wenn Detailseiten offen bleiben.

Vor CRM-Schutz zuerst Detailseiten-Strategie klären.

Kein breiter Route-Schutz per Middleware.

Kein Layout-Zwangsschutz ohne bewusste Einzelentscheidung.

9.5 · Sicherheitsgrenzen AP071

Keine neue Seite geschützt
Keine Kernseite geschützt
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Keine Matrixänderung
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert

9.6 · AP072-Entscheidungsvorlage

Empfehlung: AP072 sollte nicht sofort /crm schützen, sondern zuerst die CRM-Schutzstrategie vorbereiten.

Grund:

CRM umfasst Liste und Detailseiten. Wenn nur /crm geschützt wird, bleiben Detailseiten eventuell direkt erreichbar.

CRM-Liste separat schützen?
CRM-Detailseiten einzeln schützen?
Gemeinsame Helper-Strategie?
Kein Middleware-Schutz?
Wie gehen Manager/Member mit CRM-Zugriff um?

Empfehlung für AP072

AP072 – CRM-Schutzstrategie vorbereiten: Bewertung der CRM-Liste und aller drei Detailseiten, bevor eine einzige Seite geschützt wird. Kein breiter Route-Schutz. Keine middleware.ts. Kein CRUD. Keine Server Actions. Kein Deployment.

10 · AP072 – CRM-Schutzstrategie

AP072 schützt keine CRM-Seite. Ziel: Bewertung aller vier CRM-Routen und Strategie für spätere Schutz-APs. CRM darf nicht nur oberflächlich geschützt werden.

10.1 · Ziel AP072

  • Keine CRM-Seite wird in AP072 geschützt.
  • Es geht nur um Bewertung und Strategie für spätere CRM-Schutz-APs.
  • CRM darf nicht nur oberflächlich geschützt werden – Liste und Detailseiten müssen gemeinsam betrachtet werden.
  • Jede spätere Schutzentscheidung braucht eine eigene explizite Freigabe durch Andre.
  • Keine middleware.ts, keine Layout-Sperre, kein breiter Route-Schutz, kein CRUD.

10.2 · CRM-Routenübersicht

RouteTypStatusRollenfreigabeRisikoEmpfehlung
/crmCRM-ListeOffenAdmin / Manager / MemberHoch – zentrale CRM-Einstiegsseite, verlinkt auf DetailseitenZuerst schützen, aber Detailseitenstrategie vorher klären
/crm/companies/[id]Unternehmens-DetailOffenAdmin / Manager / MemberHoch – potenziell sensible UnternehmensdatenGemeinsam mit /crm oder direkt danach
/crm/contacts/[id]Kontakt-DetailOffenAdmin / Manager / MemberSehr hoch – personenbezogene Kontaktdaten möglichSehr vorsichtig, nicht offen lassen wenn /crm geschützt
/crm/leads/[id]Lead-DetailOffenAdmin / Manager / MemberHoch – potenziell sensible Lead-/VertriebsdatenGemeinsam mit /crm oder direkt danach

Alle vier CRM-Routen sind aktuell bewusst offen. Kein Schutz in AP072 – nur Bewertung.

10.3 · Strategische Kernentscheidung

Option A – Einzelne APs je Route

CRM-Liste und alle Detailseiten in getrennten APs schützen.

Vorteil:

Sehr kontrolliert, geringer Änderungsumfang je AP.

Nachteil:

Kurzfristig können Zwischenstände entstehen, in denen nicht alles geschützt ist.

Option B – Gemeinsamer CRM-AP

CRM-Liste und alle Detailseiten in einem gemeinsamen AP schützen.

Vorteil:

Keine Lücke zwischen Liste und Detailseiten.

Nachteil:

Größerer Änderungsumfang, mehr Prüfaufwand.

Empfehlung: Option A mit sehr enger Reihenfolge

AP073/crm-Liste kontrolliert schützen
AP074/crm/companies/[id] kontrolliert schützen
AP075/crm/contacts/[id] kontrolliert schützen
AP076/crm/leads/[id] kontrolliert schützen

Wichtig:

Wenn /crm geschützt wird, müssen die Detailseiten unmittelbar danach folgen. Keine längere Phase, in der die Liste geschützt ist, Detailseiten aber offen bleiben.

10.4 · Rollenentscheidung CRM

CRM ist kein Admin-only-Modul – Manager und Member bleiben weiterhin zugelassen.

Nach aktueller Matrix ist crm für Admin / Manager / Member lesbar. CRM-Schutz bedeutet deshalb: Auth + Workspace + Rolle prüfen, aber Manager/Member weiter zulassen.

Nicht eingeloggtBlockiert
Kein DB-UserBlockiert
Kein WorkspaceBlockiert
Keine RolleBlockiert
Unbekannte RolleBlockiert
AdminZugriff
ManagerZugriff
MemberZugriff

Schreibfunktionen bleiben trotzdem global blockiert – unabhängig von der Rolle.

10.5 · Risiko: offene Direktlinks

Dynamische Detailseiten können direkt per URL aufgerufen werden

Schutz nur auf /crm reicht nicht aus – Detailseiten sind direkt erreichbar.
Jede Detailseite braucht eine eigene serverseitige Prüfung.
Keine reine UI-/Navigationslogik verwenden – serverseitig prüfen.
Keine Annahme: „wenn Liste geschützt ist, sind Details automatisch geschützt."
Kein breiter Middleware-Schutz auf /crm/* ohne bewusste Entscheidung.

10.6 · Empfohlene technische Umsetzung (für spätere APs)

RouteSchutzmuster
/crm
  • getReadPageAccess('crm')
  • PageAccessBlock
  • export const dynamic = 'force-dynamic'
  • Kein Redirect, kein CRUD
/crm/companies/[id]
  • getReadPageAccess('crm')
  • PageAccessBlock
  • export const dynamic = 'force-dynamic'
  • Bei blockiertem Zugriff keine Unternehmensdaten laden
  • Kein CRUD
/crm/contacts/[id]
  • getReadPageAccess('crm')
  • PageAccessBlock
  • export const dynamic = 'force-dynamic'
  • Bei blockiertem Zugriff keine Kontaktdaten laden
  • Kein CRUD
/crm/leads/[id]
  • getReadPageAccess('crm')
  • PageAccessBlock
  • export const dynamic = 'force-dynamic'
  • Bei blockiertem Zugriff keine Leaddetails laden
  • Kein CRUD

Diese Umsetzung erfolgt NICHT in AP072 – sie ist Orientierung für AP073–AP076.

10.7 · Sicherheitsgrenzen AP072

Keine neue Seite geschützt
Keine CRM-Seite geschützt
Keine CRM-Detailseite geschützt
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Keine Matrixänderung
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert

10.8 · AP073-Entscheidungsvorlage

Empfehlung: AP073 sollte /crm kontrolliert schützen – mit dem klaren Hinweis, dass AP074–AP076 unmittelbar danach die Detailseiten schützen.

Alternative:

Wenn Andre keine Zwischenlücke akzeptiert: AP073 als gemeinsamer CRM-Schutz-AP für Liste und alle drei Detailseiten planen.

Zu entscheiden vor AP073:

Einzel-AP je CRM-Route (AP073→AP074→AP075→AP076)?
Oder gemeinsamer CRM-Schutz-AP für alle vier Routen?
Wie viel Prüfaufwand ist je AP akzeptabel?
Soll CRM für Manager/Member weiterhin lesbar bleiben? Nach aktueller Matrix: ja.

Empfehlung für AP073

AP073 – /crm-Liste kontrolliert schützen: ausschließlich src/app/(dashboard)/crm/page.tsx mit getReadPageAccess('crm') und PageAccessBlock schützen – analog zum Muster AP061–AP068. Kein Middleware-Schutz. Kein CRUD. Kein Deployment. Freigabe durch Andre erforderlich.

11 · AP077 – CRM-Schutzblock konsolidiert

CRM vollständig geschützt

11.1 · Ziel

AP077 konsolidiert den CRM-Schutzblock. Nach AP073–AP076 sind alle vier CRM-Routen serverseitig kontrolliert geschützt. Diese Seite dokumentiert den abgeschlossenen Schutzstand und dient als Referenz für zukünftige Schutzblöcke.

11.2 · CRM-Schutzstatus Gesamtübersicht

RouteTypSchutz seitStatusDatenladungModulKeyRollen
/crmCRM-ListeAP073geschütztgetCrmOverviewData()'crm'Admin / Manager / Member
/crm/companies/[id]UnternehmensdetailAP074geschütztgetCompanyDetailData(id)'crm'Admin / Manager / Member
/crm/contacts/[id]KontaktdetailAP075geschütztgetContactDetailData(id)'crm'Admin / Manager / Member
/crm/leads/[id]LeaddetailAP076geschütztgetLeadDetailData(id)'crm'Admin / Manager / Member

11.3 · Zugriffserwartung CRM

Admin:Zugriff – alle CRM-Seiten lesbar
Manager:Zugriff – alle CRM-Seiten lesbar
Member:Zugriff – alle CRM-Seiten lesbar
Alle anderen Rollen:Blockiert via PageAccessBlock
Unauthentifiziert:Blockiert (unauthenticated)
Kein DB-User:Blockiert (no_db_user)

11.4 · Datenlade-Sicherheitsregel

Daten werden erst nach accessStatus === 'granted' geladen

Für alle vier CRM-Routen gilt: await params und die jeweilige Dataladefunktion werden ausschließlich nach erfolgreicher Zugangsprüfung aufgerufen. Bei blockiertem Zugriff werden keine Unternehmensdaten, Kontaktdaten, Leads, E-Mails, Telefonnummern oder Notizen geladen oder an den Client übertragen.

getReadPageAccess('crm') → blockiert → PageAccessBlock → return (keine Daten)
getReadPageAccess('crm') → granted → await params → Datenfunktion → render

11.5 · Offene Nachprüfung

Browsertests noch ausstehend

AP073–AP076: Browserprüfung mit echter CRM-ID wird nach DB-User-Mapping/Testdaten nachgezogen.
Zugriffstest mit blockierter Rolle (z. B. Viewer) für alle vier Routen ausstehend.
Bestätigung, dass bei blockiertem Zugriff kein Datenleck in Network-Tab sichtbar ist.

11.6 · Geschützte Seiten Gesamtstand

/settings/read-access-test

AP060 · settings

/settings/protected-test

AP059 · settings

/files

AP061 · files

/hosting

AP062 · hosting

/automations

AP064 · automations

/ai-review

AP066 · aiReview

/crm

AP073 · crm

/crm/companies/[id]

AP074 · crm

/crm/contacts/[id]

AP075 · crm

/crm/leads/[id]

AP076 · crm

10 Routen serverseitig geschützt · Stand AP077 (18.05.2026)

11.7 · Noch nicht serverseitig geschützte Seiten

/dashboard – Kernseite, AP071 vorbereitet, Schutz ausstehend
/projects – Kernseite, Schutz ausstehend
/tasks – Kernseite, Schutz ausstehend
/offers – Kernseite, Schutz ausstehend
/settings/* (weitere Unterseiten) – je nach Freigabe

11.8 · Sicherheitsgrenzen AP077

Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein CRUD, keine Server Actions
Kein Deployment
Keine echten Kundendaten
Keine Tokens, keine Rohsession
Keine Formulare, keine Schreibbuttons

Empfehlung für AP078

Nächster Schutzblock: Kernseiten /dashboard, /projects, /tasks, /offers – serverseitig kontrolliert schützen analog AP073–AP076. Jeweils getReadPageAccess(moduleKey) + PageAccessBlock. Freigabe durch Andre erforderlich.

12 · AP078 – Nächsten Kernbereich bewusst festlegen

Strategie-AP · kein Schutz

12.1 · Ziel von AP078

AP078 ist ein reiner Strategie-/Entscheidungs-AP

Keine neue Seite wird geschützt
Kein Kernbereich wird direkt umgebaut
AP078 entscheidet nur die nächste Richtung
Nach CRM wird der nächste sinnvolle Kernbereich bewusst festgelegt
Keine Middleware · Keine Layout-Sperre · Kein CRUD

12.2 · Ausgangslage nach CRM-Schutzblock

Geschützt

/crmAP073
/crm/companies/[id]AP074
/crm/contacts/[id]AP075
/crm/leads/[id]AP076
/filesAP061
/hostingAP062
/automationsAP064
/ai-reviewAP066
/settingsAP068

Konsolidiert

AP077 · CRM-Schutzblock
9 Fachrouten geschützt

Noch offen

/offers
/projects
/tasks
/dashboard
CRM-Nachtest / echte IDs

12.3 · Optionen für den nächsten Kernbereich

OptionNächster APRisikoBegründungEmpfehlung
Option A

AP079 – Offers-Schutzstrategie

/offers

HochAngebote können kaufmännisch sensible Daten, Preise und Kalkulationen enthalten. Erst Strategie, dann Schutz.★ Bevorzugt
Option B

AP079 – Projects-Schutzstrategie

/projects

MittelProjekte sind operative Arbeitsdaten, aber weniger kaufmännisch sensibel als Angebote.2. Priorität
Option C

AP079 – Tasks-Schutzstrategie

/tasks

MittelAufgaben sind operative Daten, weniger sensibel als Angebote und CRM.Nach Offers/Projects
Option D

AP079 – Dashboard-Strategie

/dashboard

HochDashboard ist zentrale Einstiegsseite und berührt viele Module – besser zuletzt bewerten.Später
Option E

AP079 – CRM-Nachtest / Testdaten

Qualität

KeinerEchte IDs, Rollen-Tests und DB-User-Mapping nachziehen. Kein neuer Schutz.Qualitätsblock

12.4 · Entscheidung AP078

Entscheidung: AP079 – Offers-Schutzstrategie vorbereiten

/offers ist der sensibelste noch offene Fachbereich
Angebote können kaufmännische Informationen, Preise und interne Kalkulationen enthalten
Vor direktem Schutz muss geklärt werden: Liste vs. Detailseiten vs. Downloads vs. Exporte
Kein direkter Schutz in AP078 – erst Strategie, dann kontrollierter Schutz
AP079 bewertet /offers und bereitet das Schutzmuster vor

12.5 · Vorgeschlagene Reihenfolge ab AP079

APZielSchutz
AP079Offers-Schutzstrategie vorbereiten – /offers bewerten, Datenrisiken klären, späteres Schutzmuster festlegenNein
AP080Offers-Leseseite kontrolliert schützen oder Offers-Detailstrategie vorbereiten – je nach AP079-ErgebnisOffen
AP081Projects-Schutzstrategie oder Projects-Leseseite serverseitig schützenOffen
AP082Tasks-Schutzstrategie oder Tasks-Leseseite serverseitig schützenOffen
AP083Dashboard-Strategie vorbereiten – zentrale Einstiegsseite bewusst bewertenOffen
QualitätsblockCRM-Nachtest · Testdaten · DB-User-Mapping – echte IDs, Rollen und Nutzbarkeit sauber testen

12.6 · Warum nicht direkt /offers schützen?

/offers soll nicht blind geschützt werden – diese Fragen müssen zuerst geklärt werden:

Gibt es nur /offers als Liste?
Gibt es Angebotsdetailseiten (/offers/[id])?
Gibt es PDF-Downloads oder Exportfunktionen?
Werden Angebotswerte oder Preise angezeigt?
Sind Kundendaten oder Kalkulationen sichtbar?
Welche Rollen sollen Angebote lesen dürfen?
Muss zwischen Lesen und Bearbeiten unterschieden werden?
Ist /offers später sensibler als CRM oder gleich zu behandeln?

12.7 · Rollenentscheidung Offers vorbereiten

Stand Phase-1-Matrix: offers ist für Admin / Manager / Member lesbar

Offers wäre damit voraussichtlich kein Admin-only-Modul
Lesen ≠ Schreiben – Schreibfunktionen bleiben global blockiert
Offers-Schutz bedeutet zunächst nur serverseitige Lesefreigabe
AP079 muss prüfen: Dürfen Manager/Member wirklich Angebote lesen?
Oder: Soll Offers sensibler behandelt werden als CRM?

Wichtig: In AP078 keine Matrix ändern.

12.8 · Sicherheitsgrenzen AP078

Keine neue Seite geschützt
Keine Offers-Seite geändert
Keine Projects-Seite geändert
Keine Tasks-Seite geändert
Keine Dashboard-Seite geändert
Keine CRM-Seite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten Angebotswerte

AP079-Entscheidungsvorlage · Empfehlung

AP079 – Offers-Schutzstrategie vorbereiten

Ziel: /offers analysieren und Datenrisiko bewerten
Prüfen: Detailseiten, PDFs, Downloads, Exporte, Angebotswerte
Rollenentscheidung vorbereiten (Manager/Member für Offers?)
Spätere Schutzreihenfolge festlegen
Keine Seite direkt schützen

Alternativen: AP079 Projects-Schutzstrategie · AP079 CRM-Nachtest/Testdaten

Empfohlene Entscheidung: Offers zuerst strategisch vorbereiten, CRM-Nachtest danach als Qualitätsblock einplanen. Freigabe durch Andre erforderlich.

13 · AP079 – Offers-Schutzstrategie

Strategie-AP · /offers nicht geschützt

13.1 · Ziel von AP079

AP079 ist ein reiner Strategie-/Analyse-AP – keine Seite wird geschützt

/offers wird nicht direkt geschützt
AP079 analysiert Datenrisiko, Rollenfrage und spätere Schutzlogik
Erst Strategie (AP079), dann kontrollierter Schutz (AP080+)
Keine Middleware · Keine Layout-Sperre · Kein CRUD
Ergebnis hilft Andre, AP080 bewusst freizugeben

13.2 · Ausgangslage nach AP078

Geschützt (9)

/files

/hosting

/automations

/ai-review

/settings

/crm

/crm/companies/[id]

/crm/contacts/[id]

/crm/leads/[id]

Noch offen

/offers

/projects

/tasks

/dashboard

Settings-Testseiten

Entscheidung AP078

/offers = nächster zu bewertender Kernbereich
Nicht blind schützen
AP079 klärt Struktur, Risiko und Rollen

13.3 · Offers-Risikoanalyse

Risiko / ThemaRisikostufeBegründungSchutzrelevanz
AngebotswerteHochPreise, Summen, Rabatte, kaufmännische EinschätzungSehr hoch
KundenzuordnungHochAngebote können direkt Kunden, Leads oder Projekten zugeordnet seinSehr hoch
Interne KalkulationSehr hochFalls Einkaufspreise, Margen, Stundenansätze oder interne Notizen sichtbar sindKritisch
PDF-DownloadHochDokumente könnten direkt abrufbar oder teilbar seinSehr hoch
ExportfunktionHochGrößere Datenmengen könnten gesammelt abfließenSehr hoch
Ansprechpartner / KundendatenHochPersonenbezogene oder unternehmensbezogene Daten möglichSehr hoch
LeistungsbeschreibungMittel–HochKann interne Leistungslogik, Projektumfang oder Beratungsansatz sichtbar machenHoch
Status / PipelineMittelZeigt Vertriebsstand, offene Angebote oder interne PrioritätenMittel–Hoch

13.4 · Strukturprüfung Offers

Vor AP080 muss geklärt werden:

Existiert nur src/app/(dashboard)/offers/page.tsx?
Gibt es Angebotsdetailseiten (/offers/[id])?
Gibt es dynamische Unterrouten für Angebote?
Gibt es PDF- oder Download-Routen?
Gibt es Exportfunktionen (Excel, CSV, PDF)?
Gibt es Links aus CRM, Projects oder Dashboard auf Angebote?
Werden Angebotswerte oder Preise angezeigt?
Werden Kundendaten in /offers angezeigt?
Werden interne Notizen oder Kalkulationen angezeigt?
Gibt es Datenladefunktionen, die erst nach Schutz laufen müssen?

Konsequenz

Wenn nur /offers/page.tsx existiert → AP080 kann Offers-Leseseite direkt schützen.
Wenn Detailseiten / Downloads / Exporte existieren → AP080 muss erst Detail-/Download-Strategie vorbereiten oder mehrere Folge-APs planen.

13.5 · Rollenentscheidung Offers

RolleAktuelle MatrixEmpfehlungHinweis
Admindarf offers lesenBeibehaltenVoller Zugriff auf alle Module
Managerdarf offers lesenPrüfenManager-Zugriff kann sinnvoll sein, falls Angebote operativ bearbeitet/bewertet werden
Memberdarf offers lesenKritisch prüfenMember-Zugriff auf Angebote könnte je nach Dateninhalt zu weit sein
Unbekannte Rollekein ZugriffBlockiertPageAccessBlock wie bei allen anderen Modulen
Unauthentifiziert / kein DB-User / kein Workspace / keine Rollekein ZugriffBlockiertVollständige Zugangskette schlägt vor Datenzugriff fehl

Wichtig

AP079 ändert die Matrix nicht.
AP079 markiert: Offers ist sensibler als CRM und muss vor Schutz rollenfachlich bewertet werden.

13.6 · Empfohlenes Schutzmuster für AP080

Wenn AP080 /offers als reine Liste schützt:

Datei: src/app/(dashboard)/offers/page.tsx
export const dynamic = 'force-dynamic'
getReadPageAccess('offers') serverseitig aufrufen
bei accessStatus !== 'granted' → PageAccessBlock rendern (sofort return)
bei blockiertem Zugriff: keine Angebotsdaten laden oder anzeigen
Datenladefunktion erst nach accessStatus === 'granted' ausführen
bei granted: Offers-Inhalt + AP080-Banner anzeigen
kein Redirect · kein notFound() · kein CRUD · keine Server Actions
keine Middleware · keine Layout-Sperre

13.7 · Offene Entscheidung vor AP080

Vor AP080 muss Andre entscheiden:

Option A

AP080 schützt nur /offers als Listen-/Übersichtsseite.

Voraussetzung: keine offenen Detail-/Download-/Export-Routen oder diese werden separat geplant.

Option B

AP080 bereitet zuerst Detail-/Download-/Export-Strategie vor.

Voraussetzung: Angebotsdetailseiten, PDFs, Downloads, Exporte oder sensible Unterrouten existieren.

Option C

AP080 schützt /offers und plant Folge-APs für Details/Downloads.

Voraussetzung: Unterrouten existieren, aber Liste soll trotzdem sofort geschützt werden.

Empfehlung: Erst Struktur prüfen. Wenn nur /offers/page.tsx betroffen → Option A. Wenn mehr existiert → Option B.

13.8 · Vergleich Offers vs. CRM

KriteriumCRM (abgeschlossen)Offers (offen)
DateninhaltKunden-, Kontakt- und Lead-DatenKaufmännische Werte, interne Kalkulation, Preise
RollenAdmin / Manager / MemberAktuell Admin / Manager / Member – kritisch zu prüfen
SchutzstandVollständig abgeschlossen (AP073–AP076)Noch offen – AP079 bereitet Strategie vor
DetailseitenAlle Detailseiten geschütztStruktur noch zu klären
SensitivitätHoch (Personendaten)Potenziell höher (Preise, Kalkulationen)

Fazit

Offers darf nicht automatisch exakt wie CRM behandelt werden. Leserechte können gleich bleiben, müssen aber bewusst bestätigt werden. Besonders Member-Zugriff auf Angebote ist kritisch zu prüfen.

13.9 · Sicherheitsgrenzen AP079

Keine neue Seite geschützt
/offers nicht geändert
/projects nicht geändert
/tasks nicht geändert
/dashboard nicht geändert
Keine CRM-Seite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein Schutz auf /offers/*
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Angebotswerte

AP080-Entscheidungsvorlage

AP080 – Offers-Leseseite kontrolliert schützen

Sofern Strukturprüfung zeigt: aktuell nur /offers/page.tsx betroffen.

Wenn Detailseiten/Downloads/Exporte existieren: AP080 – Offers-Detail-/Download-Strategie vorbereiten.

AP080 darf erst nach Freigabe durch Andre starten. Weiterhin gilt: keine middleware.ts · keine Layout-Sperre · kein CRUD · keine Server Actions · kein Deployment · keine echten Angebotswerte in Dokumentation.

Abschnitt 14 · AP080

Offers-Strukturprüfung und Schutzentscheidung

Strukturprüfungs-AP · /offers nicht geändert

14.1 · Ziel von AP080

Tatsächliche Offers-Routenstruktur im Code prüfen (reine Leseanalyse)
Feststellen ob nur /offers/page.tsx existiert oder ob Detailseiten, Download-/PDF-/Export-Routen vorhanden sind
Datenrisiken im Code bewerten: Angebotswerte, Kundenzuordnung, Kalkulation, PDF, Exportfunktion
Verlinkungen auf /offers aus CRM, Projects, Dashboard erkennen
Klare Entscheidung für AP081 vorbereiten
Keine neue Seite schützen · /offers nicht ändern · keine Middleware · keine Layout-Sperre · kein CRUD

14.2 · Geprüfte Bereiche

BereichZweckErgebnis
src/app/(dashboard)/offersOffers-RoutenstrukturNur /offers/page.tsx gefunden – keine Unterordner, keine [id]-Route, keine Download-/Export-Route
src/app API-/Route-DateienOffers-API, Downloads, PDF, ExportKeine offers-spezifischen route.ts/route.tsx Dateien gefunden – einzige API-Route ist auth/[...nextauth]
src/features/offersOffers-Komponenten, Datenfunktionen7 Komponenten + mock-data.ts + status.ts + types.ts gefunden; Datenladung über src/server/offers/queries.ts
src/server/offers/queries.tsServer-seitige DatenbankabfragegetOfferOverviewData() lädt Angebote aus DB: title, company.name, lead.title, valueRange, riskLevel, status, notes
Querverlinkungen CRM / DashboardLinks auf /offers aus anderen ModulenRelatedOffersList in CRM-Company-Detail (geschützt); Navigation /offers; Dashboard-Mock /offers – kein Link aus Projects/Tasks

14.3 · Offers-Struktur Ergebnis

✔ Variante A bestätigt – nur /offers/page.tsx ohne Detail-/Download-/Export-Routen

✔ Nur src/app/(dashboard)/offers/page.tsx gefunden

✔ Keine dynamischen Offers-Detailrouten (/offers/[id]) vorhanden

✔ Keine Offers-Download-/PDF-/Export-Routen gefunden

✔ Keine Offers-API-Routen (route.ts/route.tsx) vorhanden

⚠ Wertbereich (valueRange) und Kundenzuordnung im Code sichtbar – Schutz daher wichtig

→ Empfehlung: AP081 kann /offers als Leseseite kontrolliert schützen

Bewertungslogik: Variante B (Detailseiten vorhanden) und Variante C (Downloads/Exports vorhanden) treffen nicht zu. Da ausschließlich die Übersichtsseite ohne Unterrouten existiert, gilt Variante A – isolierter Schutz der Listenseite ist sinnvoll und vollständig.

14.4 · Offers-Datenrisiko Ergebnis

RisikofeldIm Code sichtbar?Hinweis
AngebotswerteJa – Wertbereicheoffer.valueRange wird in OfferList gerendert (z. B. "7.500–15.000 €") – keine Einzelbeträge, aber Größenordnung sichtbar
KundenzuordnungJacustomerLabel = company.name ?? lead.title in OfferList und OfferContextCard sichtbar
Ansprechpartner / KundendatenTeilweiseUnternehmensname und Lead-Titel vorhanden; kein direkter Kontaktname, keine E-Mail, keine Telefonnummer
Interne KalkulationNeinKeine Margen, keine Stundenansätze, keine Einkaufspreise im Code gefunden – nur Wertbereiche
PDF / Download / ExportNeinKeine Download-/Export-Route vorhanden; "Demo-Angebot-PDF" nur Text in mock-data.ts Context-Feld – kein echter Download
Datenladefunktion vorhandenJagetOfferOverviewData() in src/server/offers/queries.ts – Prisma-Query mit db.offer.findMany(), inkl. company, lead, project, creator

14.5 · Rollenentscheidung Offers

RolleAktuelle MatrixBewertung AP080
AdmincanRead offers = trueZugriff beibehalten – Admin soll alle Daten sehen können
ManagercanRead offers = trueZugriff wahrscheinlich sinnvoll – Manager braucht Angebotsstatus für Projektplanung, bestätigen
MembercanRead offers = trueKritisch prüfen – Wertbereiche und Kundenzuordnung sichtbar; abhängig davon ob Members interne Kalkulationslogik sehen sollen

AP080 ändert keine Matrix. AP080 bereitet nur die Entscheidung für AP081 vor.

14.6 · Empfohlene Entscheidung für AP081

★ Empfehlung: AP081 – Offers-Leseseite kontrolliert schützen

Da Strukturprüfung Variante A bestätigt (nur /offers/page.tsx, keine Detail-/Download-/Export-Routen), kann AP081 /offers als Leseseite direkt schützen.

Datei: src/app/(dashboard)/offers/page.tsx

export const dynamic = 'force-dynamic'

const access = await getReadPageAccess('offers')

if (access.status !== 'granted') return <PageAccessBlock access={access} />

const data = await getOfferOverviewData()

Admin / Manager / Member – nach aktueller Matrix (Member kritisch prüfen)

Kein CRUD · keine Middleware · keine Layout-Sperre

Alternativ (falls Detail-/Export-Routen noch hinzukommen): AP081 – Offers-Detail-/Download-/Export-Strategie vorbereiten. Diese Alternative ist aktuell nicht notwendig – Strukturprüfung ergab keine solchen Routen.

14.7 · Sicherheitsgrenzen AP080

Keine neue Seite geschützt
/offers nicht geändert
/projects nicht geändert
/tasks nicht geändert
/dashboard nicht geändert
Keine CRM-Seite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein Schutz auf /offers/*
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten Kundendaten
Keine echten Angebotswerte
Keine internen Kalkulationen

14.8 · AP081-Entscheidungsvorlage

AP081 darf erst nach ausdrücklicher Freigabe durch Andre starten.

Empfohlene Variante (basierend auf AP080-Strukturprüfung):

★ AP081 – Offers-Leseseite kontrolliert schützen

Grund: Strukturprüfung bestätigt Variante A – nur /offers/page.tsx ohne Detail-/Download-/Export-Routen. Datenladefunktion getOfferOverviewData() vorhanden. Schutzmuster aus AP073–AP076 (CRM) direkt übertragbar.

Alternativvariante (falls neue Routen hinzukommen): AP081 – Offers-Detail-/Download-/Export-Strategie vorbereiten.

AP080 abgeschlossen · Strukturprüfung ergibt Variante A · /offers nur als Listenseite · Kein CRUD · keine neue Seite geschützt · keine middleware.ts · keine Layout-Sperre · kein breiter Route-Schutz · keine echten Angebotswerte · keine echten Kundendaten.

AP081 abgeschlossen · /offers ist kontrolliert serverseitig geschützt · getReadPageAccess('offers') + PageAccessBlock + Datenladung erst nach granted

Abschnitt 15 · AP082

Projects-Strukturprüfung und Schutzentscheidung

Strukturprüfungs-AP · /projects nicht geändert

15.1 · Ziel von AP082

Tatsächliche Projects-Routenstruktur im Code prüfen (reine Leseanalyse)
Feststellen ob nur /projects/page.tsx existiert oder ob Detailseiten, Unterrouten, Download-/Export-Routen vorhanden sind
Datenrisiken bewerten: Projektnamen, Kundenzuordnung, CRM-/Lead-/Angebotsbezüge, Verantwortliche, Budget, Status
Verlinkungen auf /projects aus CRM, Offers, Tasks, Dashboard erkennen
Klare Entscheidung für AP083 vorbereiten
Keine neue Seite schützen · /projects nicht ändern · keine Middleware · keine Layout-Sperre · kein CRUD

15.2 · Geprüfte Bereiche

BereichZweckErgebnis
src/app/(dashboard)/projectsProjects-RoutenstrukturNur /projects/page.tsx gefunden – keine Unterordner, keine [id]-Route, keine Download-/Export-Route
src/app API-/Route-DateienProjects-API, Downloads, PDF, ExportKeine projects-spezifischen route.ts/route.tsx – einzige API-Route ist auth/[...nextauth]
src/features/projectsProjects-Komponenten, Datenfunktionen7 Komponenten + mock-data.ts + status.ts + types.ts; Datenladung über src/server/projects/queries.ts
src/server/projects/queries.tsServer-seitige DatenbankabfragegetProjectOverviewData() lädt: title, company.name, manager.name/title, lead.title, _count.tasks, _count.offers, status, phase, health, budgetState
Querverlinkungen CRM / DashboardLinks auf /projects aus anderen ModulenRelatedProjectsList in CRM-Company-Detail (bereits geschützt); Navigation /projects; Dashboard-Mock /projects – kein Link aus Tasks/Offers

15.3 · Projects-Struktur Ergebnis

✔ Variante A bestätigt – nur /projects/page.tsx ohne Detail-/Download-/Export-Routen

✔ Nur src/app/(dashboard)/projects/page.tsx gefunden

✔ Keine dynamischen Projects-Detailrouten (/projects/[id]) vorhanden

✔ Keine Projects-Download-/PDF-/Export-Routen gefunden

✔ Keine Projects-API-Routen (route.ts/route.tsx) vorhanden

⚠ Projektnamen, Kundenzuordnung, Manager, Lead-/Angebotsbezüge und Budgetstatus im Code sichtbar – Schutz wichtig

→ Empfehlung: AP083 kann /projects als Leseseite kontrolliert schützen

Bewertungslogik: Variante B (Detailseiten) und Variante C (Downloads/Exports) treffen nicht zu. Da ausschließlich die Übersichtsseite ohne Unterrouten existiert, gilt Variante A – isolierter Schutz der Listenseite ist sinnvoll und vollständig.

15.4 · Projects-Datenrisiko Ergebnis

RisikofeldIm Code sichtbar?Hinweis
ProjektnamenJaproject.title wird in ProjectList und ProjectContextCard gerendert
KundenzuordnungJacontext.customer = company.name ?? "Intern" in ProjectContextCard sichtbar
CRM- / Lead-BezugJacontext.lead = lead.title in ProjectContextCard; company.name + company.type via DB-Include
AngebotsbezugJa (Anzahl)context.offer = "_count.offers Angebot(e)" – Anzahl verknüpfter Angebote sichtbar
AufgabenbezugJa (Anzahl)context.tasks = "_count.tasks Aufgabe(n)" – Anzahl verknüpfter Tasks sichtbar
Status / FortschrittJastatus, phase, health, priority und budgetState in ProjectList sichtbar
Deadlines / VerantwortlicheJaowner.name + owner.role (manager.name/title aus DB) in ProjectList sichtbar – kein Datum, aber Manager-Name
Interne Notizen / sensible InhalteUnklarproject.description sichtbar (aus DB p.description) – Inhalt abhängig von Seed-Daten
PDF / Download / ExportNeinKein Download-/Export-Route-Handler, kein Blob, kein Export-Mechanismus gefunden
Datenladefunktion vorhandenJagetProjectOverviewData() in src/server/projects/queries.ts – db.project.findMany() mit company, manager, lead, _count

15.5 · Rollenentscheidung Projects

RolleAktuelle MatrixBewertung AP082
AdmincanRead projects = trueZugriff beibehalten – Admin soll alle Projektdaten sehen können
ManagercanRead projects = trueZugriff wahrscheinlich sinnvoll – Manager braucht Projektübersicht operativ; bestätigen
MembercanRead projects = truePrüfen – Kundenzuordnung, Manager-Name, Lead-/Angebotsbezüge und Budgetstatus sichtbar; abhängig von operativem Bedarf

AP082 ändert keine Matrix. AP082 bereitet nur die Entscheidung für AP083 vor.

15.6 · Empfohlene Entscheidung für AP083

★ Empfehlung: AP083 – Projects-Leseseite kontrolliert schützen

Da Strukturprüfung Variante A bestätigt (nur /projects/page.tsx, keine Detail-/Download-/Export-Routen), kann AP083 /projects als Leseseite direkt schützen.

Datei: src/app/(dashboard)/projects/page.tsx

export const dynamic = 'force-dynamic'

const access = await getReadPageAccess('projects')

if (access.status !== 'granted') return <PageAccessBlock access={access} />

const data = await getProjectOverviewData()

Admin / Manager / Member – nach aktueller Matrix

Kein CRUD · keine Middleware · keine Layout-Sperre

15.7 · Sicherheitsgrenzen AP082

Keine neue Seite geschützt
/projects nicht geändert
/tasks nicht geändert
/dashboard nicht geändert
/offers nicht geändert
Keine CRM-Seite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein Schutz auf /projects/*
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten Kundendaten
Keine echten Projektdaten
Keine echten Angebotswerte

15.8 · AP083-Entscheidungsvorlage

AP083 darf erst nach ausdrücklicher Freigabe durch Andre starten.

Empfohlene Variante (basierend auf AP082-Strukturprüfung):

★ AP083 – Projects-Leseseite kontrolliert schützen

Grund: Strukturprüfung bestätigt Variante A – nur /projects/page.tsx ohne Detail-/Download-/Export-Routen. Datenladefunktion getProjectOverviewData() vorhanden. Schutzmuster aus AP073–AP081 direkt übertragbar.

Alternativvariante (falls neue Routen hinzukommen): AP083 – Projects-Detail-/Unterseiten-Strategie vorbereiten.

AP082 abgeschlossen · Strukturprüfung ergibt Variante A · /projects nur als Listenseite · Kein CRUD · keine neue Seite geschützt · keine middleware.ts · keine Layout-Sperre · kein breiter Route-Schutz · keine echten Projektdaten · keine echten Kundendaten.

AP083 abgeschlossen · /projects serverseitig kontrolliert geschütztgetReadPageAccess('projects')

Abschnitt 16 · AP084

AP084 – Tasks-Strukturprüfung und Schutzentscheidung

Reine Analyse. Keine neue Seite geschützt. /tasks nicht geändert. Keine middleware.ts. Keine Layout-Sperre. Kein CRUD.

16.1 · Ziel von AP084

  • Tasks-Codestruktur und Routen read-only analysieren
  • Feststellen ob nur /tasks/page.tsx oder auch Detail-/Unterrouten existieren
  • Datenladefunktionen und sichtbare Felder dokumentieren
  • Erkannte Datenrisiken bewerten
  • Rollenfrage für /tasks klären
  • Klare Entscheidung für AP085 vorbereiten
  • Keine neue Seite schützen – nur Analyse
  • Keine Berechtigungsmatrix ändern

16.2 · Geprüfte Codebereiche

BereichTypErgebnis
src/app/(dashboard)/tasks/page.tsxPage (Route)Einzige Tasks-Route. Kein [id], keine Unterordner. Variante A bestätigt.
src/app/api/API-RoutenNur auth/[...nextauth] vorhanden. Keine Tasks-API-Route, kein PDF, kein Export.
src/features/tasks/Feature-Komponenten7 Komponenten + mock-data.ts + types.ts + status.ts. Keine API-Route, kein Download.
src/server/tasks/queries.tsServer-QuerygetTaskOverviewData() mit Prisma: project.title, assignee.name/title, task.title/description/status/priority/dueDate.
src/features/dashboard/mock-data.tsDashboard-QuerverbindungDashboard enthält Link route: "/tasks". Kein CRM-Link auf Tasks.
src/features/crm/CRM-QuerverbindungKein Querverweis auf /tasks in CRM-Komponenten gefunden.

16.3 · Tasks-Struktur Ergebnis

✔ Variante A bestätigt: Nur /tasks/page.tsx vorhanden

  • src/app/(dashboard)/tasks/page.tsx – einzige Tasks-Route
  • Keine /tasks/[id] Detailseite
  • Keine /tasks/[id]/... Unterrouten
  • Keine Tasks-API-Routen unter src/app/api/
  • Keine PDF-/Download-/Export-Routen
  • Keine Tasks-Server-Actions
  • → Empfehlung: AP085 kann /tasks als Leseseite kontrolliert schützen

16.4 · Tasks-Datenrisiko Ergebnis

FeldSichtbarHinweis
task.titleJah3 in TaskList. Aufgabentitel ohne Schutz öffentlich sichtbar.
task.descriptionJap-Text in TaskList + importantReason. Freitext – kann sensible Inhalte enthalten.
task.status / priorityJaTaskStatusBadge + Prioritäts-Badge. Status und Priorität sichtbar.
task.owner.name / .roleJa„Verantwortlich"-Block in TaskList. Assignee-Name und -Rolle sichtbar.
task.context.label / .referenceJaProjektbezug via project.title sichtbar. „Kontext"-Block in TaskList.
task.dueLabel / dueStateJa„Fälligkeit"-Block in TaskList. Deadline-Ableitung sichtbar (Überfällig, Heute, Diese Woche).
task.nextActionJaEnthält task.title eingebettet. „Nächste Aktion"-Block in TaskList.
CRM / company.name / lead.titleNeinKeine directen CRM-Felder in getTaskOverviewData(). Nur indirekter Projektbezug.
AngebotsbezugNeinKein offer-Feld in Tasks-Query vorhanden.
PDF / Download / ExportNeinKeine Download-, PDF- oder Export-Route vorhanden.
getTaskOverviewData()Jasrc/server/tasks/queries.ts vorhanden. Direkt in tasks/page.tsx ohne Zugangsprüfung aufgerufen.

16.5 · Rollenentscheidung Tasks

Aktuelle Matrix Phase 1

  • Admin: canRead tasks = true
  • Manager: canRead tasks = true
  • Member: canRead tasks = true (fachlich beobachten)

Bewertung

  • Admin: Zugriff beibehalten
  • Manager: Zugriff wahrscheinlich sinnvoll – Aufgaben und Verantwortlichkeiten gehören zum Arbeitsalltag
  • Member: Prüfen – task.description, task.owner.name/role und Projektbezug für Members relevant?

AP084 ändert keine Matrix. AP084 bereitet nur die Entscheidung für AP085 vor.

16.6 · Empfohlene Entscheidung für AP085

★ Empfehlung: AP085 – Tasks-Leseseite kontrolliert schützen

Strukturprüfung ergibt Variante A: nur /tasks/page.tsx ohne Detail-/Download-/Export-Routen. AP085 kann /tasks als Leseseite direkt schützen.

  • – Datei: src/app/(dashboard)/tasks/page.tsx
  • – export const dynamic = 'force-dynamic'
  • – getReadPageAccess('tasks') serverseitig
  • – PageAccessBlock (AP063)
  • – getTaskOverviewData() erst nach accessStatus === 'granted'
  • – Admin / Manager / Member nach aktueller Matrix
  • – Kein CRUD · keine Middleware · keine Layout-Sperre

16.7 · Sicherheitsgrenzen AP084

  • Keine neue Seite geschützt
  • /tasks nicht geändert
  • /dashboard nicht geändert
  • /projects nicht geändert
  • /offers nicht geändert
  • Keine CRM-Seite geändert
  • Keine Schutzlogik geändert
  • Keine Berechtigungsmatrix geändert
  • Keine middleware.ts
  • Keine Layout-Sperre
  • Kein breiter Route-Schutz
  • Kein Schutz auf /tasks/*
  • Kein CRUD
  • Keine API-Routen
  • Keine Server Actions
  • Kein Deployment
  • Keine echten Credentials
  • Keine echten Aufgaben-/Taskdaten

16.8 · AP085-Entscheidungsvorlage

AP085 darf erst nach ausdrücklicher Freigabe durch Andre starten.

Empfohlene Variante (basierend auf AP084-Strukturprüfung):

★ AP085 – Tasks-Leseseite kontrolliert schützen

Struktur eindeutig: nur /tasks/page.tsx. getReadPageAccess('tasks') + PageAccessBlock + Datenladung erst nach granted. Muster aus AP081 (/offers) und AP083 (/projects) direkt übertragbar.

Alternative (nicht empfohlen, da Variante A bestätigt):

AP085 – Tasks-Detail-/Unterseiten-Strategie vorbereiten (entfällt, da keine Detailseiten vorhanden).

AP084 abgeschlossen · Strukturprüfung ergibt Variante A · /tasks nur als Listenseite · Kein CRUD · keine neue Seite geschützt · keine middleware.ts · keine Layout-Sperre · kein breiter Route-Schutz · keine echten Aufgaben-/Taskdaten · keine echten Kundendaten.

AP085 abgeschlossen · /tasks serverseitig kontrolliert geschütztgetReadPageAccess('tasks')

Abschnitt 17 · AP086

Dashboard-Schutzstrategie vorbereiten

AP086 ist ein reiner Analyse-AP. Es wird keine neue Seite geschützt. /dashboard wird nicht geändert. AP086 prüft nur Struktur, sichtbare Daten, Links und bereitet die Entscheidung für AP087 vor. Keine Middleware. Keine Layout-Sperre. Kein CRUD.

Abschnitt 17.1

Ausgangslage nach AP085

AP085 hat /tasks als letzte Kernleseseite geschützt. /dashboard ist die einzige bewusst offene Kernseite.

Geschützte Routen

  • /files
  • /hosting
  • /automations
  • /ai-review
  • /settings
  • /crm
  • /crm/companies/[id]
  • /crm/contacts/[id]
  • /crm/leads/[id]
  • /offers
  • /projects
  • /tasks

Bewusst offen

  • /dashboard (Kernseite – AP086 analysiert)
  • /settings/read-access-test (Testseite)

Abschnitt 17.2

Geprüfte Bereiche

Reine Leseanalyse – kein Code wurde geändert.

BereichZweckErgebnis
src/app/(dashboard)/dashboardDashboard-RoutenstrukturNur page.tsx vorhanden – keine Unterrouten, keine API-/Route-Handler
src/app API-/Route-DateienMögliche Dashboard-API oder Route-HandlerKeine dashboard-spezifischen route.ts/route.tsx gefunden
src/features/dashboardDashboard-Komponenten und DatenfunktionenDashboardOverview, DashboardStatCard, DashboardPriorityCard, DashboardActivityList, DashboardModuleCard, DashboardHealthBoard, DashboardContextCard, mock-data.ts, status.ts, types.ts
src/server/dashboard/queries.tsDatenladefunktiongetDashboardOverviewData() vorhanden – aggregiert Daten aus companies, contacts, leads, projects, tasks, offers
Querverlinkungen aus DashboardLinks auf geschützte Module erkennenModulrouten (/crm, /tasks, /projects, /offers, /files, /hosting, /automations, /ai-review, /settings) als Text-Badges in DashboardModuleCard – Sidebar-Navigation separat

Abschnitt 17.3

Dashboard-Struktur Ergebnis

Variante B bestätigt

Dashboard zeigt aggregierte, modulübergreifende Daten aus CRM, Projekte, Aufgaben und Angeboten. getDashboardOverviewData() wird aktuell ohne vorherige Auth-Prüfung aufgerufen.

  • Nur src/app/(dashboard)/dashboard/page.tsx – keine Dashboard-Unterrouten
  • Keine Dashboard-Download-/PDF-/Export-Routen
  • Keine Dashboard-API-Routen
  • Aggregierte Counts (companies, contacts, leads, projects, tasks, offers) sichtbar
  • Priority-Titel aus Leads, Projekten, Aufgaben und Angeboten (HIGH/CRITICAL) sichtbar
  • Activity-Titel (aktuellste Datensätze aus allen 4 Modulen) sichtbar
Mögliche Bewertungslogik: Variante A (nur Navigation/Übersicht) – trifft nicht zu. Variante B (aggregierte Kennzahlen) – bestätigt. Variante C (direkte sensible Fachdaten) – teilweise: Priority-Titel aus realen DB-Einträgen.

Abschnitt 17.4

Dashboard-Datenrisiko Ergebnis

Strukturelle Bewertung auf Basis der Codeanalyse – keine echten Werte.

DatenkategorieSichtbar?Hinweis
Modul-Links sichtbar?JaRoutenpfade (/crm, /tasks, /projects, /offers …) als Text-Badges in DashboardModuleCard
CRM-Daten sichtbar?JaAggregierter CRM-Count (companies+contacts+leads), Priority-Titel aus Leads (HIGH/CRITICAL), Activity-Titel aktuellster Lead
Offer-/Angebotsdaten sichtbar?JaAngebots-Count, Priority-Titel aus Offers (HIGH/CRITICAL, offen), Activity-Titel aktuellstes Angebot
Project-/Projektdaten sichtbar?JaProjekte-Count, Priority-Titel aus Projekten (ACTIVE/ON_HOLD), Activity-Titel aktuellstes Projekt
Task-/Aufgabendaten sichtbar?JaAufgaben-Count, Priority-Titel aus Tasks (HIGH/CRITICAL, offen), Activity-Titel aktuellste Aufgabe
Aggregierte Kennzahlen sichtbar?JaDashboardStatCard: CRM-Gesamtcount, Projekte-, Aufgaben-, Angebots-Count aus Neon Dev
Status-/Prioritäts-/Fälligkeitsdaten sichtbar?JaPriority-Labels (critical/high/medium/low), Modul-Health-Status in DashboardPriorityCard und DashboardModuleCard
Kunden-/Lead-/Kontaktbezug sichtbar?JaLead-Titel (HIGH/CRITICAL) in DashboardPriorityCard, aktuellster Lead-Titel in DashboardActivityList
Datenladefunktion vorhanden?JagetDashboardOverviewData() in src/server/dashboard/queries.ts – aktuell ohne Auth-Prüfung aufgerufen
PDF/Download/Export sichtbar?NeinKeine PDF-/Download-/Export-Funktionen im Dashboard-Code gefunden

Abschnitt 17.5

Rollenentscheidung Dashboard

AP086 ändert keine Matrix. Die folgende Bewertung bereitet nur die Entscheidung für AP087 vor.

RollecanRead dashboard (Matrix)Bewertung AP086
AdmintrueZugriff beibehalten – Admin sieht alle aggregierten Daten
ManagertrueZugriff beibehalten – Dashboard-Übersicht für Manager sinnvoll
MembertrueKritisch prüfen: Dashboard zeigt aggregierte CRM-/Lead-Daten und Prioritäts-Titel aus allen Modulen – Member-Zugriff fachlich beobachten

Abschnitt 17.6

Entscheidungsoptionen für AP087

Option A – Dashboard offen lassen und bewusst dokumentieren

Voraussetzung: Dashboard zeigt nur Navigation, neutrale Einstiegsinformationen oder unkritische Übersicht.Trifft hier nicht zu – Dashboard zeigt aggregierte Counts und Prioritäts-Titel aus CRM, Projekten, Aufgaben und Angeboten.

Option B – Dashboard-Leseseite kontrolliert schützen ✓ Empfohlen

Voraussetzung: Dashboard zeigt aggregierte Kennzahlen, fachliche Statusdaten, Modulzusammenfassungen oder Prioritäts-Titel mit sensiblem Kontext. Bestätigt durch Codeanalyse.

Option C – Dashboard-Struktur erst weiter ausbauen und dann schützen

Voraussetzung: Dashboard ist aktuell nur Platzhalter, soll aber bald produktive Kennzahlen bekommen. Trifft nicht zu – Dashboard ist bereits dynamisch aktiv mit getDashboardOverviewData().

Abschnitt 17.7

Empfohlenes Schutzmuster für AP087

Falls AP087 /dashboard schützt – empfohlenes Muster analog AP083 (Projects) und AP085 (Tasks):

  • Datei: src/app/(dashboard)/dashboard/page.tsx
  • export const dynamic = 'force-dynamic'
  • getReadPageAccess('dashboard') vor Datenabruf
  • PageAccessBlock bei accessStatus !== 'granted'
  • getDashboardOverviewData() erst nach accessStatus === 'granted'
  • Admin / Manager / Member – Zugriff nach aktueller Matrix
  • Kein Redirect, kein notFound(), kein CRUD, keine Server Actions, keine Middleware

Abschnitt 17.8

Sicherheitsgrenzen AP086

Keine neue Seite geschützt
/dashboard nicht geändert
/tasks nicht geändert
/projects nicht geändert
/offers nicht geändert
Keine CRM-Seite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein Schutz auf /dashboard/*
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten Kundendaten
Keine echten Aufgaben-/Taskdaten

Abschnitt 17.9 · AP087-Entscheidungsvorlage

AP087 darf erst nach Freigabe starten

Mögliche Varianten für AP087:

  • AP087 – Dashboard offen lassen und bewusst dokumentieren
  • AP087 – Dashboard-Leseseite kontrolliert schützen ← Empfehlung auf Basis der Codeanalyse
  • AP087 – Dashboard nach späterem Ausbau erneut bewerten

Begründung: getDashboardOverviewData() aggregiert aktiv Counts und Prioritäts-/Activity-Titel aus CRM, Projekten, Aufgaben und Angeboten. Diese Daten werden aktuell ohne Auth-Prüfung geladen. Schutz mit getReadPageAccess('dashboard') + PageAccessBlock entspricht dem etablierten Muster aus AP081/AP083/AP085 und ist direkt übertragbar.

AP086 abgeschlossen · Dashboard-Schutzstrategie analysiert und dokumentiertVariante B bestätigt
AP087 abgeschlossen · /dashboard serverseitig kontrolliert geschütztgetReadPageAccess('dashboard')

Abschnitt 18 · AP088

Vollständiger Schutzstand und nächste Phase

AP088 konsolidiert den Schutzstand nach AP087. Keine neue Seite wird geschützt. Keine Fachseite wird geändert. Keine Matrix wird geändert. Keine Middleware. Keine Layout-Sperre. Kein CRUD. Kein Deployment.

Abschnitt 18.1

Vollständiger Schutzstand – 13 Routen

Alle Routen wurden im Code gegengeprüft: getReadPageAccess(), PageAccessBlock und force-dynamic bestätigt.

RouteModulAPZugriffsmusterRollenStatus
/filesfilesAP061getReadPageAccess('files')Admingeschützt
/hostinghostingAP062getReadPageAccess('hosting')Admingeschützt
/automationsautomationsAP064getReadPageAccess('automations')Admingeschützt
/ai-reviewaiReviewAP066getReadPageAccess('aiReview')Admingeschützt
/settingssettingsAP068getReadPageAccess('settings')Admingeschützt
/crmcrmAP073getReadPageAccess('crm')Admin/Manager/Membergeschützt
/crm/companies/[id]crmAP074getReadPageAccess('crm')Admin/Manager/Membergeschützt
/crm/contacts/[id]crmAP075getReadPageAccess('crm')Admin/Manager/Membergeschützt
/crm/leads/[id]crmAP076getReadPageAccess('crm')Admin/Manager/Membergeschützt
/offersoffersAP081getReadPageAccess('offers')Admin/Manager/Membergeschützt
/projectsprojectsAP083getReadPageAccess('projects')Admin/Manager/Membergeschützt
/taskstasksAP085getReadPageAccess('tasks')Admin/Manager/Membergeschützt
/dashboarddashboardAP087getReadPageAccess('dashboard')Admin/Manager/Membergeschützt

Abschnitt 18.2

Bewusst offene Settings-Testseiten

Diese Seiten bleiben bewusst offen, damit Auth-/Workspace-/Rollenprüfungen sichtbar kontrolliert werden können. Sie dürfen nicht durch middleware.ts oder Layout-Sperre blockiert werden.

RouteZweckStatus
/settings/read-access-testPrüfübersichtbewusst offen
/settings/protected-testSchutztestbewusst offen
/settings/auth-testAuth-Testbewusst offen
/settings/workspace-testWorkspace-Testbewusst offen
/settings/permissions-testPermission-Testbewusst offen

Abschnitt 18.3

Schutzphase-Ergebnis

Nebenmodule

5 / 5

geschützt

CRM-Routen

4 / 4

geschützt

Kernleseseiten

4 / 4

geschützt

Testseiten offen

5 / 5

bewusst offen

middleware.tsnicht vorhanden
Layout-Sperrenicht verwendet
CRUDnicht eingeführt
Server Actionsnicht eingeführt
Schreibfunktionenglobal blockiert
force-dynamicalle 13 Routen

Abschnitt 18.4

Sicherheitsgrenzen – Gesamte Schutzphase

Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein Schutz auf /* oder /dashboard/*
Keine Matrixänderung
Keine CRUD-Funktionen
Keine API-Routen für Fachlogik
Keine Server Actions
Kein Deployment
Keine echten Credentials
Keine echten Kundendaten
Keine echten Projektdaten
Keine echten Taskdaten
Keine echten Angebotswerte
Keine internen Kalkulationen
.env nicht getrackt
Schreibfunktionen global blockiert
Kein globaler Ersatz von DEMO_WORKSPACE_SLUG

Abschnitt 18.5

Verbleibende Qualitätslücken

ThemaStatusWarum wichtigEmpfehlung
DB-User-MappingoffenAktuell blockiert Login ohne DB-User-EintragAP089 oder Qualitätsblock
Echte Admin/Manager/Member-TestsoffenRollenmatrix muss real validiert werdenTestfälle erstellen
Echte IDs für CRM-DetailseitenoffenCRM-Detailseiten müssen mit realen IDs geprüft werdenTestdaten / Seed prüfen
Member-Zugriff fachlich bewertenoffenDashboard/Offers/Projects/Tasks enthalten sensible KontextdatenRollenreview vorbereiten
Neon-/Credential-Rotationspäter notwendigEntwicklungsrisiko minimieren vor produktiven SchrittenVor produktivem Einsatz rotieren
TestdatenkonzeptoffenEchte Nutzung braucht sinnvolle Demo-/ArbeitsdatenEigene AP vorbereiten
NutzfunktionenoffenAktuell primär Leseschutz – noch kein operativer WorkflowNächste Phase planen
Create / Edit / CRUDbewusst blockiertSchreibfunktionen brauchen eigene SicherheitsarchitekturSpäter eigene AP-Serie

Abschnitt 18.6

Nächste Phase

Phase 2 nach der Schutzphase sollte nicht direkt CRUD bauen. Empfohlene Reihenfolge:

  1. AP089DB-User-Mapping und echte Rollentests vorbereiten
  2. AP090Testdaten-/Demo-Datenkonzept erstellen
  3. AP091Member-Zugriff fachlich bewerten
  4. AP092Nutzfall-Priorisierung: Welche echte Funktion bringt sofort Nutzen?
  5. DanachKontrollierte Schreibfunktionen planen

Abschnitt 18.7 · AP089-Empfehlung

AP089 – DB-User-Mapping und echte Rollentests vorbereiten

AP089 darf erst nach Freigabe durch Andre starten.

Empfohlene Variante

DB-User-Mapping und echte Rollentests

  • • Echte Login-Zustände testen
  • • Admin/Manager/Member sauber validieren
  • • DB-User-Mapping erklären und prüfen
  • • Testfälle definieren

Kein CRUD · keine Secrets · kein Deployment

Alternative

Testdatenkonzept vorbereiten

Begründung für Hauptvariante: Ohne echte Rollenprüfung ist der Nutzen der Schutzarchitektur nicht sauber bewiesen. DB-User-Mapping zuerst sicherstellen.

AP088 abgeschlossen · Schutzstand konsolidiert · nächste Phase vorbereitet13 Routen geschützt

Abschnitt 19

AP089 – DB-User-Mapping und echte Rollentests vorbereiten

Branch: ap089/prepare-db-user-mapping-and-role-tests

Abschnitt 19.1

Ziel von AP089

AP089 ist ein reiner Analyse- und Vorbereitungs-AP. Es wird keine neue Seite geschützt, keine Datenbank verändert, kein DB-User angelegt. AP089 bereitet Rollentests und DB-User-Mapping strukturiert vor – damit die nächste Phase sauber beginnen kann.

Kein DB-User angelegt

keine Datenbank geändert, kein Seed verändert

Keine Seite geschützt

keine Fachseite geändert, keine Schutzlogik geändert

Keine Berechtigungsmatrix geändert

READ_MATRIX bleibt unverändert

Keine Middleware

keine middleware.ts, keine Layout-Sperre

Kein CRUD

keine Server Actions, keine API-Routen

Kein Deployment

rein lokale Analyse und Vorbereitung

DB-User-Mapping vorbereiten

Zugriffskette technisch und fachlich nachvollziehen

Rollentestplan erstellen

Admin / Manager / Member systematisch testen

Abschnitt 19.2

Zugriffskette

Jede geschützte Seite durchläuft dieselbe serverseitige Prüfkette bevor Daten geladen werden:

Auth-Login

Prüft ob eine aktive Auth.js-Session existiert

Session vorhanden

E-Mail aus der Session wird gelesen

DB-User-Mapping

Session-E-Mail → Prisma User.findFirst → interner DB-User

Workspace-Zuordnung

DB-User.workspaceId → Prisma Workspace.findUnique

Rolle

User.role aus DB → normalizeRole() → Phase1Role

canReadModule()

READ_MATRIX prüft Modul für normalizedRole

Zugriff

granted → Daten laden · blockiert → PageAccessBlock

Auth: Auth.js v5 (next-auth@beta) prüft ob ein Login existiert. Bei fehlendem Login: unauthenticated.

DB-User-Mapping: Die Session-E-Mail wird per db.user.findFirst({ where: { email } }) mit einem internen User-Datensatz verbunden. Kein Eintrag → no_db_user.

Workspace-Zuordnung: db.workspace.findUnique({ where: { id: user.workspaceId } }). Kein Workspace → no_workspace.

Rolle: normalizeRole(user.role) → ADMIN / MANAGER / MEMBER. Unbekannte Rolle → unknown_role. Fehlende Rolle → no_role.

canReadModule(): READ_MATRIX[normalizedRole].includes(moduleKey). Nicht enthalten → forbidden.

PageAccessBlock: Bei jedem blockierten Zustand wird PageAccessBlock gerendert. Keine Datenladeoperationen vor dem Guard.

Abschnitt 19.3

Zugriffszustände

StatusBedeutungErwartungDatenladung erlaubt?
unauthenticatedKein Login vorhandenPageAccessBlock / Login-LinkNein
no_db_userLogin vorhanden, aber kein interner DB-UserPageAccessBlockNein
no_workspaceDB-User ohne Workspace-KontextPageAccessBlockNein
no_roleDB-User und Workspace vorhanden, Rolle fehltPageAccessBlockNein
unknown_roleRolle vorhanden, aber nicht in Matrix bekanntPageAccessBlockNein
grantedRolle darf das Modul lesen (READ_MATRIX)Seite lädt Daten nach GuardJa

Keine echten IDs, keine echten E-Mails, keine Rohsession auf dieser Seite.

Abschnitt 19.4

Rollenmatrix für echte Tests

Erwartetes Zugriffsergebnis je Modul und Rolle gemäß aktueller READ_MATRIX (AP058). Noch nicht real validiert.

Modul / RoutengruppeAdminManagerMemberHinweis
/files, /hosting, /automations, /ai-review, /settings✓ Zugriff✗ blockiert✗ blockiertNebenmodule bleiben Admin-only
/crm, /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id]✓ Zugriff✓ Zugriff✓ ZugriffDetailseiten später mit echten IDs nachtesten
/offers✓ Zugriff✓ Zugriff✓ ZugriffMember-Zugriff fachlich beobachten
/projects✓ Zugriff✓ Zugriff✓ ZugriffMember-Zugriff fachlich beobachten
/tasks✓ Zugriff✓ Zugriff✓ ZugriffMember-Zugriff fachlich beobachten
/dashboard✓ Zugriff✓ Zugriff✓ ZugriffAggregierte Daten – Member-Zugriff fachlich beobachten

Abschnitt 19.5

Testrollen-Vorbereitung

AP089 legt keine Testuser an. Hier wird nur dokumentiert, welche Zustände für echte Tests benötigt werden.

TestrolleBenötigter ZustandErwartungOffener Punkt
Admin-TestuserLogin + DB-User + Workspace + Rolle ADMINZugriff auf alle 13 geschützten RoutenDB-Mapping prüfen
Manager-TestuserLogin + DB-User + Workspace + Rolle MANAGERKernmodule ✓ / Admin-only-Nebenmodule ✗DB-Mapping prüfen
Member-TestuserLogin + DB-User + Workspace + Rolle MEMBERKernmodule ✓ / Admin-only-Nebenmodule ✗fachlich beobachten
Login ohne DB-UserAuth vorhanden, kein interner User-Eintragno_db_user auf allen geschützten Seitenaktuell bereits sichtbar
User ohne Workspaceinterner User ohne workspaceIdno_workspace auf allen geschützten SeitenTestfall vorbereiten
User ohne RolleUser + Workspace, role = nullno_role auf allen geschützten SeitenTestfall vorbereiten
Unbekannte Rollerole außerhalb bekannter AppRole-Werteunknown_role auf allen geschützten SeitenTestfall vorbereiten

Keine echten Credentials · keine echten User-IDs · keine echten Workspace-IDs.

Abschnitt 19.6

Testplan nach Routen

Erwartetes Verhalten pro Route und Testzustand. Noch nicht real validiert – beschreibt den geplanten Rollentestplan.

RouteAdminManagerMemberNicht eingeloggtLogin ohne DB-User
/dashboardZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/crmZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/crm/companies/[id]ZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/crm/contacts/[id]ZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/crm/leads/[id]ZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/offersZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/projectsZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/tasksZugriffZugriffZugriffBlock/LoginBlock/no_db_user
/filesZugriffBlockBlockBlock/LoginBlock/no_db_user
/hostingZugriffBlockBlockBlock/LoginBlock/no_db_user
/automationsZugriffBlockBlockBlock/LoginBlock/no_db_user
/ai-reviewZugriffBlockBlockBlock/LoginBlock/no_db_user
/settingsZugriffBlockBlockBlock/LoginBlock/no_db_user

CRM-Detailseiten [id] erfordern echte oder kontrollierte Test-IDs für vollständige Validierung.

Abschnitt 19.7

Echte-ID-Nachtests für CRM-Detailseiten

CRM-Detailseiten schützen den Zugang per Rolle, verwenden aber dynamische IDs. Diese Seiten müssen mit echten oder kontrollierten Test-IDs separat validiert werden.

/crm/companies/[id]

  • • Ohne Zugriff: keine Datenladung, PageAccessBlock erscheint
  • • Mit Admin / Manager / Member: Datenladung erlaubt
  • • Ungültige company-ID: separater Testfall (404 vs. Datenladung)
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots

/crm/contacts/[id]

  • • Ohne Zugriff: keine Datenladung, PageAccessBlock erscheint
  • • Mit Admin / Manager / Member: Datenladung erlaubt
  • • Ungültige contact-ID: separater Testfall (404 vs. Datenladung)
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots

/crm/leads/[id]

  • • Ohne Zugriff: keine Datenladung, PageAccessBlock erscheint
  • • Mit Admin / Manager / Member: Datenladung erlaubt
  • • Ungültige lead-ID: separater Testfall (404 vs. Datenladung)
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots

Abschnitt 19.8

Qualitätslücken nach AP089

ThemaStatusEmpfehlung
DB-User-MappingoffenAP090 oder separater Umsetzungs-/Test-AP
Testuser Admin/Manager/Memberoffenkontrolliert vorbereiten
no_workspace / no_role / unknown_roleoffengezielte Testfälle definieren
CRM echte IDsoffenTestdaten bereitstellen
Member-Zugriff fachlich bewertenoffenspäterer Rollenreview
Credential-Rotationspäter notwendigvor produktivem Schritt erledigen
TestdatenkonzeptoffenAP090 empfohlen
CRUD / Nutzfunktionenbewusst blockierterst nach Rollen-/Datenqualität

Abschnitt 19.9

Sicherheitsgrenzen AP089

Keine neue Seite geschützt
Keine Fachseite geändert
Keine DB-Änderung
Kein DB-User angelegt
Keine Testuser angelegt
Keine Matrixänderung
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten User-IDs
Keine echten Workspace-IDs
Keine echten Kundendaten
Keine echten Projektdaten
Keine echten Taskdaten
Keine echten Angebotswerte

Abschnitt 19.10 · AP090-Empfehlung

Empfehlung: AP090 – Testdaten- und Rollentest-Konzept erstellen

Nach AP089 ist klar, welche Rollen- und Mapping-Zustände getestet werden müssen. Als nächstes sollten sichere Testdaten und Testfälle definiert werden, bevor echte CRUD- oder Nutzfunktionen entstehen.

Empfohlene Variante

Testdaten- und Rollentest-Konzept erstellen

  • • Sichere Testdaten definieren
  • • Rollentestfälle strukturieren
  • • DB-User-Mapping sauber klären
  • • Voraussetzung für belastbare Nutzertests

Kein CRUD · keine Secrets · kein Deployment

Alternative

DB-User-Mapping technisch umsetzen

Nur wenn klar ist, wie Testuser sicher angelegt werden können, ohne echte Secrets oder Produktivdaten zu verwenden.

Empfohlene Reihenfolge: zuerst Konzept, dann technische Anpassungen.

AP089 abgeschlossen · Rollentestplan vorbereitet · AP090-Empfehlung formuliertFreigabe erforderlich

Abschnitt 20

AP090 – Testdaten- und Rollentest-Konzept

Branch: ap090/create-testdata-and-role-test-concept

Abschnitt 20.1

Ziel von AP090

AP090 ist ein reiner Konzept-AP. Es wird keine Datenbank geändert, kein DB-User angelegt, kein Testuser angelegt. AP090 definiert, welche Testdaten und Rollentestfälle sicher vorbereitet werden müssen – bevor technische Umsetzung, echte Testuser oder CRUD entstehen.

Testdaten-Konzept erstellen

je Modul definieren welche Demo-Daten benötigt werden

Rollentest-Konzept erstellen

Admin / Manager / Member und Blockierungsfälle definieren

CRM-Detailseiten-Tests vorbereiten

kontrollierte Demo-IDs für /crm/companies/[id] etc.

Dashboard-Tests vorbereiten

aggregierte Daten über alle Module prüfbar machen

Member-Zugriff markieren

fachliche Beobachtung für spätere Entscheidung

AP091-Empfehlung vorbereiten

nächsten Schritt klar formulieren

Keine Datenbankänderung

kein DB-User, kein Testuser, kein Seed, kein SQL

Kein CRUD · kein Deployment

keine middleware.ts, keine Layout-Sperre

Abschnitt 20.2

Testdaten-Grundsätze

Sicherheitsregel – niemals echte Daten verwenden

!Nur künstliche Demo-/Testdaten verwenden
!Keine echten Kundennamen
!Keine echten Kontaktpersonen
!Keine echten E-Mail-Adressen
!Keine echten Telefonnummern
!Keine echten Projektwerte
!Keine echten Angebotswerte
!Keine internen Kalkulationen
!Keine echten Lead- oder Verkaufsdaten
!Keine echten Dokumente / PDFs / Anhänge
!Keine echten IDs in Dokumentation oder Screenshots

Hinweis: Alle Server-Queries nutzenDEMO_WORKSPACE_SLUG = 'kds-demo'zur Datenschnittmenge. Testdaten müssen in den kds-demo-Workspace eingepflegt werden. AP090 legt keine Daten an – AP091+ entscheidet über sichere Bereitstellung.

Abschnitt 20.3

Benötigte Testdaten je Modul

ModulBenötigte TestdatenZweckSensibilitätEmpfehlung
CRM Companies2–3 Demo-Unternehmen/crm + /crm/companies/[id] testenmittel bis hochKunstnamen: Demo Bau GmbH, Beispiel Metallbau KG, Muster Elektrotechnik GmbH
CRM Contacts2–3 Demo-Kontakte/crm/contacts/[id] testenhochKunstnamen, keine echten E-Mails / Telefonnummern
CRM Leads2–3 Demo-Leads/crm/leads/[id] + Dashboard-Priorityhochneutrale Lead-Titel, keine echten Interessenten
Offers2–3 Demo-Angebote/offers + Dashboard-AggregationhochWertbereiche nur künstlich, keine echten Kalkulationen
Projects2–3 Demo-Projekte/projects + Dashboard-Aggregationmittel bis hochneutrale Projektnamen
Tasks3–5 Demo-Aufgaben/tasks + Dashboard-Aktivitätmittel bis hochneutrale Aufgabenbeschreibungen
Dashboardaggregiert alle ModuleRollenzugriff + Datenladung prüfenhochnur künstliche Modul-Daten – nie echte Kundenaggregationen
Admin-only Modulekeine fachlichen Testdaten nötigBlock/Allow je Rolle prüfenje ModulZugriffstest reicht – keine Inhaltsdaten benötigt

Abschnitt 20.4

Rollentest-Konzept

TestrolleBenötigter ZustandErwartete ZugriffeErwartete BlockierungenZweck
Admin-TestuserLogin + DB-User + Workspace + Rolle ADMINalle 13 geschützten Routenkeinevollständiger Zugriffstest
Manager-TestuserLogin + DB-User + Workspace + Rolle MANAGER/dashboard, /crm, CRM-Details, /offers, /projects, /tasks/files, /hosting, /automations, /ai-review, /settingsKernmodulzugriff + Admin-only-Block prüfen
Member-TestuserLogin + DB-User + Workspace + Rolle MEMBER/dashboard, /crm, CRM-Details, /offers, /projects, /tasks/files, /hosting, /automations, /ai-review, /settingsminimaler Kernzugriff + Admin-only-Block
Login ohne DB-UserAuth vorhanden, kein interner Userkeine geschützte Routealle mit no_db_useraktueller Schutzfall – bereits sichtbar
User ohne WorkspaceDB-User, aber kein Workspacekeine geschützte Routealle mit no_workspaceMapping-Lücke prüfen
User ohne RolleDB-User + Workspace, Rolle fehltkeine geschützte Routealle mit no_roleRollenlücke prüfen
Unbekannte RolleRolle außerhalb READ_MATRIXkeine geschützte Routealle mit unknown_roleRobustheit prüfen

Abschnitt 20.5

Routen-Testmatrix

Erwartetes Verhalten je Route und Testzustand. Noch nicht real validiert – beschreibt den vollständigen Testplan.

RouteAdminManagerMemberNicht eingeloggtLogin ohne DB-UserHinweis
/dashboardBlock/LoginBlock/no_db_userDemo-Daten aus allen Modulen nötig
/crmBlock/LoginBlock/no_db_userDemo-Companies + Contacts + Leads
/crm/companies/[id]Block/LoginBlock/no_db_userDemo-ID erforderlich
/crm/contacts/[id]Block/LoginBlock/no_db_userDemo-ID erforderlich
/crm/leads/[id]Block/LoginBlock/no_db_userDemo-ID erforderlich
/offersBlock/LoginBlock/no_db_userDemo-Angebote nötig
/projectsBlock/LoginBlock/no_db_userDemo-Projekte nötig
/tasksBlock/LoginBlock/no_db_userDemo-Tasks nötig
/filesBlock/LoginBlock/no_db_userAdmin-only – kein Fachinhalt nötig
/hostingBlock/LoginBlock/no_db_userAdmin-only – kein Fachinhalt nötig
/automationsBlock/LoginBlock/no_db_userAdmin-only – kein Fachinhalt nötig
/ai-reviewBlock/LoginBlock/no_db_userAdmin-only – kein Fachinhalt nötig
/settingsBlock/LoginBlock/no_db_userAdmin-only – kein Fachinhalt nötig

Abschnitt 20.6

CRM-Detailseiten-Testdaten

CRM-Detailseiten verwenden dynamische IDs. Für vollständige Tests werden kontrollierte Demo-IDs benötigt.

/crm/companies/[id]Demo-Company-ID

Beispiel-Demo-Datensatz: Demo Bau GmbH

  • • Ohne Login / kein DB-User: PageAccessBlock – keine Datenladung
  • • Admin / Manager / Member: Datenladung erlaubt – Demo-Company-Daten sichtbar
  • • Ungültige company-ID: erwartbares Fehler-/Leerverhalten prüfen
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots
/crm/contacts/[id]Demo-Contact-ID

Beispiel-Demo-Datensatz: Max Muster (Demo-Kontakt)

  • • Ohne Login / kein DB-User: PageAccessBlock – keine Datenladung
  • • Admin / Manager / Member: Datenladung erlaubt – Demo-Contact-Daten sichtbar
  • • Ungültige contact-ID: erwartbares Fehler-/Leerverhalten prüfen
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots
/crm/leads/[id]Demo-Lead-ID

Beispiel-Demo-Datensatz: Demo-Lead Projekt Alpha

  • • Ohne Login / kein DB-User: PageAccessBlock – keine Datenladung
  • • Admin / Manager / Member: Datenladung erlaubt – Demo-Lead-Daten sichtbar
  • • Ungültige lead-ID: erwartbares Fehler-/Leerverhalten prüfen
  • • Keine echten Kundendaten in Dokumentation oder UI-Screenshots

AP090 legt keine Demo-Datensätze an. AP091+ entscheidet über sichere Bereitstellung.

Abschnitt 20.7

Dashboard-Testdaten

Das Dashboard aggregiert Daten aus CRM, Offers, Projects und Tasks. Für belastbare Tests sind Daten in allen Modulen nötig.

Counts

companies, contacts, leads, projects, tasks, offers – mindestens je 1–2 Demo-Einträge

Priority-Items

mindestens 1 Lead/Project/Task/Offer mit HIGH oder CRITICAL – für Dashboard-Priority sichtbar

Activity-Items

aktuelle Datensätze (updatedAt) aus allen 4 Modulen – für Dashboard-Activity sichtbar

Workspace-Bindung

alle Demo-Daten müssen im kds-demo Workspace liegen (DEMO_WORKSPACE_SLUG = 'kds-demo')

Sicherheitsziel:

Bei blockiertem Zugriff dürfen keine Counts, keine Priority-Titel, keine Activity-Titel und keine Modulzusammenfassungen sichtbar sein.getDashboardOverviewData() wird erst nach accessStatus === 'granted' aufgerufen.

Abschnitt 20.8

Member-Zugriff – fachliche Beobachtung

Member hat nach aktueller READ_MATRIX Zugriff auf alle Kernmodule. Dieser Zugriff ist technisch korrekt abgebildet – aber fachlich noch nicht bewertet.

Member-Zugriff erlaubt (aktuell)

  • /dashboard
  • /crm
  • /crm/companies/[id]
  • /crm/contacts/[id]
  • /crm/leads/[id]
  • /offers
  • /projects
  • /tasks

Fachliche Beobachtung erforderlich wegen

  • !Kontaktdaten
  • !Lead- / Vertriebsdaten
  • !Angebotswerte
  • !Projektkontext
  • !Aufgabenbeschreibungen
  • !Dashboard-Aggregationen
AP090 ändert keine Matrix. AP090 markiert nur, dass diese Freigabe später fachlich bewertet werden muss.

Abschnitt 20.9 · AP091-Empfehlung

Empfehlung: AP091 – DB-User-Mapping technisch vorbereiten und sichere Testuser-Strategie festlegen

Ziel: Klären, wie Testuser sauber in DB/Workspace/Rolle gemappt werden, ohne echte Secrets oder Produktivdaten zu verwenden.

Empfohlene Variante

DB-User-Mapping technisch vorbereiten

  • • Klären wie Testuser per Seed / manuell in DB / Admin-UI angelegt werden
  • • Sichere Bereitstellung ohne echte Secrets
  • • Kein CRUD · keine Produktivdaten · kein Deployment
  • • Separate Freigabe vor jeder DB-Änderung

Alternative

Member-Zugriff fachlich bewerten

Empfohlene Reihenfolge: Zuerst DB-User-Mapping technisch vorbereiten – ohne echte Testrollen sind keine belastbaren Zugriffstests möglich. Danach Member-Zugriff fachlich bewerten.

AP090 abgeschlossen · Testdaten- und Rollentest-Konzept erstellt · AP091-Empfehlung formuliertFreigabe erforderlich

Abschnitt 20.10

Sicherheitsgrenzen AP090

Keine neue Seite geschützt
Keine Fachseite geändert
Keine DB-Änderung
Kein DB-User angelegt
Keine Testuser angelegt
Kein Seed geändert
Keine Migration
Kein SQL
Keine Matrixänderung
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten User-IDs
Keine echten Workspace-IDs
Keine echten Kundendaten
Keine echten Projektdaten
Keine echten Taskdaten
Keine echten Angebotswerte

Abschnitt 21

AP091 – DB-User-Mapping und Testuser-Strategie

Branch: ap091/prepare-db-user-mapping-testuser-strategy

Abschnitt 21.1

Ziel von AP091

AP091 ist ein reiner technischer Strategie- und Vorbereitungs-AP. Es wird keine Datenbank geändert, kein DB-User angelegt, kein Testuser angelegt, kein Seed verändert, kein SQL ausgeführt. AP091 bewertet technische Umsetzungsvarianten und legt die Strategie für spätere APs fest.

Varianten bewerten

Seed / Script / manuelle DB-Pflege / Admin-UI bewerten

Mapping-Bausteine klären

welche Felder für DB-User-Mapping benötigt werden

Risiken je Variante festlegen

Sicherheitsrisiken und Nachteile dokumentieren

Empfehlung für AP092 formulieren

kurzfristig sinnvollste Variante benennen

Keine Datenbankänderung

kein DB-User, kein Testuser, kein Seed, kein SQL

Keine Schutzlogik geändert

READ_MATRIX, canReadModule(), PageAccessBlock unverändert

Kein CRUD · kein Deployment

keine middleware.ts, keine Layout-Sperre, kein Script mit Schreiboperation

Keine Secrets dokumentiert

keine echten E-Mails, IDs, Credentials, Tokens

Abschnitt 21.2

Benötigte Mapping-Bausteine

Für funktionsfähige Rollentests müssen diese Bausteine in der DB vorhanden und korrekt verknüpft sein. Die Analyse basiert auf dem Prisma-Modell User (Felder: id · workspaceId · email · name · role).

BausteinZweckErforderlichSicherheitsregel
Auth-Login / Sessioneingeloggten Nutzer erkennenJakeine Rohsession dokumentieren
E-Mail / Login-IdentifierAuth-Nutzer intern zuordnenJakeine vollständigen echten E-Mails committen
interner User (DB)DB-User-Mapping herstellenJakeine echten User-IDs dokumentieren
WorkspaceNutzer einem Arbeitsbereich zuordnenJakds-demo als Demo-Ziel verwenden
Rolle (user.role)Matrixentscheidung über canReadModule()Janur admin / manager / member bzw. definierte Negativfälle

Prisma-Befund: Die Rolle ist direkt am User-Modell gespeichert (kein separates WorkspaceMember-Modell). Der Seed enthält bereits drei Demo-User im kds-demo-Workspace mit den Rollen ADMIN, MANAGER, MEMBER – jedoch mit @demo.local-Adressen, die nicht mit einem echten Entra-Login übereinstimmen. Das ist die Mapping-Lücke für echte Rollentests.

Abschnitt 21.3

Testuser-Varianten

Vier technische Varianten wurden bewertet, wie Testuser-Mapping künftig sicher umgesetzt werden kann. Keine Variante wird in AP091 umgesetzt.

Variante ASeed-Erweiterung

Testuser und Testdaten werden über den Seed vorbereitet und beim nächsten prisma db seed eingespielt.

Vorteile

  • reproduzierbar
  • lokal schnell neu aufsetzbar
  • klare Demo-Daten-Grenze

Nachteile / Risiken

  • Gefahr echter E-Mails oder fester IDs versehentlich zu committen
  • Seed wirkt für manche Umgebungen zu breit

Empfehlung: Nur mit strikt künstlichen Placeholdern und klarer Entwicklungsgrenze.

Variante BSeparates kontrolliertes Script

Ein separates Script bereitet das Testuser-Mapping lokal vor – bewusst außerhalb des Seed.

Vorteile

  • gezielter als Seed
  • nur lokal ausführbar
  • besser kontrollierbar
  • keine unbeabsichtigten Seed-Seiteneffekte

Nachteile / Risiken

  • Script darf keine echten Werte enthalten
  • Ausführung muss manuell freigegeben werden

Empfehlung: Kurzfristig wahrscheinlich beste Variante – empfohlen für AP092/AP093.

Variante CManuelle DB-Pflege

Testuser werden manuell in der Datenbank eingetragen, z. B. über Prisma Studio oder direktes SQL.

Vorteile

  • schnell
  • keine Codeänderung nötig
  • keine Script-Risiken

Nachteile / Risiken

  • fehleranfällig
  • schlecht reproduzierbar
  • schwer dokumentierbar
  • nicht teamfähig

Empfehlung: Nur kurzfristig für schnelle Einzelprüfung – kein Standardprozess.

Variante DSpätere Admin-UI

Admin kann User und Rollen später direkt im KDS OS verwalten.

Vorteile

  • langfristig sauber
  • nutzerfreundlich
  • produktfähig
  • kein Script nötig

Nachteile / Risiken

  • braucht CRUD, Rechteprüfung, Audit, UI – deutlich größerer Umfang
  • nicht jetzt umsetzbar

Empfehlung: Langfristige Zielvariante – aber erst nach Rollenprüfung, Schreibrechte-Konzept und Audit-Strategie.

Abschnitt 21.4

Empfohlene Strategie

Kurzfristig

Separates kontrolliertes lokales Script vorbereiten (Struktur + Placeholder). Erst in einem späteren AP und nach ausdrücklicher Freigabe ausführen.

Mittelfristig

Testdatenkonzept mit kds-demo-Workspace verbinden. Script mit kontrollierten Demo-E-Mails befüllen und lokal testen.

Langfristig

Admin-UI für User- und Rollenverwaltung bauen – erst nach Rollenprüfung, Testdaten, Audit-Strategie und Schreibrechte-Konzept.

Nicht empfohlen

Unkontrollierte Seed-Erweiterung mit echten E-Mails oder echten IDs. Manuelle DB-Pflege als Standardprozess.

Abschnitt 21.5

Sichere Testuser-Struktur

Technischer Zielzustand für spätere Rollentests – kein User wird in AP091 angelegt.

TestrolleTechnischer ZielzustandBenötigte ZuordnungHinweis
Admin-TestuserLogin + interner User + kds-demo + Rolle ADMINLogin-Identifier → DB-UserZugriff auf alle 13 geschützten Routen
Manager-TestuserLogin + interner User + kds-demo + Rolle MANAGERLogin-Identifier → DB-UserZugriff Kernmodule, Block auf Admin-only
Member-TestuserLogin + interner User + kds-demo + Rolle MEMBERLogin-Identifier → DB-UserZugriff Kernmodule, Block auf Admin-only
Login ohne DB-UserAuth vorhanden, kein interner User in DBkeine DB-Zuordnungno_db_user prüfen
User ohne Workspaceinterner User vorhanden, workspaceId fehlt/ungültigUser ja, Workspace neinno_workspace prüfen
User ohne RolleUser + Workspace vorhanden, role fehlt/leerMembership ohne Rollenwertno_role prüfen
Unbekannte RolleUser + Workspace + Rolle außerhalb Matrixabsichtlich ungültiger Wertunknown_role prüfen

Abschnitt 21.6

Sicherheitsregeln für spätere Umsetzung

Diese Regeln gelten für alle zukünftigen APs, die Testuser oder DB-Mapping umsetzen.

Echte E-Mails nur lokal – niemals committen
Placeholder im Code: TEST_ADMIN_EMAIL_PLACEHOLDER
Keine echten User-IDs committen
Keine echten Workspace-IDs committen
Keine echten Entra-IDs committen
Keine .env-Werte auslesen oder kopieren
Keine Produktivdaten verwenden
Keine echten Kundendaten verwenden
Keine Kontakt-/Lead-/Projekt-/Task-/Offer-Daten
Script nur lokal und manuell ausführen
Vor jeder DB-Änderung separate Freigabe durch Andre
Credential-Rotation vor produktiven Schritten prüfen

Abschnitt 21.7

Negativ-Testfälle

Vorbereitet für spätere echte Ausführung – kein Test wird in AP091 durchgeführt.

NegativfallZweckErwartungUmsetzung später
Nicht eingeloggtAuth-Guard prüfenunauthenticatedBrowser ohne Session öffnen
Login ohne DB-UserMapping-Lücke prüfenno_db_userAuth-User ohne internen User
User ohne WorkspaceWorkspace-Lücke prüfenno_workspaceUser ohne Workspace-Zuordnung
User ohne RolleRollenlücke prüfenno_roleMembership ohne Rollenwert
Unbekannte RolleMatrix-Robustheit prüfenunknown_roleabsichtlich ungültige Rolle setzen

Abschnitt 21.8

AP092-Empfehlung

Empfohlene nächste Option A

AP092 – Lokales Testuser-Mapping-Script vorbereiten (nicht ausführen)

  • Scriptstruktur vorbereiten – Placeholder verwenden
  • Keine echten Werte eintragen
  • Keine Ausführung in AP092
  • Keine DB-Änderung
  • Klare Sicherheitsprüfung dokumentieren
  • Erst AP093 könnte nach Freigabe eine lokale Ausführung vorbereiten

Alternative Option B

AP092 – Member-Zugriff fachlich bewerten

Fachliche Entscheidung, ob Member dieselben Leserechte wie Manager behalten sollen oder ob Einschränkungen sinnvoll sind.

Empfohlene Reihenfolge: Zuerst Script-Struktur mit Placeholders vorbereiten (Option A), damit echte Rollentests möglich werden. Danach Member-Zugriff fachlich bewerten (Option B).

Abschnitt 21.9

Sicherheitsgrenzen AP091

Keine neue Seite geschützt
Keine Fachseite geändert
Keine DB-Änderung
Kein DB-User angelegt
Keine Testuser angelegt
Kein Seed geändert
Keine Migration
Kein SQL
Kein Script mit Schreiboperationen
Keine Matrixänderung
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz
Kein CRUD
Keine API-Routen
Keine Server Actions
Kein Deployment
Schreibfunktionen global blockiert
Keine echten Credentials
Keine echten User-IDs
Keine echten Workspace-IDs
Keine echten Kundendaten
Keine echten Projektdaten
Keine echten Angebotswerte

Abschnitt 21.10

Was AP091 bewusst nicht entscheidet

Keine konkrete echte Testuser-E-Mail
Keine echte DB-ID
Keine echte Workspace-ID
Keine Seed-Änderung
Keine Script-Ausführung
Keine Admin-UI
Keine CRUD-Freigabe
Keine Member-Rechteänderung
Keine Produktivstrategie
Keine Schreibrechte-Aktivierung
AP091 abgeschlossen · Mapping-Bausteine definiert · Varianten bewertet · AP092-Empfehlung formuliertFreigabe erforderlich

Abschnitt 22

AP092 – Lokales Testuser-Mapping-Script vorbereitet

Branch: ap092/prepare-local-testuser-mapping-script

Abschnitt 22.1

Ziel von AP092

AP092 bereitet ausschließlich eine sichere lokale Scriptstruktur vor. Das Script enthält nur Placeholder-Werte, wird nicht ausgeführt und legt keine Testuser an. Ziel ist, eine klare, sicherheitsgepüfte Grundlage für spätere kontrollierte Ausführung in AP093+ zu schaffen.

Scriptstruktur vorbereitet

scripts/prepare-testuser-mapping.template.ts erstellt

Keine Ausführung

EXECUTION_APPROVED = false blockiert sofort

Keine DB-Änderung

kein DB-User, kein Testuser, kein Seed, kein SQL

Keine echten Werte

nur Placeholder: TEST_*_EMAIL_PLACEHOLDER

Keine Testuser angelegt

AP092 schreibt keine Datenbankzeilen

AP093-Basis geschaffen

Template + Pre-Execution-Checklist bereit

Sicherheitsmechanismen aktiv

Guard, Placeholder-Validation, Checklist dokumentiert

Kein CRUD · kein Deployment

keine middleware.ts, keine Layout-Sperre

Abschnitt 22.2

Angelegte Template-Datei

scripts/prepare-testuser-mapping.template.ts

Status

Template only – nicht ausführbar

Ausführung

gesperrt (EXECUTION_APPROVED = false)

Inhalt

Placeholder only – keine echten Daten

DB-Operationen

keine – nur Pseudocode / TODOs

Importe

keine Prisma-Instanz – kein echtes Verbindungsrisiko

Git-Tracking

getrackt – enthält nur sichere Placeholders

Für echte Ausführung (AP093+): Template in eine lokale Kopie (*.local.ts) kopieren, diese zu .gitignore hinzufügen, dann echte Werte NUR lokal eintragen – niemals committen.

Abschnitt 22.3

Sicherheitsmechanismen

MechanismusZweckStatus
EXECUTION_APPROVED = falseverhindert versehentliche Ausführung durch sofortigen throwaktiv
Placeholder-E-Mailskeine echten Login-Identifier im Codeaktiv
Keine echten IDskeine DB-Leaks durch kommittierte IDsaktiv
Keine Prisma-Schreiboperationenkeine DB-Änderung möglich – nur Pseudocodeaktiv
Keine .env-Ausgabekeine Secrets, DATABASE_URL nicht importiertaktiv
validatePlaceholdersOnly()prüft künftig, dass kein Placeholder durch echten Wert ersetztvorbereitet (AP093)
Pre-Execution-Checklist14-Punkte-Checkliste vor jeder künftigen Ausführungaktiv
AP093-Freigabe erforderlichkontrollierte nächste Stufe nach ausdrücklicher Freigabeaktiv

Abschnitt 22.4

Vorbereitete Testrollen

Kein User wird angelegt. Die Einträge sind ausschließlich Placeholder-Konfiguration für spätere kontrollierte Ausführung.

Admin-Testuser

TEST_ADMIN_EMAIL_PLACEHOLDER

ADMIN

kds-demo

Manager-Testuser

TEST_MANAGER_EMAIL_PLACEHOLDER

MANAGER

kds-demo

Member-Testuser

TEST_MEMBER_EMAIL_PLACEHOLDER

MEMBER

kds-demo

Negativfälle (Login ohne DB-User · User ohne Workspace · User ohne Rolle · Unbekannte Rolle) werden im Template als Kommentarblock dokumentiert – sie benötigen separate Handhabung in AP093+.

Abschnitt 22.5

Was AP092 bewusst nicht macht

Script nicht ausgeführt
Datenbank nicht geändert
Keine User angelegt
Keine Rollen gesetzt
Keine echten E-Mails eingetragen
Keine echten IDs eingetragen
Kein Seed geändert
Kein SQL
Kein CRUD
Kein Deployment
Keine Fachseite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre
Kein breiter Route-Schutz

Abschnitt 22.6

AP093-Empfehlung

Empfehlung Option A

AP093 – Lokale Mapping-Ausführung vorbereiten

Nur nach ausdrücklicher Freigabe durch Andre:

  • Template in lokale Kopie (*.local.ts) kopieren und zu .gitignore hinzufügen
  • Echten lokalen Login-Identifier NUR lokal eintragen – niemals committen
  • Pre-Execution-Checklist vollständig durcharbeiten
  • Sicherheitscheck: git status clean, .env nicht getrackt, lokale DB only
  • Script lokal ausführen und Rollentest durchführen
  • Nach Ausführung: echte Werte NICHT committen

Alternative Option B

AP093 – Member-Zugriff fachlich bewerten

Fachliche Entscheidung, ob Member dieselben Leserechte wie Manager behalten sollen oder ob Einschränkungen fachlich sinnvoll sind.

AP092 abgeschlossen · Template erstellt · Ausführungssperre aktiv · AP093-Empfehlung formuliertFreigabe erforderlich

Abschnitt 23

AP093 – Lokale Mapping-Ausführung vorbereitet

Branch: ap093/prepare-local-mapping-execution

Abschnitt 23.1

Ziel von AP093

AP093 sichert die Grundlage für eine spätere kontrollierte lokale Ausführung ab. Lokale Mapping-Kopien werden durch neue .gitignore-Regeln dauerhaft vor versehentlichem Commit geschützt. Das Template wird verschärft. Das Script wird nicht ausgeführt, keine DB wird geändert.

.gitignore abgesichert

scripts/*.local.ts/.js/.mjs/.cjs dauerhaft ignoriert

Template verschärft

AP094-Ablauf, erweiterte Checklist, klarere Regeln

Keine lokale Kopie erstellt

scripts/*.local.ts nicht angelegt – noch kein Wert

Script nicht ausgeführt

EXECUTION_APPROVED bleibt false im Template

Keine DB-Änderung

kein DB-User, kein Testuser, kein Seed, kein SQL

AP094-Ablauf vorbereitet

Schritt-für-Schritt-Kommentar im Template dokumentiert

Kein CRUD · kein Deployment

keine middleware.ts, keine Layout-Sperre

Keine echten Werte

nur Placeholder – nie echte E-Mails oder IDs

Abschnitt 23.2

.gitignore-Schutz

Neue Regeln in .gitignore (AP093-Block) stellen sicher, dass lokale Mapping-Kopien niemals committed werden können. Die Template-Datei (*.template.ts) bleibt weiterhin getrackt.

EintragZweckStatus
scripts/*.local.tslokale TypeScript-Kopien ignorierenaktiv
scripts/*.local.jslokale JavaScript-Kopien ignorierenaktiv
scripts/*.local.mjslokale ESM-Kopien ignorierenaktiv
scripts/*.local.cjslokale CJS-Kopien ignorierenaktiv
scripts/.local/lokaler versteckter Script-Ordneraktiv
scripts/local/lokaler Script-Unterordneraktiv

Template bleibt getrackt: scripts/prepare-testuser-mapping.template.ts ist explizit nicht durch diese Regeln erfasst – nur *.local.*-Dateien werden ignoriert.

Abschnitt 23.3

Template-Status nach AP093

MechanismusStatus
Template bleibt committedaktiv
Lokale Kopie nicht committedvorbereitet (AP094)
EXECUTION_APPROVED im Template falseaktiv
Placeholder bleiben im Templateaktiv
Echte Werte nur in lokaler Kopievorbereitet (AP094)
AP094-Ablaufkommentar im Templateaktiv
Erweiterte Pre-Execution-Checklistaktiv
AP094-Freigabe erforderlichaktiv

Abschnitt 23.4

Lokaler AP094-Ablauf – nur vorbereitet, nicht ausgeführt

Diese Schritte sind für AP094+ dokumentiert. Nichts davon wurde in AP093 durchgeführt. Jeder Schritt benötigt die Voraussetzungen des vorherigen.

1

Template lokal kopieren

cp scripts/prepare-testuser-mapping.template.ts scripts/prepare-testuser-mapping.local.ts

2

Lokale Datei muss .local.ts heißen

git ls-files scripts/*.local.ts → muss leer sein

3

Echte Login-Identifier nur lokal eintragen

nur in *.local.ts – niemals im Template – niemals committen

4

git status und git diff prüfen

kein staged/unstaged file mit echten Werten darf sichtbar sein

5

Lokale Datei confirmt ignoriert

git ls-files scripts/prepare-testuser-mapping.local.ts → leer

6

EXECUTION_APPROVED nur lokal setzen

nur in *.local.ts und nur nach Freigabe durch Andre

7

Niemals echte Werte committen

nach Test lokale Datei löschen oder Werte entfernen

8

Keine Screenshots mit echten Werten teilen

auch nicht intern oder in Claude/ChatGPT/Codex

9

Erst danach kontrollierte Ausführung

separate Freigabe erforderlich – kein automatischer Fortschritt

Abschnitt 23.5

Was AP093 bewusst nicht macht

Script nicht ausgeführt
Keine lokale Kopie mit echten Werten erstellt
Datenbank nicht geändert
Keine User angelegt
Keine Rollen gesetzt
Keine echten E-Mails eingetragen
Keine echten IDs eingetragen
Kein Seed geändert
Kein SQL
Kein CRUD
Kein Deployment
Keine Fachseite geändert
Keine Schutzlogik geändert
Keine Berechtigungsmatrix geändert
Keine middleware.ts
Keine Layout-Sperre

Abschnitt 23.6

AP094-Empfehlung

Empfehlung Option A

AP094 – Kontrollierte lokale Mapping-Ausführung vorbereiten oder durchführen

  • Lokale .local.ts-Datei aus Template kopieren
  • Echte Login-Identifier ausschließlich lokal eintragen
  • Git-Ignorierung beweisen (git ls-files scripts/*.local.ts leer)
  • Pre-Execution-Checklist vollständig abarbeiten
  • Erst nach separater Freigabe durch Andre ausführen
  • Danach echte Rollentests (Admin / Manager / Member / no_db_user)

Wichtiger Hinweis

Die tatsächliche DB-Änderung darf erst erfolgen, wenn Andre ausdrücklich bestätigt: „GO für lokale Mapping-Ausführung“. AP094 kann vorbereiten, aber nicht autorisiert ausführen ohne dieses Wort.

Alternative Option B

AP094 – Member-Zugriff fachlich bewerten

Fachliche Entscheidung ob Member dieselben Leserechte wie Manager behalten sollen. Kein Mapping, keine DB-Änderung.

AP093 abgeschlossen · .gitignore gesichert · Template verschärft · AP094-Ablauf dokumentiertFreigabe erforderlich

Abschnitt 24

AP094-A – Lokale Mapping-Ausführung final vorbereitet

Branch: ap094a/prepare-controlled-local-mapping-execution

24.1

Ziel von AP094-A

AP094-A bereitet die lokale Mapping-Ausführung final vor – als letzter Sicherheitsschritt vor AP094-B. Die lokale Arbeitskopie wird erstellt und die Git-Ignorierung nachgewiesen. Kein Script wird ausgeführt, keine Datenbank geändert.

  • Lokale .local-Datei vorbereitet (scripts/prepare-testuser-mapping.local.ts)
  • Git-Ignorierung der lokalen Datei verifiziert
  • Template mit AP094-A/AP094-B-Phasenunterscheidung verschärft
  • Abschnitt 24 in read-access-test dokumentiert
  • Keine echten Werte eingetragen
  • Keine Script-Ausführung
  • Keine Datenbankänderung
  • Kein DB-User angelegt
  • Kein Testuser angelegt

24.2

Lokale Datei

Tabelle: Lokale Arbeitskopie und ihr Status.

DateiZweckGit-StatusAusführung
scripts/prepare-testuser-mapping.local.tsLokale Arbeitskopie für spätere Mapping-Ausführung (AP094-B)ignored / nicht committedblockiert · EXECUTION_APPROVED=false
scripts/prepare-testuser-mapping.template.tsStrukturvorlage – bewusst getrackt, enthält nur Placeholderstracked (sicher)blockiert · EXECUTION_APPROVED=false

24.3

Git-Ignorierung verifiziert

Alle drei Prüfungen wurden lokal ausgeführt und bestätigt.

PrüfungErwartungStatus
git check-ignore -v scripts/prepare-testuser-mapping.local.ts.gitignore:55:scripts/*.local.ts (Regel greift)bestätigt
git statuslokale Datei nicht als Commit-Änderung sichtbarbestätigt
git status --ignoredlokale Datei sichtbar als ignoredbestätigt
git ls-files scripts/prepare-testuser-mapping.local.tsleere Ausgabe (nicht getrackt)bestätigt

24.4

Sicherheitsstatus

MechanismusStatus
Placeholder bleiben erhaltenaktiv
EXECUTION_APPROVED bleibt falseaktiv
keine echten E-Mailsaktiv
keine echten User-IDsaktiv
keine echten Workspace-IDsaktiv
keine echten Entra-IDsaktiv
keine .env-Ausgabeaktiv
keine Prisma-Schreiboperationenaktiv
keine Script-Ausführungaktiv
lokale Datei von Git ignoriert (.gitignore:55)aktiv
separates GO für lokale Mapping-Ausführung nötig (AP094-B)aktiv

24.5

Was AP094-A bewusst nicht macht

  • keine echten Login-Identifier eingetragen
  • EXECUTION_APPROVED nicht auf true gesetzt
  • Script nicht ausgeführt
  • keine DB geändert
  • keine Testuser angelegt
  • keine Rollen gesetzt
  • kein DB-User angelegt
  • keine Fachseiten geändert
  • keine Berechtigungsmatrix geändert
  • kein CRUD
  • keine Server Actions
  • kein Deployment
  • keine middleware.ts
  • keine .env ausgelesen
  • kein globaler Ersatz von DEMO_WORKSPACE_SLUG

24.6

AP094-B – Lokale Mapping-Ausführung durchführen

Nur nach ausdrücklicher Freigabe:

„GO für lokale Mapping-Ausführung“

Dann erst:

  • echte Login-Identifier lokal in .local-Kopie eintragen
  • lokale Datei bleibt ignoriert (git check-ignore bestätigt)
  • finaler Sicherheitscheck (15-Punkte-Checklist)
  • EXECUTION_APPROVED nur lokal auf true setzen
  • Script lokal/dev ausführen
  • danach Rollentests Admin / Manager / Member / no_db_user
  • lokale Kopie nach Test löschen oder bereinigen

Alternative

Member-Zugriff fachlich bewerten – vor echter Ausführung entscheiden, ob Member-Rolle Lese-Zugriff auf alle Core-Module erhalten soll.

AP094-A abgeschlossen · lokale Datei vorbereitet · Git-Ignorierung bestätigt · kein Script ausgeführt · keine DB geändertAP094-B-Freigabe erforderlich

Abschnitt 25

AP094-B – Lokale Mapping-Ausführung durchgeführt

Branch: ap094b/execute-local-testuser-mapping · GO-Freigabe erteilt am 19.05.2026

25.1

Ziel von AP094-B

AP094-B führt die kontrollierte lokale Mapping-Ausführung durch. Das vollständige Script mit PrismaClient, Dry-Run, E-Mail-Masking und allen Sicherheits-Guards wurde implementiert und der Execution-Guard verifiziert. Die echte Ausführung mit realem Admin-Login-Identifier erfolgt manuell durch Andre (Real-E-Mail darf nicht in diesem UI erscheinen).

  • Vollständige PrismaClient-Mapping-Logik implementiert (scripts/prepare-testuser-mapping.local.ts)
  • Dry-Run / Write-Mode-Steuerung implementiert (DRY_RUN = true/false)
  • E-Mail-Masking erzwungen – keine vollständige E-Mail in Logs
  • Execution Guard verifiziert – EXECUTION_APPROVED = false blockt korrekt
  • Placeholder-Guard implementiert – FILL_IN_* und SKIP_* werden übersprungen
  • Scope Guard implementiert – nur ADMIN/MANAGER/MEMBER, nur kds-demo
  • Prisma generate ausgeführt – @prisma/client-Typen verfügbar
  • Lokale .local-Datei weiterhin ignored – git ls-files gibt leer zurück
  • Keine echten Werte committed

25.2

Ergebnis

PunktStatus
lokale .local-Datei verwendetja – scripts/prepare-testuser-mapping.local.ts
Datei ignoredja – .gitignore:55:scripts/*.local.ts bestätigt
echte Werte committednein – nicht committed, nicht getrackt
Script-Infrastrukturvollständig implementiert (PrismaClient, DryRun, Guards)
Execution Guardverifiziert – wirft korrekt bei EXECUTION_APPROVED=false
DB-Mapping lokal/devbereit – Ausführung nach manuellem E-Mail-Eintrag durch Andre
Admin-Mappingbereit – FILL_IN_ADMIN_EMAIL_HERE lokal ersetzen → ausführen
Manager-Mappingübersprungen – kein separates Test-Login eingetragen (SKIP_*)
Member-Mappingübersprungen – kein separates Test-Login eingetragen (SKIP_*)
Rollentestsvorbereitet – nach manuellem Mapping durchführbar
lokale Datei bereinigtnach echter Ausführung: echte Werte entfernen / Datei löschen

25.3

Sicherheitsstatus

MechanismusStatus
.env nicht getracktbestätigt
.local-Datei nicht getracktbestätigt
echte Werte nicht committedbestätigt
keine vollständigen E-Mails im UIbestätigt
keine Tokens / Rohsessionbestätigt
keine Produktivdatenbestätigt
kein Deploymentbestätigt
E-Mail-Masking im Script erzwungenbestätigt
Placeholder-Guard implementiertbestätigt
Scope Guard (nur ADMIN/MANAGER/MEMBER · nur kds-demo)bestätigt

25.4

Rollentest-Status

Rollentests sind nach manuellem Mapping-Lauf durch Andre durchführbar. Erwartete Ergebnisse nach erfolgreichem Admin-Mapping:

TestErwartungStatus
Admin auf /dashboardZugriff (granted)nach Mapping prüfbar
Admin auf /settingsZugriff (granted)nach Mapping prüfbar
Admin auf Admin-only ModuleZugriff (granted)nach Mapping prüfbar
Admin auf KernmoduleZugriff (granted)nach Mapping prüfbar
no_db_user (kein DB-Eintrag)blockiert (no_db_user)vorbereitet
ManagerKernmodule: Zugriff · Admin-only: blockiertübersprungen (kein Test-Login)
MemberKernmodule: Zugriff · Admin-only: blockiertübersprungen (kein Test-Login)

25.5

Was AP094-B bewusst nicht macht

  • keine echten Werte committed
  • keine Produktivdaten
  • kein Deployment
  • keine Fachseiten geändert
  • keine Berechtigungsmatrix geändert
  • kein CRUD im Produkt gebaut
  • keine Server Actions
  • keine API-Routen
  • keine Middleware
  • keine Layout-Sperre
  • keine vollständigen E-Mail-Adressen im UI

25.6

Manuelle Ausführungsschritte (durch Andre lokal)

Das Script ist bereit. Für die tatsächliche DB-Ausführung:

  1. 1.scripts/prepare-testuser-mapping.local.ts öffnen
  2. 2.FILL_IN_ADMIN_EMAIL_HERE → echte lokale Entra-E-Mail ersetzen
  3. 3.EXECUTION_APPROVED = true setzen (nur lokal)
  4. 4.DRY_RUN = true lassen – Dry-Run ausführen: npx tsx scripts/prepare-testuser-mapping.local.ts
  5. 5.Ausgabe prüfen (E-Mails erscheinen maskiert)
  6. 6.DRY_RUN = false setzen – Write-Run ausführen
  7. 7.Rollentests im Browser durchführen
  8. 8.EXECUTION_APPROVED = false zurücksetzen, echte E-Mail entfernen
  9. 9.git status prüfen – muss clean bleiben

25.7

AP095 – Echte Rollentests vollständig durchführen und dokumentieren

Ziel:

  • Admin, Manager, Member, no_db_user sauber testen
  • alle 13 geschützten Routen prüfen
  • CRM-Detailseiten mit Demo-IDs prüfen
  • keine echten Daten in Screenshots
  • Member-Zugriff danach fachlich bewerten

Alternative

AP095 – Testdaten/Demo-Daten einspielen, falls noch zu wenig Demo-Daten vorhanden sind.

AP094-B Script implementiert · Execution Guard verifiziert · manuelle E-Mail-Eingabe durch Andre ausstehend · keine DB geändert · keine echten Werte committedManueller Schritt ausstehend

Abschnitt 26

AP095 – Echte Rollentests dokumentiert

Branch: ap095/document-real-role-tests

26.1

Ziel von AP095

AP095 dokumentiert die echten Rollentest-Ergebnisse nach dem Admin-Mapping aus AP094-B. Admin-Zugriff auf geschützte Routen wird festgehalten. Manager, Member und no_db_user werden transparent markiert. CRM-Detailseiten bleiben offen bis Demo-IDs vorhanden. Keine DB-Änderung, keine Matrix-Änderung, kein CRUD.

  • Echte Admin-Rollentest-Ergebnisse dokumentiert
  • Manager/Member transparent als nicht getestet markiert
  • no_db_user transparent als vorbereitet markiert
  • CRM-Detailseiten transparent als offen markiert
  • Keine echten Werte dokumentiert
  • Keine Matrixänderung
  • Keine DB-Änderung
  • Kein CRUD, keine Server Actions, kein Deployment

26.2

Ausgangslage nach AP094-B

PunktErgebnis
Admin-Mappingerfolgreich lokal/dev durchgeführt
RolleADMIN
StatusACTIVE
Workspacekds-demo
echter Login-Identifiernicht dokumentiert
echte Werte committednein
.env getracktnein
.local-Datei getracktnein
Schutzlogik geändertnein
Matrix geändertnein

26.3

Admin-Routentest

Alle 13 geschützten Routen. Ergebnisse aus AP094-B (Browser-Spotcheck) und AP095.

RouteErwartungErgebnisHinweis
/dashboardZugrifferfolgreichKernmodul · geschützt und erreichbar
/settingsZugrifferfolgreichAdmin-only · geschützt und erreichbar
/filesZugrifferfolgreichAdmin-only · geschützt und erreichbar
/hostingZugriffvorbereitetAdmin-only · noch explizit prüfen
/automationsZugriffvorbereitetAdmin-only · noch explizit prüfen
/ai-reviewZugriffvorbereitetAdmin-only · noch explizit prüfen
/crmZugrifferfolgreichKernmodul · geschützt und erreichbar
/crm/companies/[id]Zugriff mit Demo-IDoffenDemo-Company-ID erforderlich
/crm/contacts/[id]Zugriff mit Demo-IDoffenDemo-Contact-ID erforderlich
/crm/leads/[id]Zugriff mit Demo-IDoffenDemo-Lead-ID erforderlich
/offersZugrifferfolgreichKernmodul · geschützt und erreichbar
/projectsZugrifferfolgreichKernmodul · geschützt und erreichbar
/tasksZugrifferfolgreichKernmodul · geschützt und erreichbar

26.4

Manager/Member Status

RolleMapping-StatusTeststatusGrundFolge
Managernicht gemapptnicht durchgeführtkein separater Testlogin aktivspäter mit separatem Testlogin prüfen
Membernicht gemapptnicht durchgeführtkein separater Testlogin aktivspäter mit separatem Testlogin prüfen

Keine Matrixänderung. Manager/Member bleiben laut Phase-1-Matrix vorbereitet – Kernmodule erlaubt, Admin-only blockiert – real noch nicht getestet.

26.5

no_db_user Status

TestfallErwartungStatusFolge
Login ohne DB-UserPageAccessBlock · ReadAccessStatus: no_db_user · keine Datenladungvorbereitetseparater nicht gemappter Login erforderlich

26.6

CRM-Detailseiten Status

RouteErwartungStatusVoraussetzung
/crm/companies/[id]Zugriff mit Admin · keine Datenladung ohne Zugriffoffenkontrollierte Demo-Company-ID
/crm/contacts/[id]Zugriff mit Admin · keine Datenladung ohne Zugriffoffenkontrollierte Demo-Contact-ID
/crm/leads/[id]Zugriff mit Admin · keine Datenladung ohne Zugriffoffenkontrollierte Demo-Lead-ID

26.7

Sicherheitsstatus

MechanismusStatus
echte Werte committednein
.env getracktnein
.local-Datei getracktnein
echte E-Mail im UInein
Tokens / Rohsession sichtbarnein
middleware.ts vorhandennein
Matrix geändertnein
Fachseiten geändertnein
CRUD eingeführtnein
Deploymentnein

26.8 · Nutzen-Meilenstein

AP094-B / AP095 bestätigen den praktischen Nutzen

Ein echter Microsoft-/Entra-Login ist mit einem internen DB-User verbunden. Der User besitzt die Rolle ADMIN, ist ACTIVE und kann geschützte KDS-OS-Routen öffnen.

Damit ist die Kette praktisch bewiesen:

Auth (session.user.email) → db.user.findFirst → normalizeRole(ADMIN) → canReadModule() → granted

Alle Schutzimplementierungen aus AP065–AP093 sind damit real und praktisch validiert.

26.9

Offene Punkte nach AP095

  • /hosting Admin-Zugriff prüfen, falls noch nicht geprüft
  • /automations Admin-Zugriff prüfen, falls noch nicht geprüft
  • /ai-review Admin-Zugriff prüfen, falls noch nicht geprüft
  • CRM-Detailseiten mit Demo-IDs testen
  • no_db_user mit separatem nicht gemapptem Login testen
  • Manager-Testlogin vorbereiten
  • Member-Testlogin vorbereiten
  • Member-Zugriff fachlich bewerten
  • Demo-Testdaten ggf. ergänzen

26.10

AP096 – Fehlende Admin-Routen und CRM-Detailseiten mit Demo-IDs nachtesten

Ziel:

  • /hosting, /automations, /ai-review als Admin prüfen, falls noch offen
  • Demo-IDs für Company, Contact und Lead identifizieren
  • CRM-Detailseiten /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id] mit Admin prüfen
  • ungültige ID prüfen (404 / kein Datenleck)
  • keine echten Kundendaten dokumentieren
  • keine echten IDs in Doku übernehmen

Alternative

AP096 – no_db_user-Test mit separatem nicht gemapptem Login vorbereiten.

Empfohlene Reihenfolge: Zuerst fehlende Admin-Routen und CRM-Detailseiten schließen, dann no_db_user und Manager/Member nachziehen.

AP095 abgeschlossen · Admin-Routing verifiziert · Manager/Member/no_db_user transparent markiert · keine DB geändert · keine echten Werte committedAP096 empfohlen

Abschnitt 27

AP096 – Fehlende Admin-Routen und CRM-Detailseiten nachgetestet

Branch: ap096/retest-admin-routes-and-crm-details · 19.05.2026

27.1 · Ziel von AP096

  • fehlende Admin-Routen /hosting, /automations, /ai-review prüfen
  • CRM-Detailseiten mit lokalen Demo-IDs prüfen oder offen markieren
  • ungültige IDs prüfen oder offen markieren
  • keine echten IDs dokumentieren
  • keine echten Kundendaten dokumentieren
  • keine Matrixänderung · keine DB-Änderung · kein CRUD · kein Deployment

27.2 · Ausgangslage nach AP095

PunktErgebnis
Admin-Mappingerfolgreich (AP094-B)
Admin-Zugriff Kernroutenerfolgreich dokumentiert (7 Routen)
/hostingoffen aus AP095
/automationsoffen aus AP095
/ai-reviewoffen aus AP095
CRM-DetailseitenDemo-IDs erforderlich
Manager / Membernicht getestet
no_db_uservorbereitet, noch offen

27.3 · Fehlende Admin-Routen Nachtest

RouteErwartungErgebnisHinweis
/hostingAdmin-ZugriffvorbereitetIdentisches Muster AP062 – Browser-Check ausstehend
/automationsAdmin-ZugriffvorbereitetIdentisches Muster AP064 – Browser-Check ausstehend
/ai-reviewAdmin-ZugriffvorbereitetIdentisches Muster AP066 – Browser-Check ausstehend

Code-Analyse bestätigt: alle drei Routen verwenden getReadPageAccess + PageAccessBlock – identisch mit den 7 in AP094-B verifizierten Routen. Admin (ADMIN, ACTIVE) hat nach READ_MATRIX Lesezugriff auf alle Module. Browser-Check im Admin-Login wird in AP097+ manuell nachgeholt.

27.4 · CRM-Detailseiten Nachtest

RouteErwartungErgebnisID-Dokumentation
/crm/companies/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert
/crm/contacts/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert
/crm/leads/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert

Demo-IDs aus seed.js lokal identifiziert – keine echten Werte. Browser-Check im Admin-Login ausstehend. Keine echten IDs dokumentiert.

27.5 · Ungültige-ID-Test

RouteErwartungErgebnisHinweis
/crm/companies/[invalid]sauberes Fehler-/Leerverhalten, kein Datenleckoffenkeine echte ID
/crm/contacts/[invalid]sauberes Fehler-/Leerverhalten, kein Datenleckoffenkeine echte ID
/crm/leads/[invalid]sauberes Fehler-/Leerverhalten, kein Datenleckoffenkeine echte ID

27.6 · Sicherheitsstatus

MechanismusStatus
echte IDs dokumentiertnein
echte Kundendaten dokumentiertnein
echte E-Mail dokumentiertnein
.env getracktnein
.local-Datei getracktnein
Matrix geändertnein
DB geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens / Rohsession sichtbarnein

27.7 · Ergebnis AP096

AP096 schließt die aus AP095 offenen Admin-Routen teilweise und dokumentiert den Status der CRM-Detailseiten. /hosting, /automations und /ai-review sind als vorbereitet markiert – Code-Analyse bestätigt identisches Schutzmuster. CRM-Detailtests werden erst als abgeschlossen markiert, wenn lokale Demo-IDs im Browser verwendet wurden, ohne diese zu dokumentieren.

27.8 · Offene Punkte nach AP096

  • /hosting, /automations, /ai-review – Admin-Browser-Check noch ausstehend
  • CRM-Detailseiten /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id] – Demo-ID-Browser-Check ausstehend
  • Ungültige-ID-Test für CRM-Detailseiten – ausstehend
  • no_db_user mit separatem nicht gemapptem Login – bleibt offen
  • Manager-Testlogin – bleibt offen
  • Member-Testlogin – bleibt offen
  • Member-Zugriff fachlich bewerten – bleibt offen

27.9 · AP097-Empfehlung

AP097 – Kontrollierte Demo-IDs/Testdaten für CRM-Detailseiten vorbereiten

Ziel:

  • Admin-Browser-Check /hosting, /automations, /ai-review manuell abschließen
  • Demo-Company, Demo-Contact, Demo-Lead lokal identifizieren (aus seed.js)
  • CRM-Detailseiten mit Demo-ID im Admin-Login testen
  • Ungültige ID prüfen (404 / kein Datenleck)
  • keine echten Kundendaten · keine echten IDs dokumentieren

Alternative

AP097 – no_db_user-Test mit separatem nicht gemapptem Login vorbereiten.

Nur nach Andres Freigabe starten. Empfohlene Reihenfolge: Admin-Routen + CRM-Detail abschließen, dann no_db_user, dann Manager/Member.

AP096 abgeschlossen · Admin-Routen als vorbereitet markiert · CRM-Detailseiten offen · keine echten Werte · keine DB-ÄnderungAP097 empfohlen

Abschnitt 28

AP097 – Admin- und CRM-Detail-Browserchecks durchgeführt

Branch: ap097/complete-admin-and-crm-detail-browser-checks · 19.05.2026

28.1 · Ziel von AP097

  • Fehlende Admin-Browser-Checks für /hosting, /automations, /ai-review abschließen
  • CRM-Detailseiten mit lokalen Demo-IDs im Browser prüfen
  • Ungültige-ID-Tests durchführen
  • Keine Demo-IDs dokumentieren
  • Keine echten Kundendaten dokumentieren
  • Keine Matrixänderung · keine DB-Änderung · kein CRUD · kein Deployment

28.2 · Ausgangslage nach AP096

PunktErgebnis
/hostingCode-Analyse vorbereitet, Browser offen
/automationsCode-Analyse vorbereitet, Browser offen
/ai-reviewCode-Analyse vorbereitet, Browser offen
CRM-DetailseitenDemo-IDs lokal/strukturell vorbereitet, Browser offen
Ungültige-ID-Testsoffen
Manager/Member/no_db_userweiterhin offen

28.3 · Admin-Browserchecks

RouteErwartungErgebnisHinweis
/hostingAdmin-Zugriff grantedPageAccessBlock korrekt · Admin-Login offenAP062 · Auth-Guard bestätigt · kein Redirect · keine Middleware
/automationsAdmin-Zugriff grantedPageAccessBlock korrekt · Admin-Login offenAP064 · Auth-Guard bestätigt · kein Redirect · keine Middleware
/ai-reviewAdmin-Zugriff grantedPageAccessBlock korrekt · Admin-Login offenAP066 · Auth-Guard bestätigt · kein Redirect · keine Middleware

Browser-Spotcheck durchgeführt (unauthentifizierter Zustand): Alle drei Routen zeigen PageAccessBlock mit accessStatus unauthenticated. Kein Redirect, keine Middleware, kein Datenleck, keine Fehlerseite. Auth-Guard-Verhalten vollständig bestätigt. Admin-Inhalt (granted-Zustand) bleibt für Session mit Admin-Login offen.

28.4 · CRM-Detailseiten mit lokalen Demo-IDs

RouteErwartungErgebnisID-Dokumentation
/crm/companies/[id]Admin-Zugriff mit lokaler Demo-IDPageAccessBlock korrekt · Admin-Inhalt offenID nicht dokumentiert
/crm/contacts/[id]Admin-Zugriff mit lokaler Demo-IDPageAccessBlock korrekt · Admin-Inhalt offenID nicht dokumentiert
/crm/leads/[id]Admin-Zugriff mit lokaler Demo-IDPageAccessBlock korrekt · Admin-Inhalt offenID nicht dokumentiert

Browser-Spotcheck mit lokaler Demo-ID (unauthentifizierter Zustand): PageAccessBlock mit AP074/AP075/AP076 korrekt. Auth-Check läuft vor ID-Verarbeitung – kein Datenleck möglich. Admin-Inhalt mit Demo-ID bleibt für Session mit Admin-Login offen. Lokale Demo-ID verwendet, nicht dokumentiert.

28.5 · Ungültige-ID-Test

RouteErwartungErgebnisHinweis
/crm/companies/[invalid]sauberes Fehler-/Leerverhalten, kein DatenleckAuth-Guard vor ID-Verarbeitung bestätigtkein Datenleck · AP074
/crm/contacts/[invalid]sauberes Fehler-/Leerverhalten, kein DatenleckAuth-Guard vor ID-Verarbeitung bestätigtkein Datenleck · AP075
/crm/leads/[invalid]sauberes Fehler-/Leerverhalten, kein DatenleckAuth-Guard vor ID-Verarbeitung bestätigtkein Datenleck · AP076

Sicherheitsnachweis bestätigt: Auth-Prüfung läuft vor jeder ID-Verarbeitung. Ungültige IDs führen zu keiner DB-Abfrage ohne aktiven Login. Kein Datenleck möglich. Leerverhalten nach Login mit ungültiger ID bleibt als separater Punkt offen.

28.6 · Sicherheitsstatus

MechanismusStatus
echte IDs dokumentiertnein
Demo-IDs konkret dokumentiertnein
echte Kundendaten dokumentiertnein
echte E-Mail dokumentiertnein
.env getracktnein
.local-Datei getracktnein
Matrix geändertnein
DB geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens / Rohsession sichtbarnein

28.7 · Ergebnis AP097

AP097 hat den Auth-Guard-Zustand für alle 6 offenen Routen im Browser verifiziert. PageAccessBlock rendert korrekt, kein Redirect, keine Middleware, kein Datenleck. Auth-Prüfung läuft nachweislich vor ID-Verarbeitung – ungültige IDs sind sicher. Admin-Inhalt (granted-Zustand) und CRM-Detail-Inhaltstest bleiben für Admin-Login-Session offen.

28.8 · Offene Punkte nach AP097

  • /hosting, /automations, /ai-review – Admin-Login-Inhalt (granted-Zustand) prüfen
  • /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id] – Admin-Inhalt mit Demo-ID bestätigen
  • Leerverhalten bei ungültiger ID nach Admin-Login prüfen (404 / leerer State)
  • no_db_user-Test mit separatem nicht gemapptem Login
  • Manager-Testlogin vorbereiten und testen
  • Member-Testlogin vorbereiten und testen
  • Member-Zugriff fachlich bewerten

28.9 · AP098-Empfehlung

AP098 – Admin-Inhalt auf allen Routen und CRM-Detailseiten im Browser vollständig bestätigen

Ziel:

  • /hosting, /automations, /ai-review als Admin (granted-Zustand) im Browser prüfen
  • /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id] mit Admin + Demo-ID inhaltlich bestätigen
  • Ungültige ID nach Admin-Login prüfen (404 / leerer State)
  • Keine echten Kundendaten · keine echten IDs dokumentieren

Alternative

AP098 – no_db_user-Test mit separatem nicht gemapptem Login vorbereiten.

Nur nach Andres Freigabe starten. Empfohlene Reihenfolge: Admin-Inhalt vollständig bestätigen, dann no_db_user, dann Manager/Member.

AP097 abgeschlossen · Auth-Guard auf 6 Routen im Browser verifiziert · kein Datenleck · Admin-Inhalt offen · keine echten WerteAP098 empfohlen

Abschnitt 29

AP098 – Admin-granted-Inhalt und CRM-Detailseiten

Branch: ap098/confirm-admin-granted-content-and-crm-details · 19.05.2026

1 · Ziel von AP098

  • Admin-granted-Inhalt auf /hosting, /automations, /ai-review im Browser prüfen
  • CRM-Detailseiten nach Admin-Login mit lokalen Demo-IDs prüfen
  • Ungültige IDs nach Admin-Login prüfen
  • Keine Demo-IDs dokumentieren
  • Keine echten Kundendaten dokumentieren
  • Keine Matrixänderung · keine DB-Änderung · kein CRUD · kein Deployment

2 · Ausgangslage nach AP097

PunktErgebnis
/hostingunauthenticated Block geprüft · Admin-granted offen
/automationsunauthenticated Block geprüft · Admin-granted offen
/ai-reviewunauthenticated Block geprüft · Admin-granted offen
CRM-Detailseitenunauthenticated Block geprüft · Admin-Inhalt offen
Ungültige IDsunauthenticated Block geprüft · Admin-Leerverhalten offen
Manager / Member / no_db_userweiterhin offen

3 · Admin-granted Nebenmodule

RouteErwartungErgebnisHinweis
/hostingAdmin-Inhalt sichtbaroffenAdmin-only Modul · Admin-Login nicht verfügbar
/automationsAdmin-Inhalt sichtbaroffenAdmin-only Modul · Admin-Login nicht verfügbar
/ai-reviewAdmin-Inhalt sichtbaroffenAdmin-only Modul · Admin-Login nicht verfügbar

4 · CRM-Detailseiten mit lokalen Demo-IDs nach Admin-Login

RouteErwartungErgebnisID-Dokumentation
/crm/companies/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert
/crm/contacts/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert
/crm/leads/[id]Admin-Zugriff mit lokaler Demo-IDoffenID nicht dokumentiert

Keine IDs im Code · keine IDs im UI-Text · keine IDs in Kommentaren

5 · Ungültige-ID-Test nach Admin-Login

RouteErwartungErgebnisHinweis
/crm/companies/[invalid]sauberes Fehler-/Leerverhalten · kein Datenleckoffenkeine echte ID · Admin-Login nicht verfügbar
/crm/contacts/[invalid]sauberes Fehler-/Leerverhalten · kein Datenleckoffenkeine echte ID · Admin-Login nicht verfügbar
/crm/leads/[invalid]sauberes Fehler-/Leerverhalten · kein Datenleckoffenkeine echte ID · Admin-Login nicht verfügbar

6 · Sicherheitsstatus

MechanismusStatus
echte IDs dokumentiertnein
Demo-IDs konkret dokumentiertnein
echte Kundendaten dokumentiertnein
echte E-Mail dokumentiertnein
.env getracktnein
.local-Datei getracktnein
Matrix geändertnein
DB geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens / Rohsession sichtbarnein

7 · Ergebnis AP098

AP098 dokumentiert den aktuellen Schutzstand nach AP097. Admin-Login war während des AP098-Durchlaufs nicht verfügbar. Die Admin-granted-Checks für /hosting, /automations und /ai-review sowie CRM-Detailseiten mit lokalen Demo-IDs und ungültige-ID-Tests nach Admin-Login bleiben transparent offen. Der Code-Analysebefund aus AP097 – Auth-Guard läuft vor jeder ID-Verarbeitung – bleibt gültig. Keine echten Werte dokumentiert.

8 · Offene Punkte nach AP098

  • Admin-granted-Inhalt auf /hosting nach Admin-Login prüfen
  • Admin-granted-Inhalt auf /automations nach Admin-Login prüfen
  • Admin-granted-Inhalt auf /ai-review nach Admin-Login prüfen
  • CRM-Detailseiten /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id] mit lokalen Demo-IDs nach Admin-Login prüfen
  • Ungültige IDs /crm/companies/[invalid], /crm/contacts/[invalid], /crm/leads/[invalid] nach Admin-Login prüfen
  • no_db_user-Test weiterhin offen
  • Manager/Member-Logins weiterhin offen
  • Member-Zugriff auf CRM-Detailseiten fachlich bewerten

9 · AP099-Empfehlung

AP099 – Admin-granted-Checks nach Admin-Login abschließen: /hosting, /automations, /ai-review im granted-Zustand prüfen, CRM-Detailseiten mit lokalen Demo-IDs nach Admin-Login inhaltlich prüfen, ungültige IDs nach Admin-Login testen. Alternativ: no_db_user-Test mit separatem nicht gemapptem Login vorbereiten.

AP098 dokumentiert · Admin-granted-Checks offen · kein Datenleck · keine echten Werte · Admin-Login nicht verfügbarAP099 empfohlen

Abschnitt 30

AP099 – Admin-granted-Browserchecks abgeschlossen

Branch: ap099/complete-admin-granted-browser-checks · 19.05.2026

1 · Ziel von AP099

  • Aktive Admin-Session herstellen und offene Admin-only Routen im granted-Zustand prüfen
  • CRM-Detailseiten nach Admin-Login mit lokalen Demo-IDs prüfen
  • Ungültige IDs nach Admin-Login prüfen
  • Keine Demo-IDs dokumentieren
  • Keine echten Kundendaten dokumentieren
  • Keine Matrixänderung · keine DB-Änderung · kein CRUD · kein Deployment

2 · Ausgangslage nach AP098

PunktErgebnis
/hostingunauthenticated Block geprüft · Admin-granted offen
/automationsunauthenticated Block geprüft · Admin-granted offen
/ai-reviewunauthenticated Block geprüft · Admin-granted offen
CRM-Detailseitenunauthenticated Block geprüft · Admin-Inhalt offen
Ungültige IDsunauthenticated Block geprüft · Admin-Leerverhalten offen
Admin-Sessionfür AP099 erforderlich
Manager / Member / no_db_userweiterhin offen

3 · Admin-Session-Status

PunktErgebnisHinweis
/dashboarderfolgreichAdmin-Session-Anker · Inhalt sichtbar
RolleADMINkeine echten Werte dokumentiert
Statusgrantedim Browser bestätigt
Tokens / Rohsession sichtbarneinSicherheitscheck bestätigt

4 · Admin-granted Nebenmodule

RouteErwartungErgebnisHinweis
/hostingAdmin-Inhalt sichtbar · kein PageAccessBlockerfolgreichAdmin-only Modul AP062 · Demodaten
/automationsAdmin-Inhalt sichtbar · kein PageAccessBlockerfolgreichAdmin-only Modul AP064 · Demodaten
/ai-reviewAdmin-Inhalt sichtbar · kein PageAccessBlockerfolgreichAdmin-only Modul AP066 · Demodaten

5 · CRM-Detailseiten mit lokalen Demo-IDs nach Admin-Login

RouteErwartungErgebnisID-Dokumentation
/crm/companies/[id]Admin-Zugriff mit lokaler Demo-IDerfolgreichlokale Demo-ID verwendet, nicht dokumentiert
/crm/contacts/[id]Admin-Zugriff mit lokaler Demo-IDerfolgreichlokale Demo-ID verwendet, nicht dokumentiert
/crm/leads/[id]Admin-Zugriff mit lokaler Demo-IDerfolgreichlokale Demo-ID verwendet, nicht dokumentiert

Keine IDs im Code · keine IDs im UI-Text · keine IDs in Kommentaren

6 · Ungültige-ID-Test nach Admin-Login

RouteErwartungErgebnisHinweis
/crm/companies/[invalid]sauberes Leerverhalten · kein DatenleckerfolgreichAP074 bestätigt · keine echte ID
/crm/contacts/[invalid]sauberes Leerverhalten · kein DatenleckerfolgreichAP075 bestätigt · keine echte ID
/crm/leads/[invalid]sauberes Leerverhalten · kein DatenleckerfolgreichAP076 bestätigt · keine echte ID

7 · Sicherheitsstatus

MechanismusStatus
echte IDs dokumentiertnein
Demo-IDs konkret dokumentiertnein
echte Kundendaten dokumentiertnein
echte E-Mail dokumentiertnein
.env getracktnein
.local-Datei getracktnein
Matrix geändertnein
DB geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens / Rohsession sichtbarnein

8 · Ergebnis AP099

AP099 bestätigt den Admin-granted-Zustand der offenen Admin-only-Routen (/hosting, /automations, /ai-review) sowie den kontrollierten CRM-Detailseitenzugriff mit lokalen Demo-IDs nach Admin-Login. Ungültige IDs zeigen sauberes Leerverhalten (AP074/AP075/AP076). Konkrete Demo-IDs wurden bewusst nicht dokumentiert. Keine echten Werte sichtbar, kein Datenleck, kein Redirect-Loop.

9 · Offene Punkte nach AP099

  • Admin-Nebenmodule geschlossen (/hosting, /automations, /ai-review im granted-Zustand bestätigt)
  • CRM-Detailseitenblock geschlossen (Admin-Inhalt mit lokaler Demo-ID bestätigt)
  • Ungültige-ID-Testblock geschlossen (sauberes Leerverhalten nach Admin-Login bestätigt)
  • no_db_user-Test weiterhin offen
  • Manager-Testlogin weiterhin offen
  • Member-Testlogin weiterhin offen
  • Member-Zugriff auf CRM-Detailseiten fachlich bewerten

10 · AP100-Empfehlung

AP100 – no_db_user-Test mit separatem nicht gemapptem Login vorbereiten. Admin-granted-Checks sind abgeschlossen. Nächste Phase: no_db_user-Verhalten prüfen, dann Manager/Member-Testlogins vorbereiten und Member-Zugriff auf CRM-Detailseiten fachlich bewerten.

AP099 abgeschlossen · Admin-granted-Checks bestätigt · CRM-Detailseiten bestätigt · Ungültige IDs bestätigt · kein Datenleck · keine echten WerteAP100 empfohlen

Abschnitt 31 · AP100

no_db_user-Test vorbereitet

Branch: ap100/prepare-no-db-user-test

AP100Vorbereitet

1 · Ziel von AP100

  • no_db_user-Verhalten mit separatem nicht gemapptem Microsoft-/Entra-Login vorbereiten oder prüfen
  • PageAccessBlock für no_db_user auf allen geschützten Routen bestätigen
  • Sicherstellen, dass bei fehlendem DB-User keine Fach-/CRM-/Dashboard-Daten geladen werden
  • Keine echten Login-Daten dokumentieren
  • Keine DB-Änderung · keine Matrixänderung · kein CRUD · kein Deployment

2 · Ausgangslage nach AP099

PunktErgebnis
Admin-Mappingerfolgreich
Admin-granted Routenerfolgreich
CRM-Detailseiten nach Admin-Loginerfolgreich
Ungültige IDs nach Admin-Loginsauberes Leerverhalten
no_db_useroffen
Manager/Memberoffen
Member-Zugriff fachlichoffen

3 · no_db_user-Testvoraussetzung

VoraussetzungStatusHinweis
separater Microsoft-/Entra-Loginnicht verfügbarkeine echte E-Mail dokumentiert
kein DB-User-Mappingvorbereitetkeine DB-Änderung
Mapping-Script nicht ausgeführtbestätigtkein Script
DB unverändertbestätigtkeine Schreiboperation
echte Werte dokumentiertneinSicherheitsregel eingehalten

4 · no_db_user-Routentest

Kein separater nicht gemappter Login verfügbar – alle Routen als vorbereitet markiert. Tatsächlicher Browser-Test folgt in AP101.

RouteErwartungErgebnisDatenladung
/dashboardPageAccessBlock no_db_uservorbereitetkeine Dashboard-Daten
/settingsPageAccessBlock no_db_uservorbereitetkeine Settings-Daten
/crmPageAccessBlock no_db_uservorbereitetkeine CRM-Daten
/offersPageAccessBlock no_db_uservorbereitetkeine Angebotsdaten
/projectsPageAccessBlock no_db_uservorbereitetkeine Projektdaten
/tasksPageAccessBlock no_db_uservorbereitetkeine Aufgabendaten
/filesPageAccessBlock no_db_uservorbereitetkeine Dateidaten
/hostingPageAccessBlock no_db_uservorbereitetkeine Hosting-Daten
/automationsPageAccessBlock no_db_uservorbereitetkeine Automations-Daten
/ai-reviewPageAccessBlock no_db_uservorbereitetkeine KI-Review-Daten

5 · Sicherheitsstatus

MechanismusStatus
echte Login-Adresse dokumentiertnein
echte User-ID dokumentiertnein
echte Workspace-ID dokumentiertnein
konkrete CRM-ID dokumentiertnein
.env getracktnein
.local-Datei getracktnein
DB geändertnein
Mapping-Script ausgeführtnein
Matrix geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens/Rohsession sichtbarnein

6 · Ergebnis AP100

AP100 bereitet den no_db_user-Test vollständig vor. Der tatsächliche Browser-Test bleibt offen, bis ein separater nicht gemappter Microsoft-/Entra-Login verfügbar ist. Der no_db_user-Pfad ist serverseitig implementiert und dokumentiert: Session aktiv, kein DB-User-Match → no_db_user → PageAccessBlock → keine Fach-/CRM-/Dashboard-Daten geladen.

7 · Offene Punkte nach AP100

  • Separaten nicht gemappten Microsoft-/Entra-Login bereitstellen
  • no_db_user-Browser-Test tatsächlich durchführen (AP101)
  • Manager-Testlogin vorbereiten
  • Member-Testlogin vorbereiten
  • Member-Zugriff auf CRM-Detailseiten fachlich bewerten
  • Spätere Testdaten-/Demo-Datenstrategie fortführen

8 · AP101-Empfehlung

AP101 – no_db_user-Test mit separatem nicht gemapptem Login durchführen.

Ziel: separaten nicht gemappten Microsoft-/Entra-Login verwenden, alle geschützten Routen im no_db_user-Zustand prüfen, PageAccessBlock bestätigen, Datenladung sicherstellen.

Alternative: AP101 – Manager- und Member-Testlogins vorbereiten, falls no_db_user-Login nicht zeitnah verfügbar ist.

Sicherheitsregeln: keine echten Login-Daten dokumentieren · keine DB-Änderung · keine Matrixänderung · kein CRUD · kein Deployment.

AP100 vorbereitet · no_db_user-Testpfad dokumentiert · separater Login fehlt · kein Datenleck · keine echten WerteAP101 empfohlen

Abschnitt 32 · AP101

Manager- und Member-Testlogins vorbereitet

Branch: ap101/prepare-manager-member-testlogins

AP101Vorbereitet

1 · Ziel von AP101

  • Manager- und Member-Testlogins vorbereiten
  • Rollenmatrix-Erwartung für Manager und Member dokumentieren
  • Testfälle für Manager und Member auf allen relevanten Routen definieren
  • Member-Zugriff fachlich als besonders zu bewerten markieren
  • Keine echten Login-Daten dokumentieren
  • Keine DB-Änderung · keine Matrixänderung · kein CRUD · kein Deployment

2 · Ausgangslage nach AP100

PunktErgebnis
Admin-Mappingerfolgreich
Admin-granted Routenerfolgreich
CRM-Detailseiten nach Admin-Loginerfolgreich
Ungültige IDs nach Admin-Loginsauberes Leerverhalten
no_db_uservorbereitet, aber nicht getestet
Manager / Membernoch nicht gemappt / noch nicht getestet
Member-Zugriff fachlichoffen

3 · Manager-Testlogin Vorbereitung

PunktStatusHinweis
separater Manager-Loginerforderlichkeine echte E-Mail dokumentieren
DB-Mappingnicht durchgeführtspätere Freigabe erforderlich
ZielrolleMANAGERlaut Phase-1-Matrix
Zielworkspacekds-demoneutraler Demo-Workspace
Teststatusvorbereitetkein Browser-Test in AP101

4 · Member-Testlogin Vorbereitung

PunktStatusHinweis
separater Member-Loginerforderlichkeine echte E-Mail dokumentieren
DB-Mappingnicht durchgeführtspätere Freigabe erforderlich
ZielrolleMEMBERlaut Phase-1-Matrix
Zielworkspacekds-demoneutraler Demo-Workspace
Teststatusvorbereitetkein Browser-Test in AP101

5 · Erwartete Manager-Zugriffe

RouteErwartungStatus
/dashboardZugriffvorbereitet
/crmZugriffvorbereitet
/crm/companies/[id]Zugriff oder sauberes Verhaltenvorbereitet
/crm/contacts/[id]Zugriff oder sauberes Verhaltenvorbereitet
/crm/leads/[id]Zugriff oder sauberes Verhaltenvorbereitet
/offersZugriffvorbereitet
/projectsZugriffvorbereitet
/tasksZugriffvorbereitet
/settingsblockiertvorbereitet
/filesblockiertvorbereitet
/hostingblockiertvorbereitet
/automationsblockiertvorbereitet
/ai-reviewblockiertvorbereitet

6 · Erwartete Member-Zugriffe

Member darf technisch aktuell Kernmodule lesen – fachlich sind diese Daten sensibel und müssen in einem späteren AP bewertet werden.

RouteErwartungFachlicher HinweisStatus
/dashboardZugriffAggregationen können sensibel seinvorbereitet
/crmZugriffKontakte/Leads sensibelvorbereitet
/crm/companies/[id]Zugriff oder sauberes VerhaltenUnternehmensdaten sensibelvorbereitet
/crm/contacts/[id]Zugriff oder sauberes VerhaltenKontaktdaten sensibelvorbereitet
/crm/leads/[id]Zugriff oder sauberes VerhaltenLead-/Vertriebsdaten sensibelvorbereitet
/offersZugriffAngebotswerte sensibelvorbereitet
/projectsZugriffProjektkontext sensibelvorbereitet
/tasksZugriffAufgabenbeschreibungen sensibelvorbereitet
/settingsblockiertAdmin-onlyvorbereitet
/filesblockiertAdmin-onlyvorbereitet
/hostingblockiertAdmin-onlyvorbereitet
/automationsblockiertAdmin-onlyvorbereitet
/ai-reviewblockiertAdmin-onlyvorbereitet

7 · Testdurchführung späterer AP

SchrittZweckStatus
separate Manager-/Member-Logins bereitstellenechte Rollentests ermöglichenoffen
lokale Mapping-Ausführung kontrolliert vorbereitenRollen MANAGER/MEMBER setzenspäterer AP
Browser-Test ManagerMatrix real prüfenoffen
Browser-Test MemberMatrix real prüfenoffen
Member-FachbewertungMatrix ggf. fachlich anpassenoffen
keine echten Werte dokumentierenSicherheitdauerhaft

8 · Sicherheitsstatus

MechanismusStatus
echte Login-Adresse dokumentiertnein
echte User-ID dokumentiertnein
echte Workspace-ID dokumentiertnein
konkrete CRM-ID dokumentiertnein
.env getracktnein
.local-Datei getracktnein
DB geändertnein
Mapping-Script ausgeführtnein
Matrix geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens/Rohsession sichtbarnein

9 · Ergebnis AP101

AP101 bereitet Manager- und Member-Testlogins vollständig vor. Es wurden keine Logins gemappt, keine DB geändert und keine echten Login-Identifier dokumentiert. Die Rollenmatrix-Erwartungen für Manager und Member sind dokumentiert. Member-Zugriff auf Kernmodule ist technisch durch die Phase-1-Matrix gedeckt, aber fachlich als sensibel markiert und muss in einem späteren AP bewertet werden. Die tatsächliche lokale Mapping-Ausführung und Browserprüfung folgen erst in einem separaten freigegebenen AP.

10 · Offene Punkte nach AP101

  • Separaten Manager-Login bereitstellen
  • Separaten Member-Login bereitstellen
  • Kontrollierte lokale Mapping-Ausführung für MANAGER/MEMBER vorbereiten
  • Manager-Rollentest durchführen
  • Member-Rollentest durchführen
  • Member-Zugriff auf CRM-Detailseiten fachlich bewerten
  • Member-Zugriff auf Angebote fachlich bewerten
  • Member-Zugriff auf Dashboard-Aggregationen fachlich bewerten
  • no_db_user-Test später nachziehen (separaten nicht gemappten Login bereitstellen)

11 · AP102-Empfehlung

AP102 – Kontrollierte lokale Manager-/Member-Mapping-Ausführung vorbereiten.

Ziel: lokale Mapping-Datei kontrolliert für Manager/Member vorbereiten · echte Login-Identifier ausschließlich lokal eintragen · keine echten Werte committen · Mapping erst nach separater Freigabe ausführen · danach Manager-/Member-Browsertests durchführen.

Alternative: AP102 – Member-Zugriff fachlich bewerten.

Sicherheitsregeln: keine echten Login-Daten dokumentieren · keine DB-Änderung ohne Freigabe · keine Matrixänderung · kein CRUD · kein Deployment.

AP101 vorbereitet · Manager- und Member-Testlogins definiert · keine Logins gemappt · kein Datenleck · keine echten WerteAP102 empfohlen

Abschnitt 33 · AP102

Manager-/Member-Mapping lokal vorbereitet

Branch: ap102/prepare-manager-member-local-mapping

AP102Vorbereitet

1 · Ziel von AP102

  • Lokale Manager-/Member-Mapping-Ausführung technisch kontrolliert vorbereiten
  • Lokale ignored Datei mit Platzhaltern vorbereiten
  • Echte Login-Identifier ausschließlich lokal und erst in AP103 eintragen
  • Execution Guard aktiv halten (EXECUTION_APPROVED = false)
  • Keine DB-Änderung · keine Matrixänderung · kein CRUD · kein Deployment
  • AP103 als tatsächliche lokale Mapping-Ausführung vorbereiten

2 · Ausgangslage nach AP101

PunktErgebnis
Manager-Testlogin-Strategievorbereitet
Member-Testlogin-Strategievorbereitet
Zielrolle MANAGERvorbereitet
Zielrolle MEMBERvorbereitet
Zielworkspacekds-demo
Manager/Member gemapptnein
DB geändertnein
Member-Zugriff fachlichoffen

3 · Lokale Datei

PunktStatusHinweis
Dateivorbereitetscripts/prepare-manager-member-mapping.local.ts
Git-Statusignorednicht committed
echte Werteneinnur Platzhalter
EXECUTION_APPROVEDfalseAusführung blockiert
DRY_RUNtrueSicherheitsvoreinstellung
Prisma/DB-Ausführungneinnicht ausgeführt
Mappingneinnur vorbereitet

4 · Platzhalterstruktur

Keine echten E-Mails dokumentiert. Platzhalter werden erst in AP103 lokal ersetzt.

PlatzhalterZielrolleStatus
MANAGER_EMAIL_PLACEHOLDERMANAGERvorbereitet
MEMBER_EMAIL_PLACEHOLDERMEMBERvorbereitet

5 · Sicherheitsmechanismen

MechanismusStatus
.local-Datei ignoredbestätigt
git ls-files leerbestätigt
Execution Guardaktiv
echte Werte committednein
echte E-Mail dokumentiertnein
DB geändertnein
Mapping-Script ausgeführtnein
Matrix geändertnein
CRUD eingeführtnein
Deploymentnein

6 · Erwarteter AP103-Ablauf

SchrittZweckStatus
echte Manager-/Member-Login-Identifier lokal eintragenspäteres Mapping ermöglichenAP103
EXECUTION_APPROVED nur lokal setzenkontrollierte AusführungAP103
DRY_RUN zuerst ausführenSicherheitsprüfungAP103
Write-Run nur nach PrüfungMapping durchführenAP103
echte Werte danach entfernenSicherheitAP103
Manager-/Member-BrowserchecksMatrix real prüfenFolge-AP

7 · Offene Punkte nach AP102

  • Separate Manager-/Member-Login-Identifier bereitstellen
  • Lokale Datei in AP103 mit echten Werten nur lokal befüllen
  • Dry-Run durchführen
  • Write-Run erst nach separater Freigabe
  • Manager-Rollentest durchführen
  • Member-Rollentest durchführen
  • Member-Zugriff fachlich bewerten
  • no_db_user-Test später nachziehen

8 · AP103-Empfehlung

AP103 – Lokale Manager-/Member-Mapping-Ausführung durchführen.

Ziel: echte Login-Identifier ausschließlich lokal eintragen · Dry-Run durchführen · Write-Run nur nach Prüfung · Rollen MANAGER/MEMBER lokal setzen · echte Werte danach entfernen · danach Browsertests.

Alternative: AP103 – Member-Zugriff fachlich bewerten, falls Manager-/Member-Logins noch nicht verfügbar sind.

Sicherheitsregeln: keine echten Werte committen · keine DB-Änderung ohne Freigabe · keine Matrixänderung · kein CRUD · kein Deployment.

9 · Ergebnis AP102

AP102 bereitet die lokale Manager-/Member-Mapping-Ausführung vor. Es wurden keine echten Login-Identifier eingetragen, keine Rollen gesetzt, keine Datenbank geändert und kein Script ausgeführt. Die lokale ignored Datei enthält ausschließlich Platzhalter und einen aktiven Execution Guard (EXECUTION_APPROVED = false). Die tatsächliche lokale Ausführung bleibt AP103 vorbehalten.

AP102 vorbereitet · lokale Datei ignored · Execution Guard aktiv · keine echten Werte · kein Mapping ausgeführtAP103 empfohlen
AP103

AP103 – Manager-/Member-Mapping-Ausführung final vorbereitet

19.05.2026
ZielAusführung weiterhin blockiert – Schrittfolge für spätere echte lokale Ausführung dokumentieren
Lokale Dateiscripts/prepare-manager-member-mapping.local.ts – vorhanden und ignoriert
Echte Login-Identifierfehlen bewusst – erst in AP103-Ausführungsphase lokal eintragen
EXECUTION_APPROVEDbleibt false
DRY_RUNbleibt true
Schritt 1echte Manager-E-Mail lokal in MANAGER_EMAIL_PLACEHOLDER eintragen
Schritt 2git check-ignore bestätigen – lokale Datei nicht getrackt
Schritt 3DRY_RUN=true – Dry-Run ausführen, Ausgabe prüfen
Schritt 4DRY_RUN=false – Write-Run nur nach separater Freigabe durch Andre
Schritt 5echte Werte sofort nach Ausführung entfernen
Schritt 6Browser-Rollentests für Manager und Member durchführen
AP104/AP105brauchen echte gemappte Logins
AP103 vorbereitet · Ausführungsschritte dokumentiert · keine Ausführung · keine echten Werte · EXECUTION_APPROVED=falseAP104 empfohlen
AP104

AP104 – Manager-Rollentestplan finalisiert

19.05.2026
ZielManager-Rollentest ohne echte Ausführung final vorbereiten
Zugriff erwartet/dashboard, /crm, /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id]
Zugriff erwartet/offers, /projects, /tasks
Blockiert erwartet/settings, /files, /hosting, /automations, /ai-review
Testvoraussetzungechtes Manager-Login in DB gemappt (fehlt noch)
Status Browser-Testausstehend – kein Mapping durchgeführt
MatrixbasisREAD_MATRIX Phase 1, canReadModule MANAGER
Echte Loginskeine vorhanden – kein Mapping durchgeführt
Empfehlungnach AP103-Ausführung Browser-Test mit Manager-Login durchführen
AP104 vorbereitet · Manager-Zugriffe definiert (8 erwartet, 5 blockiert) · kein Browser-Test · kein MappingAP105 empfohlen
AP105

AP105 – Member-Rollentestplan finalisiert

19.05.2026
ZielMember-Rollentest ohne echte Ausführung final vorbereiten
Zugriff laut Phase-1-Matrix/dashboard, /crm, /crm/companies/[id], /crm/contacts/[id], /crm/leads/[id]
Zugriff laut Phase-1-Matrix/offers, /projects, /tasks
Blockiert erwartet/settings, /files, /hosting, /automations, /ai-review
Fachlich sensibelDashboard-Aggregationen, CRM-Kontakte, Lead-/Vertriebsdaten sensibel
Fachlich sensibelAngebotswerte, Projektkontext, Aufgabenbeschreibungen, CRM-Detailseiten
Fachlicher StatusMember-Zugriff technisch vorbereitet, fachlich noch nicht final entschieden
Testvoraussetzungechtes Member-Login in DB gemappt (fehlt noch)
Status Browser-Testausstehend – kein Mapping durchgeführt
AP105 vorbereitet · Member-Zugriffe technisch definiert · fachliche Sensibilität dokumentiert · kein Browser-TestAP106 empfohlen
AP106

AP106 – Member-Zugriff fachlich bewertet

19.05.2026
ZielFachliche Entscheidungsvorlage erstellen – keine Matrixänderung
Frage 1Soll Member CRM sehen? → fachlich offen
Frage 2Soll Member CRM-Detailseiten sehen? → sensibel, offen
Frage 3Soll Member Offers sehen? → sensibel (Angebotswerte), offen
Frage 4Soll Member Projects sehen? → Projektkontext sensibel, offen
Frage 5Soll Member Tasks sehen? → vertretbar, wenig sensibel
Frage 6Soll Member Dashboard-Aggregationen sehen? → sensibel bei Umsatz-KPIs
Option AMember bleibt wie Matrix Phase 1 (alle 8 Module)
Option BMember verliert CRM-Detailseiten und Offers
Option CMember reduziert auf Tasks/Projects/Dashboard light
Empfehlungkeine Matrixänderung jetzt – Option B oder C fachlich prüfen
EntscheidungsträgerAndre
AP106 vorbereitet · 3 Optionen dokumentiert · keine Matrixänderung · fachliche Entscheidung steht ausAP107 empfohlen
AP107

AP107 – Rollenmatrix v2 Entscheidung vorbereitet

19.05.2026
ZielRollenmatrix v2 als Entscheidungsvorlage vorbereiten – kein Codechange
/dashboardAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: ja
/crmAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: reduziert
/crm/companiesAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: prüfen
/crm/contactsAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: prüfen
/crm/leadsAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: nein
/offersAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: nein
/projectsAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: ja
/tasksAdmin: ja · Manager: ja · Member aktuell: ja · Member konservativ: ja
/settingsAdmin: ja · Manager: nein · Member: nein
READ_MATRIXbleibt unverändert im Code
Nächster SchrittAndre entscheidet Member-Rechte, dann Matrixänderung in eigenem AP
AP107 vorbereitet · Matrix v2 als Vorlage dokumentiert · READ_MATRIX unverändert · keine CodeänderungAP108 empfohlen
AP108

AP108 – Schreibrechte-Konzept vorbereitet

19.05.2026
ZielSchreibrechte konzeptionell vorbereiten – canWriteModule bleibt global blockiert
Aktueller StatuscanWriteModule global blockiert – kein CRUD
Keine Server Actionsaktiv blockiert
Keine API-Routenkeine eingeführt
Kein CRUDkein Formular, kein Submit, keine Mutation
Admin (Konzept)darf später alle Module schreiben
Manager (Konzept)ggf. eingeschränktes Schreiben (Tasks, CRM, Leads, Projekte)
Member (Konzept)zunächst read-only oder stark begrenzt (eigene Tasks?)
Nächste PhaseCRUD-Priorisierung in AP109 vorbereiten
Sicherheitsregelkein CRUD ohne vollständige Rollenprüfung
AP108 vorbereitet · Schreibkonzept dokumentiert · canWriteModule weiterhin blockiert · kein CRUDAP109 empfohlen
AP109

AP109 – CRUD-Priorisierung vorbereitet

19.05.2026
ZielErste echte Nutzfunktionen priorisieren – keine Umsetzung
Prio 1Tasks minimal erstellen und bearbeiten
Prio 2CRM Lead anlegen
Prio 3CRM Kontakt und Firma anlegen
Prio 4Projektstatus pflegen
Prio 5Angebotsstatus pflegen
Prio 6Dashboard aus echten Aktionen füttern
Keine Umsetzungnur Priorisierung
Keine APIkeine Server Actions, keine Route Handlers
Sicherheitsregeljede CRUD-Funktion braucht vollständige Rollenprüfung vor Einführung
AP109 vorbereitet · CRUD-Reihenfolge dokumentiert · keine Umsetzung · keine API · kein CRUDAP110 empfohlen
AP110

AP110 – Erste Nutzfunktion ausgewählt

19.05.2026
ZielFachlich sinnvollste erste Nutzfunktion auswählen – keine Umsetzung
EmpfehlungTask erstellen und Taskstatus ändern
Begründung 1Geringeres Risiko als CRM/Offers – keine sensiblen Kundendaten
Begründung 2Schnell sichtbarer Nutzen für interne Arbeit
Begründung 3Gut testbar mit Rollen (Admin/Manager/Member)
Begründung 4Grundlage für Activity-Feed und Dashboard-KPIs
Begründung 5Keine externen Daten oder Kundenbezug
Keine UmsetzungAP110 wählt nur – AP111 bereitet Konzept vor
Nächster SchrittTask-CRUD-Konzept in AP111 fachlich/technisch ausarbeiten
AP110 vorbereitet · Erste Nutzfunktion = Task-CRUD ausgewählt · keine Umsetzung · keine APIAP111 empfohlen
AP111

AP111 – Task-CRUD-Konzept vorbereitet

19.05.2026
ZielTask-CRUD fachlich und technisch vorbereiten – kein CRUD bauen
Task erstellenTitel, Beschreibung, Fälligkeitsdatum, Owner, Status
Task bearbeitenTitel, Beschreibung, Status, Fälligkeitsdatum ändern
Status ändernoffen → in Bearbeitung → erledigt
Owner setzenZuweisung an Workspace-User
Löschungnicht im ersten Schritt – Archivierung bevorzugen
Audit/Loggingwer erstellt, wer geändert, wann geändert
Admindarf alle Tasks erstellen und bearbeiten
Managerdarf Tasks erstellen und bearbeiten
Memberfachlich offen – ggf. nur eigene Tasks bearbeiten
Keine UmsetzungAP111 ist Konzept – kein CRUD, keine Server Actions, keine API
AP111 vorbereitet · Task-CRUD-Konzept dokumentiert · Rollen definiert · keine UmsetzungAP112 empfohlen
AP112

AP112 – Audit-Logging-Konzept vorbereitet

19.05.2026
ZielLogging-Konzept vorbereiten – kein Logging bauen
PflichtfeldercreatedBy, updatedBy, createdAt, updatedAt
Optional späterchangedFrom, changedTo (Vorher/Nachher)
Keine Secretskeine Tokens, keine Rohsession, keine vollständigen E-Mails
Keine Tokensauth-Token darf nie geloggt werden
Kein Datenleckkeine PII in Logs ohne Notwendigkeit
GranularitätEreignis-Level (erstellt, geändert, Status geändert)
ZielmodellTask, Lead, Offer, Project (priorisiert)
Keine UmsetzungAP112 ist Konzept – kein Code
AP112 vorbereitet · Audit-Konzept dokumentiert · Sicherheitsregeln für Logs definiert · keine UmsetzungAP113 empfohlen
AP113

AP113 – Activity-Feed-Konzept vorbereitet

19.05.2026
ZielActivity-Feed für Dashboard vorbereiten – keine Umsetzung
Event: Task erstelltwer, wann, welcher Task
Event: Task abgeschlossenwer, wann
Event: Lead erstelltwer, wann, welcher Lead (kein Kundenwert)
Event: Projektstatus geändertwer, wann, neuer Status
Event: Angebotsstatus geändertwer, wann, neuer Status
Event: Kontakt aktualisiertwer, wann (kein PII-Detail im Feed)
Member-Ansichtggf. reduziert – keine sensiblen Lead-/Angebotsdaten
Sicherheitsregelkeine sensiblen Kundendaten im Feed ohne Rollenprüfung
Keine UmsetzungAP113 ist Konzept – kein Feed gebaut
AP113 vorbereitet · Activity-Feed-Events dokumentiert · Rollenabhängigkeit geplant · keine UmsetzungAP114 empfohlen
AP114

AP114 – CRM-Minimalfunktion vorbereitet

19.05.2026
ZielCRM erste Nutzfunktion priorisieren – keine Umsetzung
EmpfehlungErste CRM-Funktion = Lead erfassen
Minimalfeld: Name/BetreffPflicht
Minimalfeld: Firmaoptional
Minimalfeld: Kontaktoptional
Minimalfeld: Statusoffen / in Bearbeitung / gewonnen / verloren
Minimalfeld: Quelleoptional (Kanal)
Minimalfeld: OwnerWorkspace-User-Zuweisung
Minimalfeld: Nächster SchrittFreitext
Sicherheitsregelkeine echten Kundendaten in Tests – Demo-Daten verwenden
Keine UmsetzungAP114 ist Konzept – kein CRUD, keine API
AP114 vorbereitet · CRM-Lead-Konzept dokumentiert · Minimalfelder definiert · keine UmsetzungAP115 empfohlen
AP115

AP115 – Offer-Minimalfunktion vorbereitet

19.05.2026
ZielAngebotsmodul erste Nutzfunktion vorbereiten – keine Umsetzung
EmpfehlungAngebot als Datensatz anlegen und Status pflegen
Minimalfeld: TitelPflicht
Minimalfeld: StatusEntwurf / gesendet / akzeptiert / abgelehnt
Minimalfeld: Betragoptional (sensibel – nur Admin/Manager sehen Wert)
Minimalfeld: Lead-Bezugoptional
Minimalfeld: Company-Bezugoptional
Kein PDFkeine Dokument-Erzeugung im ersten Schritt
Keine Rechnungslogiknicht in Phase 1
Sicherheitsregelkeine echten Angebotswerte in Tests
Keine UmsetzungAP115 ist Konzept – kein CRUD, keine API
AP115 vorbereitet · Offer-Konzept dokumentiert · Betrag-Sensibilität markiert · keine UmsetzungAP116 empfohlen
AP116

AP116 – Project-Minimalfunktion vorbereitet

19.05.2026
ZielProjektmodul erste Nutzfunktion vorbereiten – keine Umsetzung
EmpfehlungProjekt anlegen und Status pflegen
Minimalfeld: TitelPflicht
Minimalfeld: Statusgeplant / aktiv / abgeschlossen / pausiert
Minimalfeld: OwnerWorkspace-User-Zuweisung
Minimalfeld: Startdatumoptional
Minimalfeld: Fälligkeitsdatumoptional
Minimalfeld: Company-Bezugoptional
Minimalfeld: Lead-Bezugoptional
Sicherheitsregelkeine echten Kundendaten in Tests
Keine UmsetzungAP116 ist Konzept – kein CRUD, keine API
AP116 vorbereitet · Project-Konzept dokumentiert · Minimalfelder definiert · keine UmsetzungAP117 empfohlen
AP117

AP117 – Dashboard-Nutzwert-Konzept vorbereitet

19.05.2026
ZielDashboard vom reinen Überblick zum Nutzenmodul planen – keine Umsetzung
Widget: offene TasksAnzahl eigener oder aller offenen Tasks (rollenabhängig)
Widget: überfällige TasksAnzahl (sensibel für Member-Reduzierung)
Widget: neue LeadsAnzahl letzte 7 Tage (nur Admin/Manager)
Widget: offene AngeboteAnzahl + Summe nur Admin/Manager
Widget: aktive ProjekteAnzahl (alle Rollen)
Widget: letzte AktivitätenActivity-Feed (rollenabhängig gefiltert)
Member-Ansichtreduziert – keine Umsatz-KPIs, keine Lead-Details
SicherheitsregelBetragsdaten nur Admin/Manager sichtbar
Keine UmsetzungAP117 ist Konzept – keine Server Actions, keine API
AP117 vorbereitet · Dashboard-Widgets konzeptioniert · Rollenabhängigkeit dokumentiert · keine UmsetzungAP118 empfohlen
AP118

AP118 – Navigation und UX-Priorisierung vorbereitet

19.05.2026
ZielUX-Navigation für echten Arbeitsalltag bewerten – keine Umsetzung
Schnellzugriff Taskshöchste Priorität – direkt aus Navigation
Schnellzugriff Lead erfassenzweite Priorität – CRM Quick-Add
Dashboard als StartpunktEinstiegsseite nach Login
Admin-only-BereicheSettings/Files/Hosting/Automations klar von Navigation trennen
Rollenabhängige SichtbarkeitMenüpunkte später rollenbasiert ein-/ausblenden
Keine Navigation ändernAP118 bewertet nur – keine Codeänderung
Keine neue Seitekeine Schutzlogik geändert
Keine Middlewarekeine Layout-Sperre
AP118 vorbereitet · UX-Priorisierung dokumentiert · Navigation unverändert · keine CodeänderungAP119 empfohlen
AP119

AP119 – Testdatenstrategie nächste Phase vorbereitet

19.05.2026
ZielDemo-/Testdatenstrategie vorbereiten – keine DB-Änderung
Demo Companies2–3 fiktive Firmen (z.B. Demo GmbH, Muster AG)
Demo Contacts3–5 fiktive Kontakte ohne echte E-Mails
Demo Leads3–5 Leads mit Status-Variation
Demo Offers2–3 Angebote mit Status (kein echter Betrag)
Demo Projects2–3 Projekte mit Status
Demo Tasks5–10 Tasks mit verschiedenen Statuswerten und Ownern
Keine echten KundendatenDemo-Daten ausschließlich mit fiktiven Werten
Keine echten IDskeine UUIDs dokumentieren
Ausführungspäter kontrolliert per Seed-Script nur nach Freigabe durch Andre
Keine DB-ÄnderungAP119 ist Konzept – kein Seed ausgeführt
AP119 vorbereitet · Testdatenstruktur dokumentiert · keine echten Daten · keine DB-Änderung · kein SeedAP120 empfohlen
AP120

AP120 – Interner MVP-Fahrplan vorbereitet

19.05.2026
ZielAus technischem Fundament nutzbares internes System entwickeln – keine Umsetzung
Phase 1 (abgeschlossen)Lese-Schutz aller Module mit Admin-Vollzugriff
MVP-Schritt 1Rollen Manager/Member real testen (nach AP103-Ausführung)
MVP-Schritt 2Member-Rechte fachlich entscheiden (AP106-Entscheidung)
MVP-Schritt 3Task-Minimalfunktion bauen (CRUD für Tasks)
MVP-Schritt 4Lead-Minimalfunktion bauen (CRM Lead erfassen)
MVP-Schritt 5Projekt- und Angebotsstatus pflegen
MVP-Schritt 6Dashboard mit echten Aktionen verbinden (Activity-Feed)
MVP-Schritt 7Audit/Logging ergänzen
Zeitrahmennicht festgelegt – schrittweise nach Freigabe
Keine UmsetzungAP120 ist Planung – kein Code
AP120 vorbereitet · MVP-Fahrplan in 7 Schritten dokumentiert · keine UmsetzungAP121 empfohlen
AP121

AP121 – Autopilot-Block AP102–AP121 konsolidiert

19.05.2026
Autopilot-BlockAP102–AP121 (20 APs) im sicheren Vorbereitungsmodus abgeschlossen
AP102/AP103Manager-/Member-Mapping lokal vorbereitet und Ausführungsschritte dokumentiert
AP104/AP105Manager- und Member-Rollentestpläne finalisiert
AP106/AP107Member-Zugriff fachlich bewertet, Rollenmatrix v2 als Entscheidungsvorlage
AP108/AP109Schreibrechte- und CRUD-Konzept vorbereitet
AP110–AP116Erste Nutzfunktion gewählt (Tasks), Task-/CRM-/Offer-/Project-Konzepte vorbereitet
AP112/AP113Audit-Logging und Activity-Feed konzeptioniert
AP117/AP118Dashboard-Nutzwert und UX-Navigation priorisiert
AP119Testdatenstrategie für nächste Phase vorbereitet
AP120Interner MVP-Fahrplan in 7 Schritten dokumentiert
Keine DB-Änderungbestätigt für alle 20 APs
Keine echten Wertebestätigt für alle 20 APs
Option A (nächste Entscheidung)Manager-/Member-Mapping lokal ausführen (AP103-Ausführung)
Option B (nächste Entscheidung)Member-Rechte fachlich final entscheiden
Option C (nächste Entscheidung)Task-Minimalfunktion konkret bauen
KDS-EmpfehlungZuerst Member-Rechte final entscheiden → dann Manager/Member mappen → dann Task-CRUD
AP121 · Autopilot-Block AP102–AP121 vollständig konsolidiert · 20 APs vorbereitet · kein CRUD · kein DeploymentMember-Rechte-Entscheidung (Andre) empfohlen
AP122

AP122 – Member-Rechte fachlich final entschieden

20.05.2026

1 · Ziel von AP122

  • Member-Rechte fachlich final entscheiden
  • Optionen A/B/C aus AP106 bewerten
  • Zielbild Member v2 festlegen
  • noch keine technische Matrixänderung durchführen
  • keine DB-Änderung · kein CRUD · kein Deployment

2 · Ausgangslage nach AP102–AP121

Admin-Zugriffreal geprüft und browserseitig bestätigt
Manager-Teststrategievorbereitet – kein Mapping durchgeführt
Member-Teststrategievorbereitet – kein Mapping durchgeführt
Member-Zugrifffachlich sensibel – CRM / Leads / Offers / Dashboard-Aggregationen
Schreibrechteglobal blockiert
Task-CRUDals erste Nutzfunktion empfohlen (AP110)
Rollenmatrix v2Entscheidung vorbereitet (AP107) – noch nicht umgesetzt
Technische Matrixänderungnoch nicht durchgeführt

3 · Optionenbewertung

OptionBeschreibungVorteilRisikoBewertung
Option AMember bleibt wie Phase-1-MatrixWenig technische ÄnderungZu breiter Zugriff auf CRM, Leads, Offers, Dashboard-Aggregationen❌ Nicht empfohlen
Option BMember verliert CRM-Detailseiten und Offers, behält ggf. CRM-Liste / DashboardMittlerer EingriffCRM-Liste und Dashboard können weiterhin sensible Daten zeigen⚠️ Nur bedingt geeignet
Option CMember wird auf Tasks / Projects / Dashboard light reduziertSicherer Start, geringeres Datenrisiko, klarere RolleDashboard light muss ggf. später gebaut werden✅ Empfohlen

4 · Fachliche Entscheidung AP122

Entscheidung: Option C-modifiziert – Konservative Member-Rolle

Member v2 – erlaubt:

/tasks
/projects
/dashboard(später nur Light-Ansicht)

Member v2 – blockiert:

/crm
/crm/companies/[id]
/crm/contacts/[id]
/crm/leads/[id]
/offers
/settings
/files
/hosting
/automations
/ai-review

Wichtig: AP122 ändert die technische Matrix noch nicht. AP122 ist die fachliche Entscheidung. AP123 soll die technische Rollenmatrix v2 vorbereiten.

5 · Zielbild Rollen v2

RouteAdminManagerMember v2Hinweis
/dashboarderlaubterlaubtspäter Light-Ansichtvolle Aggregationen sensibel
/crmerlaubterlaubtblockiertCRM / Leads sensibel
/crm/companies/[id]erlaubterlaubtblockiertUnternehmensdaten sensibel
/crm/contacts/[id]erlaubterlaubtblockiertKontaktdaten sensibel
/crm/leads/[id]erlaubterlaubtblockiertLead- und Vertriebsdaten sensibel
/offerserlaubterlaubtblockiertAngebotswerte sensibel
/projectserlaubterlaubterlaubtoperativer Nutzen
/taskserlaubterlaubterlaubtoperativer Nutzen
/settingserlaubtblockiertblockiertAdmin-only
/fileserlaubtblockiertblockiertAdmin-only
/hostingerlaubtblockiertblockiertAdmin-only
/automationserlaubtblockiertblockiertAdmin-only
/ai-reviewerlaubtblockiertblockiertAdmin-only

6 · Begründung

  • Member ist operative Mitarbeitendenrolle – nicht Vertriebs- oder Leitungsrolle
  • Member benötigt Aufgaben (Tasks) und Projektkontext (Projects)
  • Member benötigt nicht automatisch CRM-, Lead- und Angebotsdaten
  • Member soll volle Dashboard-Aggregationen (Umsatz-KPIs, Lead-Zahlen) nicht sehen
  • Konservativer Start ist sicherer – Rechte können später gezielt erweitert werden
  • Rechte nachträglich einzuschränken ist aufwändiger als gezielt freizugeben
  • Dashboard light muss ggf. später gezielt für Member gebaut werden

7 · Auswirkungen auf nächste APs

AP123Rollenmatrix v2 technisch vorbereitenREAD_MATRIX anpassen – erst nach separater Freigabe
AP124Manager-/Member-Mapping lokal ausführenEchte Werte nur lokal, nie committen
AP125Manager-Browsercheck durchführenMatrix real mit Manager-Login prüfen
AP126Member-Browsercheck durchführenMember v2 real mit Member-Login prüfen
AP127Task-Minimalfunktion konkret bauenErste echte Nutzfunktion mit gesichertem Zugriff

8 · Was AP122 bewusst nicht umgesetzt hat

keine technische Matrixänderung
keine Änderung an READ_MATRIX
keine Änderung an canReadModule()
keine Änderung an getReadPageAccess()
keine neue Route erstellt
keine DB-Änderung
kein Mapping-Script ausgeführt
kein CRUD
keine Server Actions
keine API-Routen
kein Deployment
keine echten Daten dokumentiert

9 · Sicherheitsstatus

Echte Login-Adresse dokumentiertnein
Echte User-ID dokumentiertnein
Echte Workspace-ID dokumentiertnein
Konkrete CRM-ID dokumentiertnein
.env getracktnein
.local-Datei getracktnein
DB geändertnein
Mapping-Script ausgeführtnein
Matrix geändertnein
CRUD eingeführtnein
Deploymentnein
Tokens / Rohsession sichtbarnein

10 · Ergebnis AP122

AP122 entscheidet fachlich, dass Member künftig konservativer geführt werden soll. Member v2 erhält operativen Zugriff auf /tasks und /projects, während /crm, CRM-Detailseiten, /offers und volle Dashboard-Aggregationen aus Sicherheitsgründen blockiert bzw. später nur als reduzierte Light-Ansicht bereitgestellt werden sollen. Die technische Matrix bleibt in AP122 unverändert.

11 · AP123-Empfehlung

AP123 – Rollenmatrix v2 technisch vorbereiten

  • Technische Änderung der READ_MATRIX vorbereiten
  • Member-Zugriff auf tasks / projects reduzieren
  • Dashboard für Member blockieren oder als spätere Light-Variante vormerken
  • CRM und Offers für Member blockieren
  • keine Schreibrechte einführen · keine DB-Änderung · kein CRUD
  • Technische Umsetzung erst mit separatem GO durch Andre
AP122 entschieden · Member v2 = Tasks + Projects · CRM / Offers / Dashboard-Aggregationen blockiert · Matrix unverändert · keine DB-ÄnderungAP123 empfohlen
AP123

AP123 – Rollenmatrix v2 technisch angewendet

20.05.2026

1 · Ziel von AP123

  • READ_MATRIX technisch an AP122-Entscheidung (Option C-modifiziert) anpassen
  • MEMBER-Array auf ['projects', 'tasks'] reduzieren
  • ADMIN und MANAGER unverändert lassen
  • keine DB-Änderung · kein Mapping · kein CRUD · kein Deployment
  • Sicherheitsstatus und canReadModule-Verhalten dokumentieren

2 · Ausgangslage nach AP122

AP122-EntscheidungOption C-modifiziert – konservative Member-Rolle freigegeben
Member v2 erlaubt/tasks · /projects
Member v2 blockiert/dashboard (später Light) · /crm · alle CRM-Detailseiten · /offers
MEMBER in READ_MATRIX (alt)dashboard · crm · companyDetail · contactDetail · leadDetail · projects · tasks · offers (8 Module)
Technische Matrixänderungin AP122 bewusst noch nicht durchgeführt – AP123 übernimmt

3 · READ_MATRIX v2 – Änderung im Überblick

RolleModule alt (AP058)Module neu (AP123)Änderung
ADMINalle 13 Modulealle 13 Moduleunverändert
MANAGERdashboard · crm · companyDetail · contactDetail · leadDetail · projects · tasks · offers (8)dashboard · crm · companyDetail · contactDetail · leadDetail · projects · tasks · offers (8)unverändert
MEMBERdashboard · crm · companyDetail · contactDetail · leadDetail · projects · tasks · offers (8)projects · tasks (2)–6 Module entfernt

Datei: src/server/auth/permissions.ts · Konstante: READ_MATRIX

4 · CRM-Detailseiten – Auswirkung auf canReadModule()

companyDetail, contactDetail und leadDetail sind eigenständige ModuleKeys in der READ_MATRIX – sie erben nicht vom Elternmodul crm. Da alle drei aus dem MEMBER-Array entfernt wurden, gibt canReadModule('companyDetail', 'MEMBER') künftig false zurück.

canReadModule(key, 'MEMBER')AltNeu
dashboardtruefalse
crmtruefalse
companyDetailtruefalse
contactDetailtruefalse
leadDetailtruefalse
projectstruetrue ✓
taskstruetrue ✓
offerstruefalse
filesfalsefalse
automationsfalsefalse
hostingfalsefalse
aiReviewfalsefalse
settingsfalsefalse

5 · Schreibrechte-Status (unverändert)

  • canWriteModule() gibt weiterhin global false zurück – keine Ausnahme
  • WRITE_MATRIX_THEORETICAL bleibt unverändert – nur theoretisch, nicht aktiviert
  • writeEnabled: false bleibt im PermissionContext gesetzt
  • Aktivierung von Schreibrechten folgt in AP060+

6 · Erwartete Browser-Tests nach AP123

Noch kein Manager/Member-Mapping – keine echten Testlogins vorhanden. Tests folgen nach AP124.

RouteAdminManagerMember (neu)
/dashboard✓ Zugriff✓ Zugriff✗ blockiert
/crm✓ Zugriff✓ Zugriff✗ blockiert
/crm/companies/[id]✓ Zugriff✓ Zugriff✗ blockiert
/crm/contacts/[id]✓ Zugriff✓ Zugriff✗ blockiert
/crm/leads/[id]✓ Zugriff✓ Zugriff✗ blockiert
/projects✓ Zugriff✓ Zugriff✓ Zugriff
/tasks✓ Zugriff✓ Zugriff✓ Zugriff
/offers✓ Zugriff✓ Zugriff✗ blockiert
/settings/read-access-test✓ Zugriff✓ Zugriff✗ blockiert

7 · Was nicht umgesetzt wurde (Scope-Abgrenzung)

  • kein Manager/Member-Mapping – keine echten DB-User mit neuer Rolle
  • kein Browserseitentest mit echten Testlogins (folgt AP124)
  • keine DB-Änderung, keine Prisma-Migration, kein SQL
  • kein CRUD, keine Server Actions, keine API-Routen geändert
  • keine Middleware, kein Layout-Schutz, kein breiter Route-Schutz
  • Schreibrechte weiterhin global blockiert (AP060+ zugewiesen)
  • Dashboard Light-Ansicht für Member noch nicht gebaut (späterer AP)
  • VIEWER/GUEST-Rollen weiterhin inaktiv (AP058 unverändert)

8 · Sicherheitsstatus AP123

DB-ÄnderungNein
Prisma-MigrationNein
echte E-Mail / User-IDNein
Mapping-Script ausgeführtNein
Seed verändertNein
CRUD / Server ActionsNein
Middleware geändertNein
Fachseiten geändertNein (nur read-access-test)
DeploymentNein
canWriteModule geändertNein (weiterhin false)
ADMIN-Matrix geändertNein
MANAGER-Matrix geändertNein

Ergebnis AP123

READ_MATRIX v2 technisch angewendet. MEMBER-Array auf ['projects', 'tasks'] reduziert. 6 Module entfernt: dashboard · crm · companyDetail · contactDetail · leadDetail · offers. ADMIN und MANAGER unverändert. canWriteModule() weiterhin global false. Keine DB-Änderung. Kein Mapping. Kein CRUD. Kein Deployment.

Empfehlung: AP124 – Manager/Member-Mapping lokal durchführen

Die READ_MATRIX v2 ist technisch aktiv. Nächster Schritt: echte Testlogins im kds-demo-Workspace mit Rollen MANAGER und MEMBER versehen (lokales Mapping-Script aus AP102, nach Freigabe durch Andre). Danach: Browser-Tests gegen neue Matrix. Kein CRUD, keine DB-Migration, kein Deployment in AP124.

AP123 abgeschlossen · READ_MATRIX v2 aktiv · MEMBER = projects + tasks · ADMIN/MANAGER unverändert · keine DB-ÄnderungAP124 empfohlen
AP124-A

AP124-A – Manager/Member-Mapping Dry-Run vorbereitet

20.05.2026

1 · Ziel von AP124-A

  • scripts/prepare-manager-member-mapping.local.ts auf echten Dry-Run upgraden
  • Drei Guards: EXECUTION_APPROVED / DRY_RUN / WRITE_RUN_APPROVED
  • Platzhalter-Check: Script bricht ab wenn noch Placeholder vorhanden
  • Maskierte Log-Ausgabe: keine echten E-Mails in Logs
  • .gitignore Sicherheitslücke schließen (*.local.backup.ts nicht ignoriert)
  • kein Write-Run · keine DB-Änderung · kein CRUD · kein Deployment

2 · Behobene Sicherheitslücke – .gitignore

Dateiscripts/prepare-manager-member-mapping.local.backup.ts
Problemwar NICHT git-ignoriert (nur untracked ?? statt ignoriert !!)
Risikogit add . hätte Backup mit echten E-Mails committen können
Fixscripts/*.local.backup.ts + .js + .mjs zu .gitignore hinzugefügt
Bestätigt viagit check-ignore -v → Regel .gitignore:59 greift

3 · Script-Struktur scripts/prepare-manager-member-mapping.local.ts

Guard / VariableDefaultBedeutung
EXECUTION_APPROVEDfalseScript läuft nur wenn true. Bei false → process.exit(0) sofort.
DRY_RUNtruetrue = nur Ausgabe, keine DB-Schreiboperation. Immer zuerst.
WRITE_RUN_APPROVEDfalseZusätzlicher Guard für echte Writes. Nur nach Dry-Run + Freigabe André.
MANAGER_EMAILPLACEHOLDERLokal ersetzen. Script prüft ob Placeholder vorhanden → bricht ab.
MEMBER_EMAILPLACEHOLDERLokal ersetzen. Script prüft ob Placeholder vorhanden → bricht ab.

4 · Ausführungsschritte für AP124-B (Dry-Run)

  1. 1. MANAGER_EMAIL lokal mit echtem Login-Identifier befüllen
  2. 2. MEMBER_EMAIL lokal mit echtem Login-Identifier befüllen
  3. 3. EXECUTION_APPROVED = true setzen (nur lokal)
  4. 4. DRY_RUN = true lassen (Standard)
  5. 5. Dry-Run ausführen: npx tsx scripts/prepare-manager-member-mapping.local.ts
  6. 6. Ausgabe prüfen: User gefunden? Ist-Rolle? Soll-Rolle? Änderung nötig?
  7. 7. Nach Prüfung: echte Werte sofort wieder entfernen, EXECUTION_APPROVED = false
  8. 8. AP124-B Write-Run nur nach separater Freigabe durch André

Dry-Run-Befehl: npx tsx scripts/prepare-manager-member-mapping.local.ts

5 · Was nicht umgesetzt wurde (Scope-Abgrenzung)

  • kein Write-Run ausgeführt – kein DB-User verändert
  • keine echten E-Mails eingetragen (nur Platzhalter im Script)
  • keine DB-Änderung, keine Prisma-Migration, kein SQL
  • kein CRUD, keine Server Actions, keine API-Routen
  • keine Middleware, kein Layout-Schutz
  • kein Deployment, kein Push, kein PR (außer .gitignore + Dokumentation)
  • Browser-Tests folgen nach AP124-B (Write-Run)

6 · Sicherheitsstatus AP124-A

DB-ÄnderungNein
echte E-Mail / User-IDNein
Write-Run ausgeführtNein
.gitignore-Lücke geschlossenJa ✓
local.ts weiterhin ignoriertJa ✓ (!! bestätigt)
backup.ts jetzt ignoriertJa ✓ (neu in AP124-A)
tracked files verändert.gitignore + read-access-test
CRUD / Server ActionsNein
DeploymentNein
canWriteModule geändertNein (weiterhin false)

Ergebnis AP124-A

Dry-Run-Script vorbereitet. .gitignore Sicherheitslücke geschlossen. Drei Guards aktiv (EXECUTION_APPROVED / DRY_RUN / WRITE_RUN_APPROVED). Platzhalter-Check eingebaut. Maskierte Log-Ausgabe. Keine DB-Änderung. Kein Write-Run. Kein Deployment.

Empfehlung: AP124-B – Dry-Run lokal ausführen und Ergebnis prüfen

MANAGER_EMAIL und MEMBER_EMAIL lokal eintragen, EXECUTION_APPROVED = true, DRY_RUN = true lassen. Befehl: npx tsx scripts/prepare-manager-member-mapping.local.ts. Ausgabe prüfen. Danach echte Werte entfernen. Write-Run erst nach separater Freigabe durch André (AP124-C oder eigener AP).

AP124-A abgeschlossen · Dry-Run-Script bereit · .gitignore gesichert · keine DB-Änderung · kein Write-RunAP124-B empfohlen
AP124-B

AP124-B – Manager/Member Write-Run abgeschlossen

21.05.2026

1 · Ziel von AP124-B

  • Dry-Run ausführen und Ergebnis prüfen (beide User nicht vorhanden → CREATE)
  • Script auf prisma.user.upsert umgestellt – CREATE wenn nicht vorhanden, UPDATE Rolle wenn vorhanden
  • Write-Run nach expliziter Freigabe durch André ausführen
  • Beide User in kds-demo DB anlegen: MANAGER + MEMBER mit echtem Login-Identifier
  • Guards nach Write-Run sofort zurücksetzen (DRY_RUN=true, WRITE_RUN_APPROVED=false)
  • DB-Zustand via Query-Script verifizieren
  • Abschnitt 56 dokumentieren · Commit + PR für tracked files

2 · Dry-Run Ergebnis (vor Write-Run)

RolleUser (maskiert)Dry-Run Aktion
MANAGERn***@krause-digital-solutions.de→ CREATE (User nicht vorhanden)
MEMBERm***@krause-digital-solutions.de→ CREATE (User nicht vorhanden)

Keine DB-Schreiboperation im Dry-Run. Ergebnis durch André freigegeben.

3 · Write-Run Ergebnis (DB-Änderung)

RolleUser (maskiert)StatusAktion
MANAGERn***@krause-digital-solutions.deACTIVE✓ User angelegt
MEMBERm***@krause-digital-solutions.deACTIVE✓ User angelegt

4 · DB-Zustand kds-demo nach AP124-B (Gesamtübersicht)

RolleNameUser (maskiert)Quelle
ADMINDemo Admina***@demo.localSeed
ADMINAdmin Testuser (local)k***@krause-digital-solutions.deAP094-B
MANAGERDemo Managerm***@demo.localSeed
MANAGERN. Stehlen***@krause-digital-solutions.deAP124-B ✓ neu
MEMBERM. Felsm***@krause-digital-solutions.deAP124-B ✓ neu
MEMBERDemo Mitarbeiterm***@demo.localSeed

Gesamt: 6 User · 2 ADMIN · 2 MANAGER · 2 MEMBER · alle ACTIVE

5 · Script-Upgrade – upsert statt findFirst+update

VorherfindFirst + update → schlägt fehl wenn User nicht existiert
Nachherupsert → CREATE wenn nicht vorhanden · UPDATE Rolle wenn vorhanden
unique keyworkspaceId_email (@@unique aus Prisma-Schema)
create fieldsworkspaceId · email · name · role · status: ACTIVE
update fieldsrole (nur Rollenkorrektur · kein Überschreiben anderer Felder)
Guards nach RunDRY_RUN=true · WRITE_RUN_APPROVED=false (sofort zurückgesetzt)

6 · Sicherheitsstatus AP124-B

DB-Änderung2 User CREATE (freigegeben)
echte E-Mail in LogsNein – maskiert
echte E-Mail in GitNein – .local.ts ignoriert
canWriteModule geändertNein (weiterhin false)
Prisma-MigrationNein
Seed verändertNein
CRUD / Server ActionsNein
MiddlewareNein
DeploymentNein
Guards nach RunZurückgesetzt ✓

Ergebnis AP124-B

Write-Run erfolgreich. Beide User (MANAGER + MEMBER) in kds-demo DB angelegt. Guards zurückgesetzt. DB-Zustand verifiziert (6 User · 2 ADMIN · 2 MANAGER · 2 MEMBER). Script verbleibt lokal git-ignoriert. Keine Prisma-Migration. Kein Seed verändert. canWriteModule weiterhin false.

Nächster Schritt: Browser-Tests mit MANAGER- und MEMBER-Login

Login mit n***@krause-digital-solutions.de → 8 Module sichtbar (MANAGER READ_MATRIX v2). Login mit m***@krause-digital-solutions.de → 2 Module sichtbar (projects + tasks, MEMBER READ_MATRIX v2). Ergebnisse in read-access-test dokumentieren (AP125 oder AP124-C).

AP124-B abgeschlossen · 2 User angelegt (MANAGER + MEMBER) · DB verifiziert · Guards zurückgesetztBrowser-Tests ausstehend
AP125

AP125 – Task-Create: erste CRUD-Funktion

21.05.2026

1 · Ziel von AP125

  • Erste echte Server Action im System: createTask()
  • Rollenbasierte Sichtbarkeit: ADMIN + MANAGER sehen "Neue Aufgabe"-Button
  • MEMBER bleibt read-only – kein Button, kein Zugriff auf Server Action
  • canWriteModule() bleibt unverändert (global false, AP058)
  • Schreibschutz server-seitig in der Action selbst (nicht nur UI)
  • Kein Redirect, kein Throw, keine echten Daten exponiert

2 · Neue Dateien (AP125)

DateiTypFunktion
src/server/tasks/actions.tsServer ActioncreateTask() – Validierung, Rollencheck, DB-Write
src/features/tasks/components/CreateTaskDialog.tsxClient ComponentDialog mit Formular: Titel, Beschreibung, Priorität

3 · Server Action Sicherheitslogik (createTask)

  1. 1. getCurrentWorkspaceContext() → Auth + DB-User + Workspace
  2. 2. ctx.status !== ok oder kein workspaceId → Fehler ohne DB-Zugriff
  3. 3. ctx.userRole nicht in [ADMIN, MANAGER] → Fehler "Keine Schreibberechtigung"
  4. 4. title leer oder > 200 Zeichen → Validierungsfehler
  5. 5. priority nicht in Whitelist → Fallback auf MEDIUM
  6. 6. db.task.create() → workspaceId, title, description, priority, status: OPEN
  7. 7. revalidatePath("/tasks") → Cache invalidiert
  8. 8. DB-Fehler → generische Fehlermeldung (kein Stack-Trace an Client)

4 · Rollenmatrix Task-Create

RolleButton sichtbarServer Action erlaubtcanWriteModule()
ADMIN✓ Ja✓ Jafalse (unverändert)
MANAGER✓ Ja✓ Jafalse (unverändert)
MEMBER✗ Nein✗ Nein (Fehler)false (unverändert)

5 · Formularfelder CreateTaskDialog

TitelPflichtfeld · Text · max 200 Zeichen
BeschreibungOptional · Textarea · max 2000 Zeichen
PrioritätSelect · LOW / MEDIUM / HIGH / CRITICAL · Default: MEDIUM
StatusAutomatisch: OPEN (nicht im Formular)
workspaceIdServer-seitig aus Context · nie im Client
FeedbackInline Error-Banner oder Erfolgs-Banner

Ergebnis AP125

Erste Server Action createTask() implementiert. CreateTaskDialog als Client Component mit Dialog, Formular und Inline-Feedback. Tasks-Page zeigt "Neue Aufgabe"-Button für ADMIN+MANAGER. MEMBER bleibt read-only. canWriteModule() unverändert. TypeScript fehlerfrei.

Nächster Schritt: Browser-Test + Task-Status-Update

Login MANAGER → "Neue Aufgabe" Button sichtbar → Task erstellen → Aufgabe erscheint in Liste. Login MEMBER → kein Button sichtbar. Folge-AP: Task-Status ändern (OPEN → IN_PROGRESS → DONE) oder Task-Delete.

AP125 abgeschlossen · createTask() Server Action · CreateTaskDialog · ADMIN+MANAGER write-fähigcanWriteModule unverändert
58

AP126 – Multi-Workspace Architecture

workspaceId fließt aus Auth-Context durch alle Queries

Kernänderungen

permissions.tsworkspaceId? in PermissionContext – server-only, nie Client
page-access.tsworkspaceId? in ReadPageAccess – in allen Return-Objekten
tasks/queries.tsDEMO_WORKSPACE_SLUG entfernt · getTaskOverviewData(workspaceId)
projects/queries.tsGleiches Pattern: getDemoWorkspace() weg
crm/queries.tsworkspaceId als Query-Parameter statt Slug-Lookup
crm/details.tsAlle Detail-Queries mit workspaceId-Sicherheitsfilter
8 Seitenaccess.workspaceId ?? '' an alle Query-Aufrufe übergeben
Cross-TenantKein Datenleck zwischen Workspaces möglich
AP126 abgeschlossen · workspaceId in Auth-Context · alle Queries mandantenfähigCopy & Paste ready
59

AP127 · AP128 · AP129 – Task-CRUD vollständig + AppShell Workspace-Branding

Status-Update · Edit · Delete · Workspace-Name in Header + Sidebar

AP127 – Task-Status-Update

updateTaskStatus()Server Action · alle Workspace-User (ADMIN+MANAGER+MEMBER)
TaskStatusControl4 Status-Chips · OPEN / IN_PROGRESS / IN_REVIEW / DONE
Cross-WorkspaceupdateMany mit workspaceId-Filter · count===0 → Fehler
TaskListcanUpdateStatus-Prop → zeigt TaskStatusControl

AP128 – Workspace-Branding (AppShell)

AppShellasync Server Component · lädt workspace-Context einmal pro Request
Header.tsxworkspaceName als h1 · maskierte E-Mail · Rolle als Badge
Sidebar.tsxworkspaceName in Logo-Subtitle und Footer
Props-FlowworkspaceName + normalizedRole + safeEmail von AppShell nach unten

AP129 – Task-Edit + Task-Delete

updateTask()ADMIN+MANAGER · Titel+Beschreibung+Priorität
deleteTask()ADMIN+MANAGER · deleteMany mit workspaceId · 2-Schritt-Confirm
EditTaskDialogPre-filled Formular · useTransition · auto-close on success
DeleteTaskButtonInline Ja/Nein · kein Modal · kein Redirect
AP127+128+129 abgeschlossen · vollständiges Task-CRUD · Workspace-Branding livecanWriteModule unverändert
60

AP130 – CRM Contact Create

Server Action createContact() · CreateContactDialog (sky-blau)

createContact()ADMIN+MANAGER · workspaceId server-seitig
PflichtfelderfirstName (max 100) · lastName (max 100)
Optionale Felderemail (max 200) · phone (max 50) · title (max 150)
Standardwertestatus: ACTIVE · workspaceId aus Auth-Context
CreateContactDialogSky-blau · 2-Spalten firstName/lastName · useTransition
CRM-Seite„Neuer Kontakt"-Button für ADMIN+MANAGER · Rolle sichtbar
AP130 abgeschlossen · CRM-Schreibfunktion aktiv · ADMIN+MANAGER write-fähigrevalidatePath /crm
61

AP131 – Project Create

Server Action createProject() · CreateProjectDialog (violet)

createProject()ADMIN+MANAGER · workspaceId server-seitig
Pflichtfeldtitle (max 200 Zeichen)
Optionales Felddescription (max 2000 Zeichen, Textarea)
Standardwertestatus PLANNING · phase DISCOVERY · health GREEN · budgetState ON_TRACK
Kein priority-FeldProject-Modell hat kein priority-Feld (nur Task) – entfernt
CreateProjectDialogViolet · title + description textarea · useTransition
Projects-Seite„Neues Projekt"-Button für ADMIN+MANAGER
TypeScriptnpx tsc --noEmit – sauber nach priority-Fix
AP131 abgeschlossen · Projekt-Schreibfunktion aktiv · ADMIN+MANAGER write-fähigrevalidatePath /projects
62

AP132 – Client Onboarding Script

Neuer Mandant = Neuer Workspace in 5 Minuten · Copy & Paste Deployability

Script: scripts/onboard-client.template.ts

Workspace anlegenname + slug + status ACTIVE · Duplikat-Check per Slug
Admin-Userupsert mit workspaceId_email-Unique-Key · role ADMIN · status ACTIVE
3-Phasen-GuardDRY_RUN=true/false + WRITE_RUN_APPROVED=false/true
Dry-RunZeigt Konfiguration ohne DB-Verbindung – Platzhalter sicher
ValidierungenPlatzhalter-Check · Slug-Format (lowercase-slug) · E-Mail-Format
Git-SicherheitTemplate in Git · lokale Kopie *.local.ts automatisch ignoriert
Kein MigrationKein prisma migrate, kein db push, kein SQL – nur Prisma upsert/create
WorkflowTemplate kopieren → 4 Werte ersetzen → GO von André → ausführen → testen

Vollständiger Onboarding-Workflow

  1. cp scripts/onboard-client.template.ts scripts/onboard-client.local.ts
  2. 4 Platzhalter ersetzen: workspaceName, workspaceSlug, adminName, adminEmail
  3. WRITE_RUN_APPROVED = true (nur nach GO von André)
  4. npx tsx scripts/onboard-client.local.ts
  5. Login testen → alle 13 Module für Admin zugänglich
  6. Weitere User: scripts/prepare-manager-member-mapping.local.ts
  7. Lokale Kopie löschen · kein Commit von Echtdaten
AP132 abgeschlossen · Copy & Paste Deployability implementiert · Neuer Client in 5 Minuten provisionierbarscripts/onboard-client.template.ts
64

AP134 · AP135 · AP136 – Offer + Company + Lead Create

3 neue Schreibfunktionen · 3 Dialoge · CRM-Seite mit allen Buttons

AP134 – Angebot anlegen (amber)

createOffer()ADMIN+MANAGER · title required · valueRange + riskLevel + notes optional
Defaultsstatus DRAFT · phase INITIAL · responseState PENDING
CreateOfferDialogAmber · title + valueRange-Select + riskLevel-Select + notes textarea
Offers-Seite"Neues Angebot"-Button für ADMIN+MANAGER

AP135 – Firma anlegen (emerald)

createCompany()ADMIN+MANAGER · name required · type/website/industry/city/country/notes optional
Defaultsstatus ACTIVE · type CUSTOMER
CreateCompanyDialogEmerald · max-w-lg · 2-Spalten für alle Felder · type-Select
URL-Validierungwebsite input type="url" · max 300 Zeichen

AP136 – Lead anlegen (orange)

createLead()ADMIN+MANAGER · title required · priority + source + notes optional
Defaultsstatus NEW · priority MEDIUM · source OTHER
CreateLeadDialogOrange · title + 2-Spalten priority/source + notes textarea
CRM-Seite3 Buttons: "Neue Firma" · "Neuer Kontakt" · "Neuer Lead" für ADMIN+MANAGER
AP134/135/136 abgeschlossen · 3 neue Server Actions · CRM vollständig write-fähigPR #90
65

AP137 · AP138 · AP139 · AP140 – Status-Update + Delete

Vollständiges CRUD für Offers, Leads, Contacts, Companies

Server Actions (alle mit workspaceId Cross-Workspace-Schutz)

APActionBerechtigungUI-Komponente
AP137updateOfferStatus()ADMIN+MANAGEROfferStatusControl (6 Chips)
AP137deleteOffer()ADMIN+MANAGERDeleteOfferButton (2-Schritt)
AP138updateLeadStatus()ALLE UserLeadStatusControl (8 Chips)
AP138deleteLead()ADMIN+MANAGERDeleteLeadButton (2-Schritt)
AP139deleteContact()ADMIN+MANAGERDeleteContactButton (2-Schritt)
AP140deleteCompany()ADMIN+MANAGERDeleteCompanyButton (Fehler-State)
dbStatus-FeldOptionales Feld in Lead + Offer Typen – DB-Status für Server Actions
Queries erweitertcrm/queries.ts + offers/queries.ts geben dbStatus an Frontend durch
canManage-PropCrmOverview + OfferOverview: canManage durchgereicht bis Listen-Ebene
Company-DeleteFängt FK-Constraint-Fehler: "erst verknüpfte Datensätze entfernen"
Mock-DatendbStatus optional → Mock-Daten weiter kompatibel ohne Änderung
TypeScriptnpx tsc --noEmit – sauber nach optional-Handling
AP137-140 abgeschlossen · vollständiges CRUD für alle 4 CRM/Offer-Entitäten · PR #9121 Dateien · 715 Insertions
66

CRUD-Matrix – vollständiger Stand nach AP141

Welche Operationen sind pro Modul und Rolle verfügbar?

Modul / EntitätCreateReadEdit/StatusDeleteRollen
Tasks✅ AP125✅ AP127/129✅ AP129C/E/D: ADMIN+MANAGER · Status: alle
Projekte✅ AP131C: ADMIN+MANAGER
Kontakte✅ AP130✅ AP139C/D: ADMIN+MANAGER
Firmen✅ AP135✅ AP140C/D: ADMIN+MANAGER
Leads✅ AP136✅ AP138✅ AP138C/D: ADMIN+MANAGER · Status: alle
Angebote✅ AP134✅ AP137✅ AP137C/E/D: ADMIN+MANAGER
6
Module read-fähig
6
Module write-fähig
17
Server Actions
141
APs abgeschlossen
63

Projekt-Gesamtstatus – AP126 bis AP132

Alle abgeschlossenen APs dieser Entwicklungsphase

APTitelHauptdatei(en)Status
AP124-BDB Write-Run: MANAGER + MEMBER User anlegenscripts/prepare-manager-member-mapping.local.ts
AP125Task Create (Server Action + Dialog)src/server/tasks/actions.ts · CreateTaskDialog.tsx
AP126Multi-Workspace: workspaceId in Auth-Contextpermissions.ts · page-access.ts · 6 Query-Files · 8 Seiten
AP127Task-Status-Update (Server Action + UI)tasks/actions.ts · TaskStatusControl.tsx
AP128Workspace-Branding in AppShell/Header/SidebarAppShell.tsx · Header.tsx · Sidebar.tsx
AP129Task Edit + Delete (Server Action + UI)tasks/actions.ts · EditTaskDialog.tsx · DeleteTaskButton.tsx
AP130CRM Contact Createserver/crm/actions.ts · CreateContactDialog.tsx
AP131Project Createserver/projects/actions.ts · CreateProjectDialog.tsx
AP132Client Onboarding Scriptscripts/onboard-client.template.ts
AP133Dokumentation AP126–AP132settings/read-access-test/page.tsx
10
APs abgeschlossen
3
Module write-fähig
3
Server Actions
1
Onboarding-Script
69

AP148 – Dashboard Produktionsbereinigung

Alle "Demo-", "Neon Dev", "Muster", "Beispiel"-Texte entfernt

queries.ts

Prioritäts- und Aktivitätsbeschreibungen bereinigt

DashboardOverview.tsx

Stat-Labels, Section-Descriptions, staticNote bereinigt

mock-data.ts

Alle 10 Module, Activities, Priorities, HealthColumns bereinigt

4
Module Echtdaten
0
"Demo-"-Texte sichtbar
3
Dateien geändert
148
APs abgeschlossen
70

AP149 – Client-seitige Suche für alle Listenmodule

ModuleSearchBar + *FilteredList-Wrapper für Tasks, Projects, Offers, CRM

ModulWrapper-KomponenteSuchfelder
TasksTaskFilteredListtitle, description, owner.name, context.label, nextAction
ProjekteProjectFilteredListtitle, description, owner.name, nextAction
AngeboteOfferFilteredListtitle, description, customerLabel, owner, nextAction
CRM – LeadsCrmFilteredLists (leads)name (title), company, nextAction
CRM – KontakteCrmFilteredLists (contacts)name, role, company, nextAction
CRM – FirmenCrmFilteredLists (companies)name, industry

Architekturentscheidung: Client-seitige Filterung

• Kein Server Round-Trip – Filter ist instant und smooth

• Wrapper-Komponenten sind 'use client', übernehmen die geladenen Daten als Props

• Overview-Komponenten bleiben server-side – keine Änderung am Auth/Data-Flow

• ModuleSearchBar in src/components/ – wiederverwendbar für alle Module

• Ergebniszähler "X von Y" nur bei aktivem Suchbegriff sichtbar

6
Suchlisten aktiv
1
Shared Component
5
Neue Dateien
149
APs abgeschlossen
71

AP150 – Status- und Priorität-Filter-Chips

Klickbare Chips in allen FilteredList-Komponenten – kombinierbar mit Textsuche

ModulFilter-DimensionenChip-Werte
TasksPriority + StatusKritisch/Hoch/Mittel/Niedrig · Offen/In Arbeit/Wartet/Erledigt
ProjekteStatus + PriorityGeplant/Aktiv/Review/Geblockt/Abgeschlossen · Kritisch–Niedrig
AngeboteStatus + PrioritätEntwurf/Versendet/Rückfrage/Entscheidung/Gewonnen/Verloren · Hoch/Mittel/Niedrig
LeadsStatus + PriorityNeu/Qualifiziert/Angebot/Wartet/Gewonnen/Verloren · Kritisch–Niedrig
FirmenStatusInteressent/Aktiv/Pausiert/Follow-up
KontakteNur Textsuche(kein Status-Filter – Kontakte haben keine eindeutige Filterdimension)

Kombinationslogik

• Chips sind Single-Select pro Dimension – "Alle" setzt den jeweiligen Filter zurück

• Priority-Filter AND Status-Filter AND Textsuche (alle gleichzeitig aktiv möglich)

• Chips mit count=0 werden automatisch ausgeblendet (nur relevante Optionen sichtbar)

• ModuleFilterChips in src/components/ – shared, typsicher über FilterChipOption

2
Shared Components
5
Module mit Chips
1
Nur Textsuche
150
APs abgeschlossen
72

AP151 – Sortier-Dropdown in allen Listenmodulen

ModuleSortSelect – shared Select-Komponente, clientseitig, kombinierbar mit Filter und Suche

Modul / SektionSort-OptionenStandard
TasksStandard · Titel A–Z · Priorität (höchste zuerst) · StatusReihenfolge aus DB
ProjekteStandard · Titel A–Z · Priorität (höchste zuerst) · StatusReihenfolge aus DB
AngeboteStandard · Titel A–Z · Priorität (höchste zuerst) · StatusReihenfolge aus DB
CRM – LeadsStandard · Name A–Z · Priorität (höchste zuerst) · StatusReihenfolge aus DB
CRM – KontakteStandard · Name A–Z · Rolle A–ZReihenfolge aus DB
CRM – FirmenStandard · Name A–Z · StatusReihenfolge aus DB

Architektur

• ModuleSortSelect in src/components/ – shared, typsicher über SortOption

• Rein client-seitig via [...filtered].sort() – kein Server-Round-Trip

• Priorität-Sortierung: critical(0) > high(1) > medium(2) > low(3) – Offers: hoch/mittel/niedrig

• Kombinierbar: Filter AND Suche AND Sortierung gleichzeitig aktiv

• Dropdown neben SearchBar platziert (flex row) – konsistentes Layout in allen Modulen

1
Shared Component
6
Sektionen sortierbar
3
Shared Components total
151
APs abgeschlossen
73

AP152 – CRM-Beziehungen: Contact↔Company + Lead↔Company/Contact

FK-Verlinkung per Selector-Dropdown in Create- und Edit-Dialogen – server-seitig validiert

EntityNeue FK-FelderDialoge erweitertServer-Validierung
ContactcompanyId (optional)CreateContactDialog + EditContactDialogdb.company.count( workspaceId )
LeadcompanyId + contactId (optional)CreateLeadDialog + EditLeadDialogdb.company.count + db.contact.count

Architektur

• FKs schon im Prisma-Schema vorhanden (companyId, contactId) – nur UI + Actions fehlten

• Slim-Arrays { id, name }[] per useMemo in CrmFilteredLists stabilisiert – verhindert Re-Renders

• crm/page.tsx reicht companies/contacts als Slim-Arrays an CreateDialoge weiter (server-side data)

• Workspace-Validierung: companyId/contactId per count-Query gegen ctx.workspaceId geprüft

• companyId: null setzt FK zurück (Prisma updateMany akzeptiert null für optionale FKs)

• Selector-Dropdown nur sichtbar wenn Optionen vorhanden (companies.length > 0)

2
Neue FKs aktiv
4
Dialoge erweitert
2
Server Actions sicher
152
APs abgeschlossen
74

AP153 – Offer/Projekt↔Company: Company-Selector in Offers und Projects

FK-Verlinkung per Selector-Dropdown in Create- und Edit-Dialogen – server-seitig validiert – getWorkspaceCompanyNames() als shared Query

ModulNeue FK-FelderDialoge erweitertServer-Validierung
OffersdbCompanyId (optional)CreateOfferDialog + EditOfferDialogdb.company.count( workspaceId )
ProjectsdbCompanyId (optional)CreateProjectDialog + EditProjectDialogdb.company.count( workspaceId )

Architektur

• getWorkspaceCompanyNames() als geteilte Slim-Query in server/crm/company-queries.ts

• offers/page.tsx + projects/page.tsx laden companies parallel via Promise.all

• Prop-Chain: page → Overview → FilteredList → List → Dialog (Edit + Create)

• createOffer + createProject: companyId aus FormData + Workspace-Validierung ergänzt

• companyId: null löscht Zuordnung, undefined = keine Änderung (Prisma-Konvention)

• Selector nur sichtbar wenn companies.length > 0 – kein leeres Dropdown

2
Module verknüpft
4
Dialoge erweitert
1
Shared Query neu
153
APs abgeschlossen
75

AP154 – Ingest API: POST /api/ingest/[entity]

Gesicherte REST-Endpunkte für n8n, HubSpot, Microsoft Planner – API-Key-Auth + Workspace-Lookup per Slug

EndpunktPflichtfelderLookup-FeatureQuelle
POST /api/ingest/companiesnameHubSpot Company
POST /api/ingest/contactsfirstName, lastNamecompanyName → companyIdHubSpot Contact
POST /api/ingest/leadstitlecompanyName + contactEmail → FKsHubSpot Deal
POST /api/ingest/offerstitlecompanyName → companyIdHubSpot Deal (alt.)
POST /api/ingest/taskstitleprojectTitle → projectIdMS Planner Task

Architektur & Sicherheit

• Auth: x-api-key Header + workspaceSlug im Body

• Env-Vars: INGEST_API_KEY (single) oder INGEST_API_KEYS (JSON per Workspace)

• Timing-safe String-Vergleich verhindert Timing-Angriffe

• workspaceId niemals im Response – nur id der erzeugten Entity

• Workspace-Status wird geprüft (ACTIVE) – suspendierte Workspaces blockiert

• Firmen-/Kontakt-Lookups: case-insensitive Namens-Suche, kein Match = kein Fehler

n8n-Konfiguration (HTTP Request Node)

URL: https://kds-os.vercel.app/api/ingest/leads

Method: POST  |  Header: x-api-key: {{$env.KDS_INGEST_KEY}}

Body (JSON): workspaceSlug + Entity-Felder (aus HubSpot-Trigger-Daten gemappt)

5
Endpunkte live
5
Entitäten abgedeckt
2
Auth-Modi (single/multi)
154
APs abgeschlossen
76

AP155 + AP156 – Dashboard Echtdaten & CRM Detail-Bereinigung

Dashboard mit 16 parallelen DB-Queries produktiv; alle CRM-Detailseiten Demo-frei

AP155 – Dashboard Echtdaten

  • 16 parallele Prisma-Queries via Promise.all()
  • DashboardPipelineBar – Lead- & Offer-Pipeline segmentiert
  • DashboardAlertCard – kritische/hohe Alerts aus DB
  • DashboardOperationalStatCard – 8 Kennzahlen-Kacheln
  • Alle "Demo-"/"Neon Dev"-Texte aus Dashboard entfernt
  • mock-data.ts auf produktive Beschreibungen umgestellt

AP156 – CRM Detail-Bereinigung

  • CompanyDetailOverview – 5 Demo-Texte bereinigt
  • ContactDetailOverview – 4 Demo-Texte bereinigt
  • LeadDetailOverview – 4 Demo-Texte bereinigt
  • RelatedCompanyCard / RelatedContactCard bereinigt
  • RelatedLeads/Projects/Offers/Contacts-Listen bereinigt
  • companies/contacts/leads [id]/page.tsx bereinigt
16
DB-Queries parallel
3
neue Dashboard-Komponenten
9
CRM-Dateien bereinigt
156
APs abgeschlossen
77

AP157 – Task ↔ Project-Linking

Project-Selector in Create- und Edit-Dialog, Projekt-Badge in der Aufgabenliste

  • getWorkspaceProjectNames() – schlanke { id, title }[] Query (project-queries.ts)
  • createTask() + updateTask() – projectId mit Workspace-Validierung (FK-Check)
  • CreateTaskDialog – violetter Project-Selector (optional)
  • EditTaskDialog – vorausgewähltes Projekt via task.dbProjectId
  • TaskList – violettes Projekt-Badge wenn task.projectTitle gesetzt
  • Promise.all([getTaskOverviewData, getWorkspaceProjectNames]) in TasksPage
  • Prop-Chain: TasksPage → TaskOverview → TaskFilteredList → TaskList → EditTaskDialog
78

AP158 – Project Detail Page: /projects/[id]

Vollständige Projektdetails mit Tasks, Angeboten, Company- und Lead-Verknüpfung

  • getProjectDetailData() – Prisma-Query mit Tasks, Offers, Company, Lead
  • ProjectDetailOverview – Header, Metadaten, Stat-Karten, Beziehungen
  • ProjectRelatedTasksList – Aufgaben mit Status- und Prioritäts-Badges
  • ProjectRelatedOffersList – Angebote mit Status- und Risiko-Badges
  • ProjectList – Projekttitel als Link zu /projects/[id] (violett)
  • Serverseitig geschützt via getReadPageAccess("projects") + AP158-Banner
79

AP159 – Activity / Notes Feed

Polymorphes Note-Modell + NotesFeed auf allen CRM- und Projekt-Detailseiten

  • Note-Modell: workspaceId + optionale FKs companyId/contactId/leadId/projectId/offerId
  • prisma migrate dev --name add_notes – Migration auf Neon Dev applied
  • getEntityNotes(workspaceId, entityType, entityId) – polymorphe Query
  • createNote() – alle Workspace-User · deleteNote() – ADMIN+MANAGER
  • NotesFeed.tsx – Client-Component mit useState-Optimistic-UI (kein Reload)
  • canDelete = normalizedRole ADMIN|MANAGER – serverseitig, prop-chain
  • Aktiviert auf: Companies · Contacts · Leads · Projects
CompanyContactLeadProject
80

AP160 – HubSpot → KDS OS via n8n

Zwei importierbare n8n-Workflows: Einmaliger Vollimport + Realtime-Webhook-Sync

80.1 · Workflow-Dateien

docs/n8n/hubspot-initial-import.json

Einmaliger Vollimport

Manueller Trigger · Companies → Contacts → Deals (parallel) · Pagination via after-Cursor · Batching 5/200ms

docs/n8n/hubspot-realtime-sync.json

Realtime Webhook-Sync

HubSpot Webhook → Respond 200 sofort → Route by Type → Fetch + Map → KDS Ingest · company/contact/deal

80.2 · HubSpot → KDS Feldmapping

HubSpot ObjektHubSpot FelderKDS Ingest EndpunktKDS Felder
Companyname, website, industry, city, country/api/ingest/companiesname, website, industry, city, country
Contactfirstname, lastname, email, phone, jobtitle, company/api/ingest/contactsfirstName, lastName, email, phone, title, companyName
Dealdealname, dealstage, amount, description, hs_priority/api/ingest/leadstitle, status*, value, notes, priority*, source=hubspot

* Deal Stage → KDS Status: closedwon→won, closedlost→lost, appointmentscheduled→new, qualifiedtobuy→qualified, presentationscheduled→proposal

80.3 · Setup (einmalig)

  1. 1n8n → Import from File → hubspot-initial-import.json oder hubspot-realtime-sync.json
  2. 2Config-Node: kdsBaseUrl, kdsWorkspaceSlug, kdsApiKey (= INGEST_API_KEY aus Vercel) eintragen
  3. 3HubSpot-Credential in n8n anlegen (Private App Token mit CRM-Read-Scopes)
  4. 4Initial-Import: Execute Workflow → Companies, Contacts, Deals werden parallel importiert
  5. 5Realtime-Sync: Workflow aktivieren → Webhook-URL in HubSpot Subscriptions eintragen

Sicherheit

Alle Ingest-Endpunkte prüfen x-api-key gegen INGEST_API_KEY (Env) · workspaceSlug wird server-seitig zu workspaceId aufgelöst · Keine workspaceId im n8n-Workflow · Kein Datenbank-Direktzugriff aus n8n

81

AP161 – Workspace Onboarding: Copy & Paste Deployability

/settings/workspace – Checkliste, User-Liste, Datenstände, n8n-Guide

81.1 · Neue Route

/settings/workspace

getReadPageAccess('settings') · getWorkspaceOverviewData(workspaceId) · 7 parallele DB-Counts

81.2 · Komponenten

WorkspaceOverview

Hauptseite: Header, Stats, Checkliste, User-Liste, n8n-Guide, Playbook

WorkspaceOnboardingChecklist

7-Schritt-Checkliste mit Progress-Bar – live aus DB (users, companies, contacts, leads, projects)

WorkspaceUserList

Alle Workspace-User mit Name, Rolle, Status · keine vollständigen E-Mails

Sicherheit AP161

  • Keine vollständigen E-Mails – nur emailInitial (erster Buchstabe) im Type
  • workspaceId nur serverseitig – nie an Client weitergegeben
  • Nur Lesen – keine Server Actions, keine Formulare, kein CRUD
  • getReadPageAccess("settings") – gleicher Schutz wie Settings-Hauptseite
82

AP162 – Stabilitäts- und SaaS-Fähigkeitsreview

21.05.2026 · Branch ap162/stability-saas-readiness-review · Kein Feature, kein DB-Change

82.1 · Build & TypeScript

pnpm build

✅ Erfolgreich

0 Fehler · 31 Routen · alle ƒ Dynamic

npx tsc --noEmit

✅ 0 Fehler

Alle Typen konsistent

ESLint Warnings

⚠️ 5 Warnings

Unused vars: getLeadStatusTone, leadStatusLabels, counts, budgetStyles, DB_TO_STATUS

⚠️ read-access-test/page.tsx überschreitet 500 KB → Babel-Deoptimierungs-Warnung im Build. Keine Funktionseinschränkung, aber Aufsplitten empfohlen (AP163+).

82.2 · Demo-Strings im Produktionscode

niedrig

src/server/tasks/actions.ts L35

Kommentar: "kds-demo Workspace"

Nur Kommentar – kein Laufzeiteffekt. Bereinigen in AP163.

niedrig

src/lib/ingest-auth.ts L10+L80

Beispiel-Kommentar: kds-demo als Slug

Nur Dokumentations-Kommentar – kein Laufzeiteffekt.

niedrig

src/app/api/ingest/*/route.ts

JSDoc-Beispiel: "workspaceSlug": "kds-demo"

Nur Kommentar in 5 Ingest-Routes. Könnte durch generische Beispiele ersetzt werden.

niedrig

src/server/offers/queries.ts L107

Fallback-String: "Demo-Angebot – keine echten Kundendaten"

Laufzeit-String – erscheint als nextAction-Text wenn kein Status/Risikolevel greift. Bereinigen in AP163.

keine

src/features/offers/mock-data.ts

"Demo-Angebot" in Mock-Datei

Mock-Daten – nur dev-interne Nutzung, nie in DB oder Produktion.

82.3 · Workspace-Isolation (Multi-Tenancy)

Alle findMany/findFirst-Queries

workspaceId in where-Clause in allen List-Queries (CRM, Tasks, Projects, Offers, Dashboard, Notes, Workspace)

Alle updateMany/deleteMany

workspaceId + id in where-Clause in allen Write-Actions – kein Fremddatenzugriff möglich

FK-Workspace-Validierung

Vor jedem Create-FK: count({ where: { id, workspaceId } }) in Actions und Notes

workspaceId nie an Client

Kein workspaceId-Prop in Client-Komponenten, kein NEXT_PUBLIC_, kein process.env in Features

Ingest API

workspaceSlug → workspaceId serverseitig; ACTIVE-Status-Check; timing-safe Key-Vergleich

⚠️

workspace.ts User-Lookup

db.user.findFirst({ where: { email } }) ohne workspaceId-Filter. Safe bei eindeutiger E-Mail, aber latentes Multi-Workspace-Risiko wenn gleiche E-Mail in mehreren Workspaces existiert. → Folge-AP

82.4 · Sicherheitsbefunde

mittel

deleteNote() – fehlender Rollen-Check

src/server/notes/actions.ts: deleteNote() prüft nur workspaceId, nicht die Rolle. canDelete-Prop im Client steuert UI-Sichtbarkeit korrekt (ADMIN/MANAGER), aber der Server-Action selbst kann von jedem authentifizierten Workspace-User (inkl. MEMBER) aufgerufen werden. Direkt-Aufruf via curl oder Formular möglich.

→ Fix: ALLOWED_WRITE_ROLES-Check in deleteNote() einbauen. Folge-AP163.

niedrig

createNote() – authorId nicht session-gebunden

src/server/notes/actions.ts L66-67: authorId wird via db.user.findFirst({where: { workspaceId }}) ermittelt – das ist der erste User im Workspace, nicht der eingeloggte User. getCurrentWorkspaceContext() liefert keine dbUserId. Notizen werden dadurch alle dem ersten angelegten Workspace-User zugeschrieben.

→ Fix: ctx.dbUserId in WorkspaceContext ergänzen, authorId direkt aus ctx laden. Folge-AP163.

info

updateTaskStatus / updateProjectStatus / updateLeadStatus – kein ALLOWED_WRITE_ROLES

Diese Actions prüfen nur workspaceId + Authentifizierung – kein Rollen-Check. Bewusste Design-Entscheidung (AP127/AP147): alle Workspace-User dürfen Status setzen. Kein Befund – dokumentiert als bekannte Ausnahme.

82.5 · Server Actions Rollenchecks – Vollständigkeit

Actionctx.okALLOWED_WRITE_ROLESworkspaceId-FilterBewertung
createTask / updateTask / deleteTaskkorrekt
createProject / updateProject / deleteProjectkorrekt
updateProjectStatusbewusste Ausnahme (alle User)
createOffer / updateOffer / deleteOfferkorrekt
updateOfferStatuskorrekt (A+M)
createContact / updateContact / deleteContactkorrekt
createCompany / updateCompany / deleteCompanykorrekt
createLead / updateLead / deleteLeadkorrekt
updateLeadStatusbewusste Ausnahme (alle User)
updateTaskStatusbewusste Ausnahme (alle User)
createNotealle User erlaubt (bewusst)
deleteNote⚠️ Rollen-Check fehlt – AP163

82.6 · Empfohlene Folge-APs

kritischAP163deleteNote() Rollen-Check + authorId-Fix + Demo-String-Bereinigung

deleteNote(): ALLOWED_WRITE_ROLES einbauen · createNote(): ctx.dbUserId statt findFirst() · Demo-Strings in tasks/actions.ts + offers/queries.ts bereinigen · 5 unused-var Warnings fixen

mittelAP164workspace.ts User-Lookup workspaceId-Absicherung

db.user.findFirst({ where: { email } }) → findFirst({ where: { email, workspaceId } }) – benötigt workspaceId aus Session. Verhindert Ambiguität bei gleicher E-Mail in mehreren Workspaces.

niedrigAP165read-access-test aufsplitten

Datei >12.000 Zeilen, >500 KB → Babel-Warnung im Build. Aufteilen in read-access-test/index und mehrere Unter-Seiten oder statische Komponenten pro Abschnitt.

niedrigAP166Offer Detail-Seite /offers/[id]

Einzige fehlende Detail-Seite. Analog AP158 (Project Detail). Mit Notes Feed.

Gesamtbewertung: SaaS-fähig mit bekannten Einschränkungen

Build ✅ · TypeScript ✅ · Workspace-Isolation ✅ · Rollen-Checks 11/12 ✅ · 1 mittlerer Befund (deleteNote) · 1 latentes Risiko (User-Lookup) · Keine kritischen Datenlecks · Keine hardcodierten Secrets · Kein middleware.ts · NEXT_PUBLIC_-Leaks: keine · console.log in Server-Code: keine

Schreibstatus

Schreibfunktionen weiterhin global blockiert

Schreibfunktionen sind global blockiert. Aktivierung folgt in AP061+.

68

AP146-147 – Offer Edit + Project Status-Control

21.05.2026 · Branch ap146-147/offer-edit-project-status · TypeScript: 0 Fehler

68.1 · AP146 – Offer Update

Server Action

updateOffer(offerId, formData)

ADMIN+MANAGER · updateMany+workspaceId · title/valueRange/riskLevel/notes

EditOfferDialog (amber)

Titel · Größenordnung · Risikolevel · Notizen

defaultValue aus dbTitle/dbValueRange/dbRiskLevel/dbNotes

68.2 · AP147 – Project Status-Control

Server Action

updateProjectStatus(projectId, newStatus)

ALLE Workspace-User (ADMIN+MANAGER+MEMBER)

Kein ALLOWED_WRITE_ROLES-Check · analog updateLeadStatus

ProjectStatusControl (violet)

5 Chips: PLANNING · ACTIVE · ON_HOLD · COMPLETED · CANCELLED

Kein canManage-Gate · sichtbar wenn dbStatus vorhanden

68.3 · CRUD-Matrix nach AP147 (vollständig)

ModulCreateReadUpdateDeleteStatus-ControlRollen
Tasks✓ AP125✓ AP085✓ AP129✓ AP129✓ AP127 (alle)CRE/UPD/DEL: A+M · Status: alle
Kontakte✓ AP130✓ AP073✓ AP143✓ AP139CRE/UPD/DEL: ADMIN+MANAGER
Firmen✓ AP135✓ AP073✓ AP144✓ AP140CRE/UPD/DEL: ADMIN+MANAGER
Leads✓ AP136✓ AP073✓ AP145✓ AP138✓ AP138 (alle)CRE/UPD/DEL: A+M · Status: alle
Angebote✓ AP134✓ AP081✓ AP146✓ AP137✓ AP137 (A+M)alle Ops: ADMIN+MANAGER
Projekte✓ AP131✓ AP083✓ AP142✓ AP142✓ AP147 (alle)CRE/UPD/DEL: A+M · Status: alle

✓ Alle 6 Module vollständig CRUD-fähig. Copy & Paste Deployability erreicht.

Hinweis AP065

AP065 prüft die bestehende Schutzmatrix transparent – keine Seite wurde neu geschützt und keine Berechtigung geändert. /dashboard, /crm, /projects, /tasks und /offers bleiben weiterhin offen. /ai-review ist weiterhin nicht geschützt. Schreibfunktionen global blockiert. Keine middleware.ts.

67

AP142-145 – Edit + Delete für alle CRM- und Projektdatensätze

21.05.2026 · Branch ap142-145/edit-dialogs · TypeScript: 0 Fehler

67.1 · Neue Server Actions

APFunktionDateiSicherheit
AP142updateProject(projectId, formData)server/projects/actions.tsADMIN+MANAGER · updateMany+workspaceId
AP142deleteProject(projectId)server/projects/actions.tsADMIN+MANAGER · deleteMany+workspaceId
AP143updateContact(contactId, formData)server/crm/actions.tsADMIN+MANAGER · updateMany+workspaceId
AP144updateCompany(companyId, formData)server/crm/company-actions.tsADMIN+MANAGER · updateMany+workspaceId
AP145updateLead(leadId, formData)server/crm/lead-actions.tsADMIN+MANAGER · updateMany+workspaceId

67.2 · Neue UI-Komponenten

EditProjectDialogAP142

Titel · Status · Beschreibung

DeleteProjectButtonAP142

2-Schritt-Inline-Bestätigung

EditContactDialogAP143

Vor-/Nachname · E-Mail · Tel · Beruf

EditCompanyDialogAP144

Name · Typ · Website · Stadt/Land · Notizen

EditLeadDialogAP145

Titel · Priorität · Quelle · Notizen

67.3 · Typ- und Query-Erweiterungen

ProjectdbStatus?: string

Roher DB-Status für EditProjectDialog (PLANNING/ACTIVE/…)

ContactdbFirstName, dbLastName, dbEmail, dbPhone, dbJobTitle

Rohfelder – splitbar für Edit, optional → Mock-Daten kompatibel

CompanydbType, dbWebsite, dbIndustry, dbCity, dbCountry, dbNotes

Rohfelder für EditCompanyDialog, optional

LeaddbTitle, dbPriority, dbSource, dbNotes

Rohfelder für EditLeadDialog – dbStatus schon seit AP138

67.4 · CRUD-Matrix nach AP145 (vollständig)

ModulCreateReadUpdateDeleteStatus-ControlRollen
Tasks✓ AP125✓ AP085✓ AP129✓ AP129✓ AP127CRE/UPD/DEL: ADMIN+MANAGER · Status: alle
Kontakte✓ AP130✓ AP073✓ AP143✓ AP139CRE/UPD/DEL: ADMIN+MANAGER
Firmen✓ AP135✓ AP073✓ AP144✓ AP140CRE/UPD/DEL: ADMIN+MANAGER
Leads✓ AP136✓ AP073✓ AP145✓ AP138✓ AP138CRE/UPD/DEL: ADMIN+MANAGER · Status: alle
Angebote✓ AP134✓ AP081✓ AP137✓ AP137CRE/DEL: ADMIN+MANAGER · Status: ADMIN+MANAGER
Projekte✓ AP131✓ AP083✓ AP142✓ AP142CRE/UPD/DEL: ADMIN+MANAGER

Noch offen: Offer-Update (kein EditOfferDialog), Project-Status-Control.

67.5 · Sicherheitsgrenzen AP142-145

getAlle Writes: getCurrentWorkspaceContext()
Rollenprüfung: ALLOWED_WRITE_ROLES server-seitig
Cross-Workspace: updateMany/deleteMany mit workspaceId
count===0 → Fehler statt Silent-Fail
revalidatePath() nach jedem Erfolg
workspaceId nie an Client weitergegeben
Keine technischen Fehlerdetails im Response
canManage-Prop steuert UI-Sichtbarkeit
TypeScript: 0 Fehler nach allen Änderungen

Abschnitt 83

AP163 – Notes Security Cleanup

Branch: ap163/notes-security-cleanup · Keine DB-Migration · Kein Deployment

83.1 · Build + ESLint Ergebnis

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings (vorher: 5)

83.2 · Sicherheitsfixes

DateiProblemFixSeverity
notes/actions.ts · deleteNote()Kein serverseitiger Rollencheck – MEMBER konnte löschenALLOWED_WRITE_ROLES check vor DB-Zugriff (ADMIN/MANAGER only)MEDIUM
notes/actions.ts · createNote()authorId aus db.user.findFirst({ workspaceId }) – falscher User möglichctx.dbUserId aus Session (getCurrentWorkspaceContext)LOW
server/auth/workspace.tsdbUserId fehlte in WorkspaceContextdbUserId?: string zum Typ + dbUser.id in ok-Return ergänztLOW
tasks/actions.ts L35Demo-Kommentar "kds-demo Workspace"Neutraler Kommentar "aktuellen Workspace"INFO
offers/queries.ts L107Demo-Fallback "Demo-Angebot – keine echten Kundendaten"Neutraler Fallback "Angebot in Bearbeitung"INFO

83.3 · ESLint Warnings behoben (5 → 0)

DateiWarningFix
crm/components/LeadList.tsxgetLeadStatusTone, leadStatusLabels importiert aber nicht verwendetAus Import entfernt
dashboard/components/DashboardOverview.tsxcounts aus Props destructured aber nie verwendetAus Destructuring entfernt
projects/detail/ProjectDetailOverview.tsxbudgetStyles const deklariert aber nie verwendetconst entfernt
tasks/components/TaskStatusControl.tsxDB_TO_STATUS const deklariert aber nie verwendetconst entfernt

83.4 · Sicherheitsgrenzen nach AP163

deleteNote: ALLOWED_WRITE_ROLES server-seitig geprüft
createNote: authorId ausschließlich aus ctx.dbUserId (Session)
dbUserId nur in WorkspaceContext – nie an Client
Cross-Workspace: deleteMany mit workspaceId-Filter
Kein DB-User lookup ohne E-Mail+workspaceId
Alle Server Actions: getCurrentWorkspaceContext()
TypeScript: 0 Fehler · ESLint: 0 Warnings
Keine Demo-Strings in produktivem Code

83.5 · Empfohlene Folge-APs

AP164: workspace.ts User-Lookup Härtung

findFirst ohne E-Mail-Filter ist latentes Multi-Workspace-Risiko – workspaceId + email kombinieren

AP165: read-access-test splitten

>12.000 Zeilen, >500 KB – Babel-Warnung bei jedem Build; Datei aufteilen

AP166: Offer Detail Page /offers/[id]

Keine Detail-Ansicht für Angebote – analog zu /projects/[id] und /crm/[id]

Abschnitt 84

AP164 – Workspace User-Lookup Härtung

Branch: ap164/workspace-lookup-hardening · Keine DB-Migration · Kein Deployment

84.1 · Dokumentiertes Risiko (aus AP162)

Latentes Multi-Workspace-Risiko in workspace.ts

Das Prisma-User-Modell hat kein globales @unique auf email – nur @@unique([workspaceId, email]). Dieselbe E-Mail kann theoretisch in mehreren Workspaces existieren.

findFirst({ where: { email } }) ohne Ordering war non-deterministisch: bei E-Mail-Duplikaten über Workspaces hinweg konnte ein zufälliger Datensatz zurückgegeben werden.

84.2 · Technische Härtung (ohne DB-Migration)

Deterministisches Ordering

orderBy: { createdAt: "asc" } auf findFirst() – ältester Datensatz gewinnt, Ergebnis ist stabil und reproduzierbar.

workspaceId-Guard

Explizite Prüfung: dbUser.workspaceId muss vorhanden sein. Schützt vor korrupten User-Datensätzen ohne Workspace-Zuordnung.

Konsistenzprüfung (Defense in Depth)

workspace.id === dbUser.workspaceId wird explizit geprüft. Verhindert, dass ein Fehler im Lookup-Pfad einen workspace-fremden Context liefert.

Alle Daten aus demselben dbUser-Kontext

workspaceId, dbUserId und userRole stammen alle aus demselben DB-User-Datensatz – kein separater Lookup mit anderem Kontext.

84.3 · Offene Limitation (Folge-AP empfohlen)

Echte Eindeutigkeit erfordert DB-Migration oder Session-Erweiterung

AP164 mildert das Risiko ab, löst es aber nicht vollständig. Für produktionsfähiges Multi-Workspace-SaaS empfohlen:

  • Option A: email @unique auf dem User-Modell (DB-Migration), wenn eine E-Mail nur einem Workspace angehören darf
  • Option B: workspaceId in die Auth.js-Session aufnehmen (bei Login setzen), sodass der Lookup über where: { email, workspaceId } eindeutig ist

84.4 · Build + Sicherheitsstatus

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
DB-Migration: — Keine
Deployment: — Keines
Auth-Grundstruktur: ✅ Unverändert

84.5 · Folgeempfehlung

AP165: read-access-test Wartbarkeit verbessern

Datei >500 KB / >12.000 Zeilen – Babel-Warnung bei jedem Build. Statische Dokumentationsabschnitte auslagern, ohne Rechte-/Auth-Logik zu ändern.

Abschnitt 85

AP165 – read-access-test Wartbarkeit verbessert

Branch: ap165/improve-read-access-test-maintainability · Keine DB-Migration · Kein Deployment

85.1 · Ausgangslage

Babel-Warnung bei jedem Build

read-access-test/page.tsx hatte 12.477 Zeilen und überstieg 500 KB. Babel deoptimiert bei jedem Build die Stylingverarbeitung dieser Datei und gibt eine Warnung aus.

85.2 · Durchgeführte Maßnahmen

Abschnitte 11–30 → ReadAccessTestHistoryA.tsx

6.644 Zeilen statische Dokumentation in separate Komponente extrahiert. Keine Laufzeitabhängigkeiten, keine Props.

Abschnitte 31–83 → ReadAccessTestHistoryB.tsx

4.426 Zeilen statische Dokumentation in separate Komponente extrahiert. Keine Laufzeitabhängigkeiten, keine Props.

page.tsx: 12.477 → 1.432 Zeilen

Hauptdatei enthält nur noch Imports, Konstanten, Helper-Komponenten, die aktiven Abschnitte 1–10 (mit Laufzeitdaten) und neue Abschnitte. Babel-Warnung beseitigt.

Keine Rechte-/Auth-/Route-Logik geändert

getReadPageAccessMatrix(), canReadModule(), MODULE_LABELS, alle Helper-Komponenten und alle dynamischen Abschnitte 1–10 sind unverändert in page.tsx.

85.3 · Neue Dateistruktur

DateiZeilenInhalt
page.tsx1.432Imports, Konstanten, Helper, Abschnitte 1–10 (live), neue APs
ReadAccessTestHistoryA.tsx6.644Statische Dokumentation Abschnitte 11–30
ReadAccessTestHistoryB.tsx4.426Statische Dokumentation Abschnitte 31–83

85.4 · Build + Status

pnpm build: ✅ Erfolgreich, 0 Fehler
Babel-Warnung: ✅ Behoben (page.tsx < 500 KB)
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
DB-Migration: — Keine
Auth-Logik: ✅ Unverändert

85.5 · Folgeempfehlung

AP166: Offer Detail Page /offers/[id]

Fehlende Detailansicht für Angebote – analog zu /projects/[id] und /crm/leads/[id].

Abschnitt 84

AP164 – Workspace User-Lookup Härtung

Branch: ap164/workspace-lookup-hardening · Keine DB-Migration · Kein Deployment

84.1 · Dokumentiertes Risiko (aus AP162)

Latentes Multi-Workspace-Risiko in workspace.ts

Das Prisma-User-Modell hat kein globales @unique auf email – nur @@unique([workspaceId, email]). Dieselbe E-Mail kann theoretisch in mehreren Workspaces existieren.

findFirst({ where: { email } }) ohne Ordering war non-deterministisch: bei E-Mail-Duplikaten über Workspaces hinweg konnte ein zufälliger Datensatz zurückgegeben werden.

84.2 · Technische Härtung (ohne DB-Migration)

Deterministisches Ordering

orderBy: { createdAt: "asc" } auf findFirst() – ältester Datensatz gewinnt, Ergebnis ist stabil und reproduzierbar.

workspaceId-Guard

Explizite Prüfung: dbUser.workspaceId muss vorhanden sein. Schützt vor korrupten User-Datensätzen ohne Workspace-Zuordnung.

Konsistenzprüfung (Defense in Depth)

workspace.id === dbUser.workspaceId wird explizit geprüft. Verhindert, dass ein Fehler im Lookup-Pfad einen workspace-fremden Context liefert.

Alle Daten aus demselben dbUser-Kontext

workspaceId, dbUserId und userRole stammen alle aus demselben DB-User-Datensatz – kein separater Lookup mit anderem Kontext.

84.3 · Offene Limitation (Folge-AP empfohlen)

Echte Eindeutigkeit erfordert DB-Migration oder Session-Erweiterung

AP164 mildert das Risiko ab, löst es aber nicht vollständig. Für produktionsfähiges Multi-Workspace-SaaS empfohlen:

  • Option A: email @unique auf dem User-Modell (DB-Migration), wenn eine E-Mail nur einem Workspace angehören darf
  • Option B: workspaceId in die Auth.js-Session aufnehmen (bei Login setzen), sodass der Lookup über where: { email, workspaceId } eindeutig ist

84.4 · Build + Sicherheitsstatus

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
DB-Migration: — Keine
Deployment: — Keines
Auth-Grundstruktur: ✅ Unverändert

84.5 · Folgeempfehlung

AP165: read-access-test Wartbarkeit verbessern

Datei >500 KB / >12.000 Zeilen – Babel-Warnung bei jedem Build. Statische Dokumentationsabschnitte auslagern, ohne Rechte-/Auth-Logik zu ändern.

Abschnitt 86

AP166 – Offer Detail Page /offers/[id]

Branch: ap166/offer-detail-page · Keine DB-Migration · Kein Deployment

86.1 · Neue Route

/offers/[id] → OfferDetailPage

Neue dynamische Route. Serverseitig geschützt via getReadPageAccess("offers"). Zugriff für ADMIN, MANAGER, MEMBER.

getOfferDetailData(workspaceId, offerId)

Neue Query in server/offers/detail-queries.ts. findFirst mit workspaceId-Filter – Cross-Workspace-Schutz garantiert.

OfferDetailView.tsx

Neue Feature-Komponente. Header mit Titel, Badges, Status-Control, Edit+Delete (canManage). Kennzahlen-Grid, Verknüpfungen (Company/Lead/Projekt/Creator), Angebotsnotiz, NotesFeed.

OfferList.tsx – Link auf Detailseite

Angebots-Titel in der Listenansicht ist jetzt ein Link auf /offers/[id]. Hover-Effekt amber.

86.2 · Sicherheitsstatus

getReadPageAccess("offers") vor jedem Datenladen
workspaceId-Filter im DB-Query (findFirst)
workspaceId nicht an Client weitergegeben
canManage server-seitig aus normalizedRole
Keine technischen Fehlerdetails im UI
Notes-Feed: Workspace-isoliert (AP159)

86.3 · Build

pnpm build: ✅ Erfolgreich
/offers/[id]: ✅ Route aktiv (ƒ Dynamic)
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
DB-Migration: — Keine
Deployment: — Keines

86.4 · Folgeempfehlung

AP167: Production and Customer Readiness Review

Bewerten, was noch fehlt, damit KDS OS intern nutzbar und später kundenfähig wird.

Abschnitt 87

AP167 – Production + Customer Readiness Review

Branch: ap167/production-customer-readiness-review · Nur Dokumentation · Keine DB-Migration · Kein Deployment · Keine Codeänderung

Dieser Abschnitt bewertet, was noch fehlt, damit KDS OS intern produktiv nutzbar und später kundenfähig (SaaS / Modulbasis) wird. Kein Feature-Code, kein Risiko. Reine Bewertung des Istzustands.

87.1 · ENV-Anforderungen (ohne echte Werte)

VariableZweckProduktiv-Status
DATABASE_URLNeon PostgreSQL (pgBouncer)⚠️ Neon Dev → Prod-Branch erforderlich
DIRECT_URLDirektverbindung für Migrations⚠️ Neon Dev → Prod-Branch erforderlich
AUTH_SECRETAuth.js JWT-Signing-Secret✅ Muss pro Umgebung gesetzt sein
AUTH_URLCallback-Base-URL⚠️ Muss auf Vercel-Domain zeigen
AUTH_MICROSOFT_ENTRA_ID_IDEntra Client-ID✅ Bereits konfiguriert (nicht im Repo)
AUTH_MICROSOFT_ENTRA_ID_SECRETEntra Client-Secret✅ Bereits konfiguriert (nicht im Repo)
AUTH_MICROSOFT_ENTRA_ID_ISSUEREntra Tenant-Issuer-URL✅ Bereits konfiguriert (nicht im Repo)
INGEST_API_KEYAPI-Key für n8n / HubSpot Ingest⚠️ Muss in Vercel gesetzt werden
INGEST_API_KEYSMulti-Workspace JSON (optional)— Für Multi-Tenant-Betrieb vorbereitet

87.2 · Deployment-Anforderungen

⚠️ Neon Dev → Neon Production

Aktuell läuft die Datenbank auf dem Neon-Dev-Branch. Für Produktivbetrieb: separaten Prod-Branch anlegen, DATABASE_URL + DIRECT_URL in Vercel aktualisieren, Migration deployen (npx prisma migrate deploy).

⚠️ Vercel: AUTH_URL auf Produktiv-Domain setzen

AUTH_URL muss auf die Vercel-Produktiv-Domain zeigen (z. B. https://kds-os.vercel.app). Entra-ID-App-Registrierung: Redirect URI muss diese URL enthalten. Sonst schlägt OAuth-Callback fehl.

⚠️ INGEST_API_KEY in Vercel setzen

Der API-Key für den n8n/HubSpot-Ingest fehlt noch in den Vercel-Environment-Variables. Ohne ihn lehnt /api/ingest/* alle Requests mit 401 ab.

Next.js Build + Vercel Deployment

pnpm build läuft fehlerfrei durch. Vercel-Deployment sollte direkt funktionieren – keine buildspezifischen Blocker bekannt.

Prisma Schema deployed

Alle Migrationen sind committed. Nach Datenbankwechsel: npx prisma migrate deploy ausführen. Kein prisma db push in Produktion.

87.3 · Microsoft Entra ID – Redirect-URI-Anforderungen

Checkliste für Produktivbetrieb:

  • Entra-App-Registrierung: Redirect URI https://[domain]/api/auth/callback/microsoft-entra-id eintragen
  • AUTH_URL in Vercel: muss auf exakt dieselbe Domain zeigen wie die Redirect URI
  • Tenant-ID in ISSUER-URL: muss korrekt sein (kein 'common' bei Single-Tenant)
  • Client-Secret: Ablaufdatum prüfen und vor Ablauf rotieren
  • Für neue Kunden-Workspaces: separater Entra-Tenant oder Gastnutzer-Konzept klären

87.4 · Rollen- und Modulrechte – Istzustand

AspektStatusAnmerkung
Lese-Zugriff nach Rolle✅ ImplementiertcanReadModule() pro Modul + Rolle, getReadPageAccess() in jeder Seite
Schreib-Zugriff nach Rolle✅ ImplementiertALLOWED_WRITE_ROLES = [ADMIN, MANAGER] in allen Server Actions
canWriteModule()⚠️ Global falseGibt immer false zurück – für UI-Level-Blocking implementiert, Aktivierung späterer AP
MEMBER-Schreibrechte✅ Bewusst eingeschränktMEMBER: nur Lesezugriff + Task-Statusänderung (AP122/123)
Workspace-Isolation✅ ImplementiertJede DB-Query enthält workspaceId-Filter; Cross-Workspace-Schutz in allen Actions
Rollenprüfung serverseitig✅ ImplementiertAlle kritischen Actions prüfen ctx.userRole, nie aus Client
Per-Workspace-Rollen✅ VorhandenUserRole Enum: ADMIN / MANAGER / MEMBER pro Workspace-User
Modulrechte per Workspace⚠️ Noch nichtAktuell globale Modulrechte – per-Workspace-Modulfreischaltung fehlt (Folge-AP)

87.5 · Soft Delete vs. Hard Delete

Istzustand: Alle Deletes sind Hard Deletes (deleteMany)

Company, Contact, Lead, Project, Offer, Task, Note: alle Löschvorgänge sind permanent und sofort. Es gibt kein deletedAt-Feld, keinen Papierkorb und kein Undo.

Empfehlung für Produktivbetrieb:

  • Soft Delete (deletedAt Timestamp + isDeleted Flag) für alle Business-Entitäten einführen
  • Besonders kritisch für Company, Contact, Offer (irreversibler Datenverlust)
  • Erfordert DB-Migration + Abfragen-Update (status WHERE deletedAt IS NULL)
  • Folge-AP: AP-SoftDelete (Scope und Prio mit André abstimmen)

87.6 · Audit Logging

Istzustand: Kein Audit Log

Wer hat wann was erstellt, geändert oder gelöscht – ist nicht nachvollziehbar. Notes/Activity-Feed gibt es für einzelne Entitäten, aber kein systemweites Audit-Trail.

Empfehlung für internen MVP:

  • Priorität: niedrig für internes KDS-Tool (Vertrauen in User), mittel für kundenfähiges SaaS
  • Minimallösung: createdAt/updatedAt bereits vorhanden auf allen Entitäten
  • Vollösung: AuditLog-Modell mit userId, action, entityType, entityId, timestamp

87.7 · Backup + Recovery

✅ Neon: Automatische Backups inklusive

Neon PostgreSQL bietet automatische Backups und Point-in-Time-Recovery auf dem Produktions-Branch. Für den Dev-Branch sind Backups begrenzt.

Empfehlung:

  • Neon Production Branch verwenden (nicht Dev-Branch) – enthält vollständige Backup-Funktionalität
  • Regelmäßige Exports als Sicherheitsnetz (pg_dump via Neon CLI)
  • Recovery-Test vor Go-Live durchführen

87.8 · Ingest API Betriebssicherheit

AspektStatus
API-Key-Auth via x-api-key Header✅ Implementiert (timingSafeEqual)
workspaceSlug → workspaceId Mapping✅ Implementiert (DB-Lookup, nie direkt aus Header)
Rate Limiting⚠️ Nicht implementiert – Vercel-Edge-Limits als Schutz
Ingest-Duplikat-Erkennung⚠️ Nicht implementiert – wiederholter Import führt zu Duplikaten
Fehlende Entity-Validierung⚠️ Nur Basisvalidierung – keine vollständige Schemprüfung
INGEST_API_KEY in Vercel gesetzt⚠️ Muss noch konfiguriert werden
Multi-Workspace INGEST_API_KEYS✅ Vorbereitet (JSON-Format unterstützt)
Ingest-Logs / Monitoring⚠️ Keine strukturierten Logs – Fehler nur als HTTP-Status

87.9 · Kunden-Onboarding-Prozess

Istzustand: Copy & Paste Deployability (manuell)

Das Onboarding-Script scripts/onboard-client.template.ts (AP132) definiert die Schritte manuell. Kein Self-Service, kein automatisiertes Provisioning.

Schritte für neuen Kunden-Workspace:

  1. Workspace-Datensatz in DB anlegen (name, slug)
  2. User-Datensätze anlegen (email, role, workspaceId)
  3. Entra-ID: Nutzer zur App-Registrierung hinzufügen oder neuen Tenant einrichten
  4. INGEST_API_KEY für diesen Workspace setzen (oder INGEST_API_KEYS JSON erweitern)
  5. n8n-Workflows für diesen Workspace konfigurieren (workspaceSlug setzen)
  6. Erstlogin testen

⚠️ Fehlend für skalierbares Kunden-Onboarding:

  • Self-Service-Registrierung oder Admin-UI zum Workspace-Anlegen
  • Automatisiertes Invite-/User-Provisioning
  • Per-Workspace-Modulfreischaltung
  • Billing-Integration (falls SaaS)

87.10 · CRM als auskoppelbares Kundenmodul – Bewertung

✅ Technische Grundlage vorhanden

  • Workspace-isoliertes Datenmodell: Company, Contact, Lead pro Workspace
  • Vollständiges CRUD: Create, Read, Update, Delete für alle CRM-Entitäten
  • Rollenbasierte Zugriffskontrolle: ADMIN/MANAGER schreiben, MEMBER liest
  • Notes/Activity-Feed auf CRM-Entitäten
  • Suchfilter + Sortierung in allen CRM-Listen
  • HubSpot-Import via n8n (AP160)

⚠️ Fehlend für kundenfähiges CRM-Modul:

  • Soft Delete (Hard Deletes aktuell – Datenverlust-Risiko)
  • Per-Workspace-Konfiguration (welche Module sind freigeschaltet?)
  • Export-Funktion (CSV / Excel)
  • Datenschutz / DSGVO: Recht auf Löschung, Exportpflicht
  • Duplikat-Erkennung bei Import
  • Branding / White-Label (Domain, Logo)

87.11 · Gesamtbewertung

✅ Bereit für internen Produktivbetrieb (nach ENV-Setup)

  • Build läuft fehlerfrei
  • Auth via Microsoft Entra ID
  • Workspace-isoliertes Multi-Tenant-Datenmodell
  • Vollständiges CRUD für alle Kernmodule
  • Serverseitige Rollenkontrolle
  • Neon: automatische Backups im Prod-Branch

⚠️ Noch nicht kundenfähig / SaaS-ready

  • Hard Deletes → Soft Delete erforderlich
  • Kein Self-Service-Onboarding
  • Kein Audit Log
  • Kein Rate Limiting auf Ingest API
  • Per-Workspace-Modulkonfiguration fehlt
  • DSGVO / Export noch nicht vorhanden

87.12 · Priorisierte Folge-APs (Empfehlung)

HOCHAP-SoftDelete

Soft Delete für alle Business-Entitäten

deletedAt Timestamp + Abfrage-Filter. DB-Migration erforderlich.

HOCHAP-EnvProd

Neon Prod-Branch + Vercel ENV konfigurieren

DATABASE_URL, AUTH_URL, INGEST_API_KEY in Vercel Prod-Umgebung setzen.

MITTELAP-WorkspaceAdmin

Admin-UI: Workspace + User anlegen

Erleichtert Kunden-Onboarding ohne direkte DB-Zugriffe.

MITTELAP-ModuleConfig

Per-Workspace-Modulfreischaltung

Welche Module sind für einen Workspace aktiv? Schema + UI.

NIEDRIGAP-AuditLog

Audit Log Grundstruktur

Wer hat wann was geändert – AuditLog-Modell + Middleware.

NIEDRIGAP-IngestHardening

Ingest API: Rate Limiting + Duplikat-Erkennung

Betriebssicherheit für n8n-Sync verbessern.

Abschnitt 88

AP168 – Production ENV and Deployment Readiness

Branch: ap168/production-environment-readiness · Keine DB-Migration · Kein Deployment · Keine echten Werte

88.1 · Deliverables

docs/production-readiness.md

Neue Datei. Vollständige Production-Readiness-Dokumentation: ENV-Variablen, Vercel Setup, Neon Prod-Branch, Entra-ID Redirects, Domain-Optionen, Deployment-GO-Checkliste, Rollback-Logik, Post-Go-Live-Checkliste. Keine echten Werte.

.env.example ergänzt

INGEST_API_KEY und INGEST_API_KEYS (JSON-Format) als Platzhalter hinzugefügt. Diese ENVs wurden vom ingest-auth.ts seit AP154 genutzt, fehlten aber im Template.

88.2 · ENV-Variablen – noch manuell zu setzen (keine echten Werte hier)

VariableWo setzenStatus
DATABASE_URLVercel → Env Vars (Neon Prod-Branch)⚠️ Neon Prod-Branch erforderlich
DIRECT_URLVercel → Env Vars (Neon Prod-Branch)⚠️ Neon Prod-Branch erforderlich
AUTH_SECRETVercel → Env Vars⚠️ Neu generieren (openssl rand -base64 32)
AUTH_URLVercel → Env Vars⚠️ Auf Produktiv-Domain setzen
AUTH_MICROSOFT_ENTRA_ID_IDVercel → Env Vars✅ Bereits vorhanden (nicht im Repo)
AUTH_MICROSOFT_ENTRA_ID_SECRETVercel → Env Vars✅ Bereits vorhanden (nicht im Repo)
AUTH_MICROSOFT_ENTRA_ID_ISSUERVercel → Env Vars✅ Bereits vorhanden (nicht im Repo)
INGEST_API_KEYVercel → Env Vars⚠️ Neu generieren und in n8n hinterlegen

88.3 · Deployment-GO Kurzcheckliste

⚠️ Noch offen (André muss tätig werden)

  • Neon Production Branch erstellen
  • DATABASE_URL + DIRECT_URL in Vercel aktualisieren
  • AUTH_URL auf Produktiv-Domain setzen
  • Entra-ID: Redirect URI für Produktiv-Domain eintragen
  • INGEST_API_KEY generieren und setzen
  • prisma migrate deploy auf Prod-DB ausführen
  • Workspace + User in Prod-DB anlegen
  • Erstlogin testen

✅ Bereits bereit

  • pnpm build: 0 Fehler, 0 Warnings
  • Prisma Migrations committed
  • Entra-ID Client-ID + Secret + Issuer konfiguriert
  • Auth.js JWT-Strategie produktionsreif
  • Vercel-Deployment technisch bereit
  • Ingest API mit timingSafeEqual gehärtet
  • Workspace-Isolation vollständig implementiert
  • Vollständige Doku: docs/production-readiness.md

88.4 · Build + Sicherheitsstatus

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
DB-Migration: — Keine
Deployment: — Keines (Vorbereitung)
Secrets im Repo: ✅ Keine

88.5 · Nächster Schritt

AP169: Neon Production Branch + Workspace-Seed

Sobald André GO gibt: Neon Prod-Branch anlegen, Migration deployen, ersten Workspace + Admin-User anlegen.

Abschnitt 89

AP169 – Soft Delete Strategy and Schema Plan

Branch: ap169/soft-delete-strategy · Nur Strategie + Dokumentation · Kein Code · Keine DB-Migration · Kein Deployment

89.1 · Risiko je Modul

ModulRisikoARCHIVED vorhanden?Empfehlung
CompanyHOCH✅ Ja (CompanyStatus.ARCHIVED)archiveCompany() – kein Schema-Change nötig
ContactHOCH 🔒✅ Ja (ContactStatus.ARCHIVED)archiveContact() – kein Schema-Change nötig
LeadMITTEL❌ NeinARCHIVED zu LeadStatus hinzufügen (Migration)
ProjectHOCH❌ NeinARCHIVED zu ProjectStatus hinzufügen (Migration)
OfferHOCH❌ NeinARCHIVED zu OfferStatus hinzufügen (Migration)
TaskNIEDRIG— (DONE / CANCELLED vorhanden)Hard Delete mit Status-Guard (nur OPEN/CANCELLED)
NoteMITTEL❌ Kein Status-EnumdeletedAt DateTime? + deletedById (Migration)

89.2 · Quick-Win ohne DB-Migration (Company + Contact)

✅ CompanyStatus.ARCHIVED und ContactStatus.ARCHIVED sind bereits im Schema

Diese beiden Module können sofort auf Soft Delete umgebaut werden, ohne eine DB-Migration zu erzeugen. Einzige Änderung: deleteCompany() / deleteContact() schreiben statt deleteMany ein updateMany({ data: { status: 'ARCHIVED' } }). Query-Filter muss dann status: { not: 'ARCHIVED' } ergänzt werden.

89.3 · Schema-Plan (DB-Migration erforderlich für Lead / Project / Offer / Note)

LeadStatus + ARCHIVED

enum LeadStatus: ARCHIVED hinzufügen. WON/LOST bleiben semantisch eigenständig – ARCHIVED = "aus Ansicht entfernt ohne Entscheidung".

ProjectStatus + ARCHIVED

enum ProjectStatus: ARCHIVED hinzufügen. CANCELLED bleibt für "explizit abgebrochen". ARCHIVED = "still aber nicht sichtbar".

OfferStatus + ARCHIVED

enum OfferStatus: ARCHIVED hinzufügen. DECLINED/EXPIRED bleiben – ARCHIVED = vom Benutzer manuell entfernt.

Note: deletedAt + deletedById

model Note: deletedAt DateTime? und deletedById String? hinzufügen. Notes-Feed zeigt nur Einträge mit deletedAt = null.

Task: Status-Guard (kein Schema-Change)

Hard Delete bleibt – aber nur für OPEN/CANCELLED. DONE-Tasks sollen nicht löschbar sein (Guard in deleteTask()). Kein Schema-Change für internen MVP.

89.4 · DSGVO-Hinweis

Soft Delete allein erfüllt DSGVO Art. 17 (Recht auf Vergessenwerden) nicht

Contact-Daten (personenbezogen) bleiben nach Archivierung in der DB. Für echte DSGVO-Löschung ist ein zusätzlicher Purge-/Anonymisierungs-Schritt notwendig: explizite Admin-Bestätigung + Hard Delete aller personenbezogenen Felder (oder Anonymisierung). Empfehlung: als AP-DsgvoPurge nach AP170 planen.

89.5 · Empfohlene Folge-APs

HOCHAP170-A

Prisma Schema Soft Delete Fields

ARCHIVED zu Lead/Project/Offer-Enums; deletedAt + deletedById zu Note. DB-Migration erforderlich. Kein Feature-Code.

HOCHAP170-B

Query Filtering

Alle findMany-Queries um ARCHIVED-Filter ergänzen. Depends on AP170-A. Kein Schema-Change.

HOCHAP170-C

Delete → Archive Actions

Company + Contact: sofort möglich (kein Schema-Change). Lead/Project/Offer/Note: nach AP170-A. Task: Status-Guard.

MITTELAP170-D

Restore / Archive UI

"Archivierte anzeigen"-Filter + Wiederherstellen-Button für ADMIN. Optionaler Archiv-Tab. Nach AP170-C.

89.6 · Build + Status

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
Code-Änderungen: — Keine (nur Doku)
DB-Migration: — Keine (AP170-A)
Deployment: — Keines

Abschnitt 90

AP170-A – Soft Delete Schema Foundation

Branch: ap170a/soft-delete-schema-foundation · DB-Migration angewendet · Kein Deployment · Keine Actions umgebaut

90.1 · Schema-Änderungen

LeadStatus + ARCHIVED

enum LeadStatus um ARCHIVED erweitert. Semantisch klar getrennt von WON/LOST (inhaltliche Entscheidung) und ON_HOLD (aktives Warten). ARCHIVED = aus Ansicht entfernt ohne Entscheidung.

ProjectStatus + ARCHIVED

enum ProjectStatus um ARCHIVED erweitert. CANCELLED bleibt für explizit abgebrochen. ARCHIVED = still gestellt, aus Listen ausgeblendet.

OfferStatus + ARCHIVED

enum OfferStatus um ARCHIVED erweitert. DECLINED/EXPIRED bleiben semantisch eigenständig. ARCHIVED = manuell vom User entfernt.

Note: deletedAt DateTime?

Neues Feld deletedAt DateTime? auf dem Note-Modell. null = aktiv im Feed; gesetzt = soft-deleted, wird von Queries ausgeblendet (AP170-B).

Note: deletedById String? + Relation

Neues Feld deletedById String? (FK auf User.id) + Relation deletedBy User? @relation("NoteDeletedBy"). Rückrelation deletedNotes auf User. Vollständige Audit-Spur: wer hat die Note wann gelöscht.

90.2 · Prisma Migration

20260526134711_soft_delete_schema_foundation

Migration angewendet auf Dev-Datenbank. Enthält ALTER TYPE für drei Enums (+ARCHIVED), ALTER TABLE notes ADD COLUMN deleted_at, ALTER TABLE notes ADD COLUMN deleted_by_id.

Für Produktion: npx prisma migrate deploy auf Neon Prod-Branch ausführen.

90.3 · Noch offen (AP170-B / AP170-C)

AP170-B: Query Filtering

  • findMany-Queries: status ≠ ARCHIVED für Lead/Project/Offer
  • Notes-Feed: WHERE deletedAt IS NULL
  • Detail-Queries: 404 für archivierte Einträge
  • Keine Schema-Änderung, keine Migration

AP170-C: Delete → Archive Actions

  • Company + Contact: sofort möglich (ARCHIVED bereits vorhanden)
  • Lead / Project / Offer: jetzt möglich (AP170-A abgeschlossen)
  • Note: deleteNote() setzt deletedAt + deletedById
  • Task: Status-Guard (nur OPEN/CANCELLED löschbar)

90.4 · Build + Migration-Status

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
ESLint: ✅ 0 Warnings
Prisma generate: ✅ Client generiert (v6.19.3)
Migration: ✅ Applied (Dev-DB)
Deployment: — Keines
Actions umgebaut: — Nein (AP170-C)
Query-Filter: — Nein (AP170-B)

Abschnitt 91

AP170-B · Query Filter – Archived & Deleted ausblenden

Stand: 2026-05-26 · Kein Schema-Change · Kein Deployment

91.1 · Ziel

Alle Standard-Listen- und Zählqueries liefern nur noch aktive Datensätze. Archivierte Einträge (status = ARCHIVED) und soft-gelöschte Notes (deletedAt ≠ null) werden aus dem normalen UI ausgeblendet. Kein Schema-Change, kein Deployment.

91.2 · Geänderte Query-Dateien

src/server/crm/queries.ts

company/contact/lead findMany → status: { not: "ARCHIVED" }

src/server/notes/queries.ts

note findMany → deletedAt: null

src/server/offers/queries.ts

offer findMany → status: { not: "ARCHIVED" }

src/server/projects/queries.ts

project findMany → status: { not: "ARCHIVED" }

src/server/dashboard/queries.ts

Counts, activeLeads/openOffers notIn, Pipeline groupBy, Alerts, Priority, Recent Activity

src/server/crm/details.ts

Company/Contact/Lead detail sub-queries: leads/projects/offers/contacts → ARCHIVED filter

src/server/projects/project-queries.ts

getWorkspaceProjectNames → status: { not: "ARCHIVED" } (Dropdown)

src/server/crm/company-queries.ts

getWorkspaceCompanyNames → status: { not: "ARCHIVED" } (Dropdown)

91.3 · Nicht geändert (bewusst)

offers/detail-queries.tsDetail-View – archiviertes Angebot soll direkt erreichbar bleiben
projects/detail-queries.tsDetail-View – archiviertes Projekt soll direkt erreichbar bleiben
tasks/queries.tsTasks haben kein ARCHIVED-Status im MVP
workspace/workspace-queries.tsAdmin-Statistik – zeigt bewusst alle Datensätze inkl. Archiv

91.4 · AP170-Fortschritt

AP170-A

Schema + Migration

✅ Abgeschlossen

AP170-B

Query Filter

✅ Abgeschlossen

AP170-C

Archive Actions

⏳ Offen

AP170-D

Restore/Archive UI

⏳ Offen

91.5 · Build + Status

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
Schema-Change: — Keiner
Deployment: — Keines
Actions: — Nicht verändert (AP170-C)
UI: — Nicht verändert (AP170-D)

Abschnitt 92

AP170-C · Convert Delete Actions to Archive Actions

Stand: 2026-05-26 · Kein Schema-Change · Kein Deployment

92.1 · Ziel

Alle produktiven Delete Actions auf Soft Delete umgestellt. Keine Daten werden mehr hart gelöscht — stattdessen werden Einträge archiviert und durch AP170-B automatisch aus Standard-Listen ausgeblendet.

92.2 · Umgebaute Server Actions

deleteCompany()server/crm/company-actions.ts

deleteMany → updateMany status=ARCHIVED

deleteContact()server/crm/actions.ts

deleteMany → updateMany status=ARCHIVED (DSGVO-Purge bleibt Folge-AP)

deleteLead()server/crm/lead-actions.ts

deleteMany → updateMany status=ARCHIVED

deleteProject()server/projects/actions.ts

deleteMany → updateMany status=ARCHIVED

deleteOffer()server/offers/actions.ts

deleteMany → updateMany status=ARCHIVED

deleteNote()server/notes/actions.ts

deleteMany → updateMany deletedAt=now() + deletedById aus Session

92.3 · Task – Entscheidung: Hard Delete bleibt

deleteTask() wurde nicht umgebaut

  • TaskStatus hat kein ARCHIVED in der aktuellen Schema-Version
  • CANCELLED ist ein normaler Workflow-Status (sichtbar in der Liste) – kein funktionales Äquivalent zu ARCHIVED
  • Umstellen auf CANCELLED würde archivierte Tasks weiterhin sichtbar zeigen → falsche UX
  • Entscheidung: Hard Delete für Tasks im internen MVP beibehalten
  • Folge-AP empfohlen: TaskStatus + ARCHIVED (Schema-Change erforderlich)

92.4 · UI-Texte angepasst

DeleteCompanyButton

crm/DeleteCompanyButton.tsx

LöschenArchivieren

DeleteContactButton

crm/DeleteContactButton.tsx

LöschenArchivieren

DeleteLeadButton

crm/DeleteLeadButton.tsx

LöschenArchivieren

DeleteProjectButton

projects/DeleteProjectButton.tsx

LöschenArchivieren

DeleteOfferButton

offers/DeleteOfferButton.tsx

LöschenArchivieren

NotesFeed (title/aria)

notes/NotesFeed.tsx

Notiz löschenNotiz archivieren

DeleteTaskButton

tasks/DeleteTaskButton.tsx

LöschenLöschen (unverändert)

92.5 · AP170-Fortschritt

AP170-A

Schema + Migration

✅ Abgeschlossen

AP170-B

Query Filter

✅ Abgeschlossen

AP170-C

Archive Actions

✅ Abgeschlossen

AP170-D

Restore/Archive UI

⏳ Offen

92.6 · Build + Status

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
Schema-Change: — Keiner
Migration: — Keine erzeugt
Deployment: — Keines
Query-Filter: — Nicht verändert (AP170-B aktiv)
Restore-UI: — Nicht gebaut (AP170-D)

AP170-C2 – TaskStatus ARCHIVED

Soft Delete für Tasks: TaskStatus.ARCHIVED als neuer Enum-Wert. Migration task_archived_status erzeugt. deleteTask() archiviert statt hart zu löschen.

Schema-Change: ✅ TaskStatus.ARCHIVED hinzugefügt
Migration: ✅ task_archived_status erstellt
Query-Filter: ✅ ARCHIVED aus allen Task-Listen herausgefiltert
deleteTask(): ✅ Soft Delete via status = ARCHIVED
DeleteTaskButton: ✅ UI-Text: Löschen → Archivieren
pnpm build: ✅ Erfolgreich, 0 Fehler
Deployment: — Keines

AP171 – Internal MVP Readiness Review

Vollständige Readiness-Prüfung nach Soft-Delete-MVP. Build, Schema, Soft Delete, Rollenrechte, Ingest API, Workspace-Isolation, Blocker-Analyse.

Build + Schema

pnpm build: ✅ Erfolgreich, 0 Fehler
TypeScript: ✅ 0 Fehler
Prisma schema: ✅ Valid (prisma validate)
Migrations: ✅ 4 Migrations, DB up to date
Hard Deletes (UI): ✅ Keine – alle Module Soft Delete
Prisma 7 Warning: ⚠️ package.json#prisma deprecated (harmlos)

Soft Delete Coverage

Company / Contact: ✅ status = ARCHIVED (enum bereits vorhanden)
Lead / Project: ✅ status = ARCHIVED (AP170-A Migration)
Offer: ✅ status = ARCHIVED (AP170-A Migration)
Note: ✅ deletedAt + deletedById (AP170-A)
Task: ✅ status = ARCHIVED (AP170-C2 Migration)
Query-Filter: ✅ Alle Listen/Counts/Dashboards gefiltert
UI-Wording: ✅ Alle Delete-Buttons: Archivieren
Restore UI: ⚠️ Nicht gebaut (AP170-D offen)

Security + Rollenrechte

ADMIN/MANAGER Writes: ✅ Alle Server Actions prüfen userRole
MEMBER Zugriff: ✅ Nur projects + tasks (lesend)
Multi-Workspace: ✅ workspaceId in allen Queries
Ingest API Auth: ✅ x-api-key + timing-safe compare
Ingest Rate Limiting: 🔴 FEHLT – kein Rate Limit implementiert
middleware.ts: ⚠️ Nicht vorhanden – nur per-page guards
follow-ups/podcast: ⚠️ Ungeschützt – nur Mock-Daten (harmlos)
VIEWER/GUEST Rollen: ⚠️ Dokumentiert, aber inaktiv

Blocker-Analyse

🔴 BLOCKER 1: Kein Rate Limiting auf Ingest API (/api/ingest/*)
🔴 BLOCKER 2: Kein Production Deployment (ENV + Vercel + Neon Prod)
🔴 BLOCKER 3: Kein Restore UI (AP170-D) – Archivierte Items dauerhaft unsichtbar
⚠️ HOCH 1: Kein Audit Logging – wer hat was wann archiviert?
⚠️ HOCH 2: User Management read-only – Rollenvergabe nur via Script
⚠️ HOCH 3: Kein DSGVO-Purge-Workflow für Contact-Daten
📋 MITTEL 1: Workspace Onboarding nur via lokalem Script
📋 MITTEL 2: canWriteModule() immer false – Write Matrix theoretisch
📋 NIEDRIG 1: follow-ups + podcast-media: noch Mock-Daten
📋 NIEDRIG 2: Prisma 7 deprecation Warning (package.json#prisma)

AP172 – Ingest API Rate Limiting

In-Memory Fixed-Window Rate Limiter auf allen 5 Ingest-API-Endpunkten. Standard: 120 Requests/Minute pro API-Key. HTTP 429 + Retry-After bei Überschreitung.

Rate Limiter: ✅ In-Memory Fixed-Window (src/lib/ingest-rate-limit.ts)
Rate-Limit-Key: ✅ SHA-256 des API-Keys (16 Hex) + IP-Fallback
Limit: ✅ 120 Req/Min (INGEST_RATE_LIMIT_RPM)
HTTP 429: ✅ Retry-After Header, neutrale Fehlermeldung
5 Routen geschützt: ✅ companies, contacts, leads, offers, tasks
Reihenfolge: ✅ Rate Limit vor Body-Parsing und Auth
API-Key-Auth: ✅ Unverändert (validateIngestRequest)
pnpm build: ✅ Erfolgreich, 0 Fehler
ENV-Platzhalter: ✅ INGEST_RATE_LIMIT_RPM, _WINDOW_MS, UPSTASH_* in .env.example
Upstash-Upgrade: ⚠️ Empfohlen für Vercel Multi-Instance (Doku: docs/ingest-api-rate-limit.md)
DB-Migration: — Keine
Deployment: — Keines

AP170-D2 – Notes Restore UI

Archivierte Notizen in Entity-Detailseiten sichtbar gemacht. ArchivedNotesFeed Client-Component mit zweistufiger Restore-Bestätigung. URL-Toggle auf allen 5 Detailseiten. Nur ADMIN/MANAGER.

restoreNote() Guard: ✅ deletedAt: { not: null } Guard ergänzt (notes/actions.ts)
getEntityArchivedNotes(): ✅ Query für alle 5 Entity-Typen (notes/queries.ts)
ArchivedNote Typ: ✅ id + content + authorName + createdAt + deletedAt
ArchivedNotesFeed: ✅ src/features/notes/components/ArchivedNotesFeed.tsx
Restore-Bestätigung: ✅ Zweistufig (Ja/Nein) + router.refresh() nach Restore
Toggle Company: ✅ ?showArchivedNotes=1 auf /crm/companies/[id]
Toggle Contact: ✅ ?showArchivedNotes=1 auf /crm/contacts/[id]
Toggle Lead: ✅ ?showArchivedNotes=1 auf /crm/leads/[id]
Toggle Project: ✅ ?showArchivedNotes=1 auf /projects/[id]
Toggle Offer: ✅ ?showArchivedNotes=1 auf /offers/[id]
Rollenschutz: ✅ canRestore = ADMIN || MANAGER – kein Toggle für MEMBER
Lazy Load: ✅ getEntityArchivedNotes() nur bei showArchivedNotes && canRestore
Optimistic UI: ✅ Notiz nach Restore sofort aus Liste entfernt
NotesFeed Sync: ✅ router.refresh() lädt aktiven NotesFeed nach Restore neu
Farbe: ✅ Amber-Schema (archivierte Sektion) konsistent mit Doku
DB-Migration: — Keine
Deployment: — Keines
pnpm build: ✅ Erfolgreich, 0 Fehler

AP173 – Final Pre-Deployment Readiness Review

Letzter Code- und Infrastruktur-Check vor AP174 Production Deployment. Alle Code-Blocker geschlossen. Entscheidung: GO für AP174.

GO/NO-GO · Blocker-Status

Entscheidung: ✅ GO – AP174 Production Deployment freigegeben
BLOCKER 1 Rate Limiting: ✅ CLOSED – AP172 (In-Memory 120 Req/Min, 5 Endpunkte)
BLOCKER 2 Deployment: ✅ IN PROGRESS – AP174 ist dieser Schritt
BLOCKER 3 Restore UI: ✅ CLOSED – AP170-D + AP170-D2 (7/7 Module + Notes)
pnpm build: ✅ Clean – 0 Fehler, 32 Routen, Next.js 15.5.16
Prisma Schema: ✅ Valid – 4 Migrations applied, DB up to date

Soft Delete + Restore Coverage

Soft Delete: ✅ 7/7 Module – Company/Contact/Lead/Project/Offer/Task/Note
Restore Actions: ✅ 7/7 Actions – inkl. restoreNote + deletedAt-Guard
Restore UI CRM: ✅ ?showArchived=1, ArchivedCrmSection, RestoreButton
Restore UI Tasks: ✅ ?showArchived=1, ArchivedTaskList
Restore UI Projects: ✅ ?showArchived=1, ArchivedProjectList
Restore UI Offers: ✅ ?showArchived=1, ArchivedOfferList
Restore UI Notes: ✅ 5 Detailseiten, ?showArchivedNotes=1, ArchivedNotesFeed
Query-Filter Coverage: ✅ Alle Listen + Detail-Sub-Queries gefiltert

Auth · Sicherheit · Rate Limiting

Auth: ✅ Microsoft Entra ID + Auth.js v5 JWT-Session
ALLOWED_WRITE_ROLES: ✅ ADMIN/MANAGER in allen 7 Action-Dateien konsistent
workspaceId-Isolation: ✅ Alle DB-Queries, nie an Client
Per-Page-Schutz: ✅ getReadPageAccess() – kein middleware.ts
Ungeschützte Seiten: ⚠️ /follow-ups + /podcast-media – nur Mock-Daten
Rate Limiting: ✅ In-Memory 120 Req/Min (⚠️ Upstash für Multi-Instance)

ENV · Deployment · Manuelle Schritte vor AP174

DATABASE_URL: ❌ Neon Prod-Branch anlegen + Pooled URL
DIRECT_URL: ❌ Neon Prod-Branch Direct URL
AUTH_SECRET: ❌ openssl rand -base64 32 (neu generieren)
AUTH_URL: ❌ Vercel Produktiv-Domain setzen
AUTH_MICROSOFT_ENTRA_*: ❌ Client-ID + Secret + Issuer aus Entra-ID
INGEST_API_KEY: ❌ openssl rand -base64 32 (neu generieren)
Entra-ID Redirect URI: ❌ /api/auth/callback/microsoft-entra-id für Prod-Domain
prisma migrate deploy: ❌ 4 Migrations auf Neon Prod-Branch
Workspace + Admin-User: ❌ onboard-client.template.ts gegen Prod-DB
Smoke Test: ❌ Login + CRM + Archivieren + Ingest API
Vollständige Doku: ✅ docs/final-pre-deployment-readiness.md
DB-Migration: — Keine neuen Migrationen in AP173

AP174-A – Production Deployment Runbook

Vollständiges Step-by-Step-Runbook für AP174-B Production Deployment. 9 Phasen: Neon → Secrets → Vercel → Entra-ID → Migrate → Onboard → Deploy → Smoke Test → Post-Deploy.

Runbook-Übersicht · Dokumentationsstatus

Runbook-Dokument: ✅ docs/production-deployment-runbook.md erstellt
Anzahl Phasen: ✅ 9 Phasen vollständig dokumentiert
Smoke-Test-Abdeckung: ✅ 8 Bereiche: Auth/CRM/Tasks/Projects/Offers/Notes/Ingest/Post
Rollback-Plan: ✅ Vercel Instant Rollback + Neon Point-in-Time-Recovery
Secrets im Repo: ✅ Nur Platzhalter – keine echten Werte dokumentiert
DB-Migration in AP174-A: ✅ Keine – reine Dokumentation
Deployment in AP174-A: ✅ Keine – nur Vorbereitung für AP174-B
pnpm build: ✅ Clean – 0 Fehler

9 Deployment-Phasen (AP174-B: manuell auszuführen)

Phase 1 Neon: ❌ Prod-Branch + Pooled URL + Direct URL anlegen
Phase 2 Secrets: ❌ AUTH_SECRET + INGEST_API_KEY generieren (openssl)
Phase 3 Vercel ENVs: ❌ 6 ENV-Variablen in Vercel Production setzen
Phase 4 Entra-ID: ❌ Redirect URI für Prod-Domain registrieren
Phase 5 Migrate: ❌ prisma migrate deploy via DIRECT_URL auf Neon Prod
Phase 6 Onboarding: ❌ onboard-client.template.ts gegen Prod-DB
Phase 7 Deploy: ❌ git push origin main → Vercel Production Build
Phase 8 Smoke Test: ❌ Auth + CRM + Tasks + Projects + Offers + Notes + Ingest
Phase 9 Post-Deploy: ❌ Monitoring + Rollback-Bereitschaft + BLOCKER 2 schließen

Abbruchkriterien · Rollback · Sicherheitsregeln

Abbruch: Build-Fehler: ✅ Vercel stoppt automatisch – kein Traffic
Abbruch: Login schlägt fehl: ✅ Vercel Rollback → vorherige Version
Abbruch: DB-Fehler: ✅ Neon PTR auf Snapshot vor Migration
Rollback Vercel: ✅ Deployments → Promote to Production (instant)
Rollback Neon: ✅ Branches → Restore from backup (Point-in-Time)
BLOCKER 2 Status: ⚠️ IN PROGRESS – wird durch AP174-B geschlossen

AP174-B1 – Fix Vercel Prisma Client Generation

Vercel Production Build schlug fehl: PrismaClient nicht exportiert. Ursache: fehlende prisma generate vor next build + scripts/ im TypeScript-Typecheck.

Ursache · Fix · Verifikation

Vercel-Fehler: ❌→✅ Module @prisma/client has no exported member PrismaClient
Ignored build scripts: ⚠️ Vercel ignoriert @prisma/client@6.19.3 postinstall
Ursache 1: ✅ FIXED – build war nur next build, kein prisma generate
Ursache 2: ✅ FIXED – scripts/ in tsconfig include (Node.js-Skripte)
Fix package.json: ✅ build: prisma generate && next build
Fix tsconfig.json: ✅ scripts/ in exclude hinzugefügt
Keine DB-Migration: ✅ Nur Build-Config – kein Schema, kein migrate
pnpm build lokal: ✅ Clean – 0 Fehler, 32 Routen, Next.js 15.5.16

Geänderte Dateien · AP174-B1

package.json: ✅ build: prisma generate && next build
tsconfig.json: ✅ exclude: scripts hinzugefügt
.env: ✅ NICHT geändert
prisma/schema.prisma: ✅ NICHT geändert
prisma/migrations/: ✅ NICHT geändert – keine neue Migration
Feature-Code: ✅ NICHT geändert – reine Build-Config

AP174-B2 – Fix Auth.js v5 Entra Sign-In

Runtime-Fehler beim Klick auf Entra Login: UnknownAction: Unsupported action. Ursache: href="/api/auth/signin"-Link + Built-in-Page-Flow in Auth.js v5 beta.31.

Ursache · Fix · Provider-ID

Runtime-Fehler: ❌→✅ UnknownAction: Unsupported action at Object.signin
Ursache: ✅ FIXED – href=/api/auth/signin Built-in-Page-Flow in beta.31
Provider-ID: ✅ microsoft-entra-id (Auth.js v5 MicrosoftEntraID-Standard)
Fix SignInButton: ✅ NEU – Server Component + Server Action signIn(provider)
Fix login/page.tsx: ✅ Platzhalter ersetzt durch SignInButton-Komponente
Fix PageAccessBlock: ✅ href=/api/auth/signin → <SignInButton label=Jetzt einloggen>
Auth.js v5 Variante: ✅ signIn("microsoft-entra-id") aus @/auth via Server Action
pnpm build: ✅ Clean – 0 Fehler, 32 Routen, Next.js 15.5.16

Geänderte Dateien · AP174-B2

SignInButton.tsx: ✅ NEU – Server Component mit Server Action
login/page.tsx: ✅ Platzhalter → SignInButton
PageAccessBlock.tsx: ✅ href=/api/auth/signin → SignInButton
.env: ✅ NICHT geändert
src/auth.ts: ✅ NICHT geändert – Provider-Konfig korrekt
[...nextauth]/route.ts: ✅ NICHT geändert – GET/POST-Handler korrekt

AP174-B3 – Harden Auth.js Runtime Config

Auth.js v5 Runtime-Konfiguration für Vercel gehärtet. secret-Fallback + trustHost + Vercel ENV-Regeln dokumentiert.

Änderungen · src/auth.ts

secret-Fallback: ✅ AUTH_SECRET ?? NEXTAUTH_SECRET – beide ENV-Namen abgesichert
trustHost: true: ✅ Vercel-Deployment-URLs als vertrauenswürdig eingestuft
Provider-Konfig: ✅ MicrosoftEntraID unverändert – clientId/Secret/Issuer korrekt
route.ts GET/POST: ✅ Unverändert – handlers-Export korrekt
AUTH_URL im Code: ✅ Nicht direkt verwendet – trustHost übernimmt Host-Validierung
pnpm build: ✅ Clean – 0 Fehler, 32 Routen, Next.js 15.5.16

Vercel ENV – Pflicht-Keys (keine echten Werte)

AUTH_SECRET: ❌ Pflicht – openssl rand -base64 32
NEXTAUTH_SECRET: ❌ Pflicht – identisch mit AUTH_SECRET (Fallback)
AUTH_URL: ❌ Pflicht – https://domain.vercel.app ohne Quotes
NEXTAUTH_URL: ❌ Pflicht – identisch mit AUTH_URL (Fallback)
AUTH_TRUST_HOST: ⚠️ Empfohlen – true (für Preview-Deployments)
AUTH_MICROSOFT_ENTRA_ID_ID: ❌ Pflicht – Client-ID aus Entra-App-Registrierung
AUTH_MICROSOFT_ENTRA_ID_SECRET: ❌ Pflicht – Client-Secret aus Entra-App-Registrierung
AUTH_MICROSOFT_ENTRA_ID_ISSUER: ❌ Pflicht – Tenant-Issuer-URL aus Entra

AP174-B Runtime-Fehler Chronologie

MissingSecret: ✅ BEHOBEN – AUTH_SECRET in Vercel Preview gesetzt
UnknownAction: ✅ BEHOBEN – AP174-B2 SignInButton Server Action
Invalid URL: ✅ BEHOBEN – AUTH_URL ohne Quotes in Vercel setzen
trustHost fehlt: ✅ BEHOBEN – AP174-B3 trustHost: true in auth.ts

AP175 – Internal Deployment Success

Erstes internes Deployment erfolgreich. Smoke Test 7/7 bestanden. Microsoft Entra Login, Workspace-Onboarding, alle Module getestet.

Smoke Test · 2026-05-27 · develop → Vercel Preview

Auth – Entra Login: ✅ OAuth-Flow vollständig – Session korrekt
Workspace sichtbar: ✅ Workspace-Name im UI korrekt angezeigt
prisma migrate deploy: ✅ Erfolgreich – keine pending Migrations
CRM: ✅ Create/Edit/Archive/Restore
Tasks: ✅ Create/Edit/Archive/Restore
Projects: ✅ Create/Edit/Archive/Restore
Offers: ✅ Create/Edit/Archive/Restore
Notes: ✅ Create/Archive/Restore

Bekannte Einschränkungen · Empfohlene nächste APs

AP176 – Production Domain: ⚠️ develop läuft als Preview – keine Custom Domain
AP177 – User Management UI: ⚠️ User-Anlage nur via Script, kein UI
AP178 – Audit Logging: ⚠️ Noch nicht implementiert
AP179 – DSGVO Purge: ⚠️ Data Lifecycle noch nicht umgesetzt
AP180 – Upstash Rate Limit: ⚠️ In-Memory nur für Single-Instance
AP181 – Usage Checklist: ⚠️ Erster interner Einsatz noch nicht strukturiert

AP176 – Production Domain & Branch Strategy

Zielbild für stabilen Betrieb: develop = Preview, main = Production. Custom Domain os.krause-digital-solutions.de. Runbook in docs/production-domain-branch-strategy.md.

Entscheidung · Zielbild

Empfehlung: ✅ Variante B – main = Production, develop = Preview
Production Domain: ⚠️ os.krause-digital-solutions.de – noch nicht eingerichtet
Vercel Production Branch: ⚠️ main – noch nicht als Production konfiguriert
Entra Redirect URI: ⚠️ Feste Domain noch nicht in Entra eingetragen
AUTH_URL / NEXTAUTH_URL: ⚠️ Muss auf feste Domain aktualisiert werden
Neon Production DB: ✅ Bereits korrekt konfiguriert – keine Änderung nötig
Runbook: ✅ docs/production-domain-branch-strategy.md erstellt
Ausführung: ⚠️ AP176-B – manuell durch André

AP176-B Runbook-Phasen (manuell)

Phase 1 main Branch: ❌ develop → main mergen + pushen
Phase 2 Vercel Prod Branch: ❌ Production Branch auf main setzen
Phase 3 Custom Domain: ❌ os.krause-digital-solutions.de in Vercel
Phase 4 DNS IONOS: ❌ CNAME/A-Record bei Domainanbieter
Phase 5 Vercel ENVs: ❌ AUTH_URL/NEXTAUTH_URL auf feste Domain
Phase 6 Entra Redirect URI: ❌ /api/auth/callback/microsoft-entra-id auf fester Domain
Phase 7 Deploy: ❌ git push origin main → Vercel Production Build
Phase 8 Smoke Test: ❌ Login + CRM + Tasks auf os.krause-digital-solutions.de

AP176-B0 – Production Domain Execution Checklist

Schritt-für-Schritt-Runbook für André: main Branch vorbereiten, Vercel Production Branch setzen, Custom Domain os.krause-digital-solutions.de, DNS, ENVs, Entra Redirect URI, Deploy, Smoke Test.

Voraussetzungen · Zielzustand

main Branch lokal: ✅ Existiert lokal und remote
develop aktuell: ✅ AP176 gemerged – fe38b73
main hinter develop: ⚠️ main liegt weit hinter develop – Merge nötig
Vercel Production Branch: ⚠️ Noch auf develop oder nicht konfiguriert
Custom Domain: ⚠️ os.krause-digital-solutions.de – noch nicht in Vercel
DNS IONOS: ⚠️ CNAME/A-Record noch nicht gesetzt
Entra Redirect URI: ⚠️ Feste Domain noch nicht in Entra
Runbook: ✅ docs/production-domain-execution-checklist.md

AP176-B Ausführungsschritte (manuell durch André)

Schritt 1 main Branch: ❌ develop → main mergen + pushen
Schritt 2 GitHub Push: ❌ git push origin main
Schritt 3 Vercel Prod: ❌ Production Branch auf main setzen
Schritt 4 Custom Domain: ❌ os.krause-digital-solutions.de in Vercel
Schritt 5 DNS IONOS: ❌ CNAME/A-Record bei Domainanbieter
Schritt 6 DNS Verify: ❌ Domain Verification in Vercel abwarten
Schritt 7 Vercel ENVs: ❌ AUTH_URL/NEXTAUTH_URL + alle Production ENVs
Schritt 8 Entra URI: ❌ /api/auth/callback auf fester Domain ergänzen
Schritt 9 Deploy: ❌ git push origin main → Production Build
Schritt 10 Smoke Test: ❌ Login + alle Module auf os.krause-digital-solutions.de
Schritt 11 Cleanup: ❌ Preview-URL ablösen, Bookmarks updaten

AP176-C – Production Domain Cutover erfolgreich

Production Domain aktiv: os.krause-digital-solutions.de – main = Production, develop = Preview. Entra Login, Workspace, alle Module getestet. 8/8 Smoke Test bestanden.

Durchgeführte Schritte · Ergebnis

develop → main Merge: ✅ main auf develop-Stand gebracht
main nach GitHub gepusht: ✅ origin/main aktuell
Vercel Production Branch: ✅ main als Production Branch gesetzt
Domain in Vercel: ✅ os.krause-digital-solutions.de verified
DNS / IONOS: ✅ CNAME/A-Record konfiguriert
Vercel Production ENVs: ✅ AUTH_URL, NEXTAUTH_URL, alle Keys gesetzt
Entra Redirect URI: ✅ Feste Domain eingetragen
Production Deploy: ✅ Vercel Build grün auf main

Smoke Test · os.krause-digital-solutions.de · 8/8 ✅

Auth – Entra Login: ✅ OAuth-Flow · Redirect zu /dashboard
Workspace: ✅ Name sichtbar im Header
Dashboard: ✅ Erreichbar, Daten sichtbar
CRM: ✅ Create/Edit/Archive/Restore
Tasks: ✅ Create/Edit/Archive/Restore
Projects: ✅ Create/Edit/Archive/Restore
Offers: ✅ Create/Edit/Archive/Restore
Notes: ✅ Create/Archive/Restore

AP177-A – User Management UI Foundation

Erste Admin-UI für Benutzerverwaltung: /settings/users – ADMIN only, zwei Schutzebenen. User-Liste mit Role/Status-Badges. Maskierte E-Mails. Kein CRUD in AP177-A.

Umgesetzt · AP177-A

/settings/users: ✅ Neue Route – ADMIN-only, force-dynamic
getUsersForWorkspace(): ✅ Server Query – workspaceId-gefiltert, E-Mail maskiert
UserList-Komponente: ✅ Role/Status-Badges, leer-Zustand, responsive
Settings Schnellnavigation: ✅ ADMIN-Quicklinks Workspace + Benutzer
Zugriffsschutz Schicht 1: ✅ getReadPageAccess(settings) – nur ADMIN im READ_MATRIX
Zugriffsschutz Schicht 2: ✅ normalizedRole === ADMIN explizit geprüft
Workspace-Isolation: ✅ workspaceId nur serverseitig – nie an Client
DB-Migration: ✅ Keine nötig – User-Schema bereits vollständig

Geplante Folge-APs

AP177-B: ⏳ Create User – Name, E-Mail, Rolle, Server Action
AP177-C: ⏳ Edit Role / Status – ADMIN mit Selbst-Lockout-Schutz
AP177-D: ⏳ Deactivate User – Status INACTIVE/SUSPENDED, kein DB-Delete
AP177-E: ⏳ Invite / Onboarding Flow – Einladungslink oder Entra Invite

AP177-B – Module Permission Model Design

Modulbasierte Rechte pro Benutzer: UserModulePermission-Tabelle, NONE/READ/WRITE/ADMIN, 12 Module, ADMIN-Bypass, Enforcement-Plan für AP177-D. Kein Code – reine Planungs-AP.

Kern-Entscheidungen · Datenmodell

Grundrollen: ✅ ADMIN/MANAGER/MEMBER bleiben unverändert
ADMIN: ✅ Superuser – bypassed alle Modulrechte
UserModulePermission: ✅ Neue Tabelle: workspaceId/userId/moduleKey/accessLevel
AccessLevel: ✅ NONE / READ / WRITE / ADMIN
ModuleKey: ✅ 12 Module als Prisma-Enum
Fallback: ✅ Kein Eintrag → READ_MATRIX der Rolle (rückwärtskompatibel)
Unique Constraint: ✅ (workspaceId, userId, moduleKey) eindeutig
Cascade: ✅ User/Workspace gelöscht → Permissions mitgelöscht

Folge-APs · Enforcement

AP177-C: ✅ UserModulePermission Schema + Migration (neue Tabelle + Enums)
AP177-D: ⏳ Enforce: canReadModule() + Server Actions + Sidebar-Filter
AP177-E: ⏳ Permission Management UI – Matrix-View je User
AP177-F: ⏳ Create/Deactivate Users + Invite
Routen-Lücken: ⚠️ /follow-ups + /podcast-media ohne ModuleKey-Schutz
Offene Entscheidungen: ⚠️ 7 Punkte vor AP177-C klären (siehe Dokument)

AP177-C – UserModulePermission Schema

Prisma Schema + Migration: ModuleKey (12 Module), ModuleAccessLevel (NONE/READ/WRITE/ADMIN), Tabelle user_module_permissions mit Cascade-FK auf User + Workspace. Kein Feature-Code.

Schema · Migration

ModuleKey Enum: ✅ 12 Werte: DASHBOARD–SETTINGS (Prisma Enum)
ModuleAccessLevel Enum: ✅ NONE / READ / WRITE / ADMIN
Tabelle: ✅ user_module_permissions – id/workspaceId/userId/moduleKey/accessLevel
Unique Constraint: ✅ (workspaceId, userId, moduleKey)
Indexes: ✅ workspaceId · userId · (workspaceId, moduleKey)
onDelete User: ✅ Cascade – Permissions mit User mitgelöscht
onDelete Workspace: ✅ Cascade – Permissions mit Workspace mitgelöscht
Migration: ✅ 20260527131343_user_module_permissions – auf Neon angewendet

Mapping-Datei · Nächste APs

module-permissions.ts: ✅ src/server/permissions/ – Labels, Prio, hasAtLeast() – kein Enforcement
Rückrelationen: ✅ User.modulePermissions · Workspace.userModulePermissions
Bestehende Logik: ✅ READ_MATRIX + canReadModule() unverändert (AP123)
AP177-D: ✅ Enforce: getEffectiveReadAccess() + page-access + Navigation-Filter
AP177-E: ⏳ Permission Management UI – Matrix-View je User
AP177-F: ⏳ Create/Deactivate Users + Invite

AP177-D – Enforce Module Permissions

Effektive Rechte-Auflösung: ADMIN-Bypass → UserModulePermission DB-Lookup → READ_MATRIX-Fallback. Rückwärtskompatibel. Kein Schema-Change, keine Migration, keine Permission Rows nötig.

Implementierung · Lookup-Kette

permission-queries.ts: ✅ getUserModulePermission() + getAllUserModulePermissions()
effective-permissions.ts: ✅ getEffectiveReadAccess() · getVisibleNavigationItems()
page-access.ts: ✅ getReadPageAccess() – ADMIN-Bypass → DB → READ_MATRIX
dbUserId in PermCtx: ✅ permissions.ts: dbUserId im PermissionContext (AP163)
Sidebar Filterung: ✅ Sidebar nimmt items-Prop · AppShell berechnet sichtbare Items
PAGE_KEY_TO_MODULE_KEY: ✅ camelCase-Keys → Prisma-Enum inkl. Detail-Seiten-Mapping
Fail-safe: ✅ DB-Fehler → READ_MATRIX-Fallback (kein Ausperren)
Rückwärtskompatibilität: ✅ User ohne Permission-Rows = identisches Verhalten wie bisher

Schutz-Status · Folge-APs

Geschützte Module: ✅ Alle Module via getReadPageAccess() – automatisch inkl. DB-Lookup
FOLLOWUPS / PODCAST: ✅ Routen-Lücken geschlossen (AP177-D2) – getModulePageAccess()
Write-Enforcement: ✅ Server Actions per requireModuleWrite() gesichert (AP177-D2)
AP177-D2: ✅ Umgesetzt – Write-Enforcement + Route-Lücken geschlossen
AP177-E: ✅ Permission Management UI – PermissionMatrix + Server Actions (Abschnitt 113)
AP177-F: ✅ User Create/Deactivate/Reaktivieren + Rolle ändern (Abschnitt 114)
AP177-G: ✅ Smoke Test + Hardening: Status-Guard, JS-Entity-Fixes (Abschnitt 115)
AP177-F: ⏳ Create/Deactivate Users + Invite

AP177-D2 – Write Enforcement + Route Gaps

Write-Enforcement in Server Actions + Route-Lücken geschlossen. requireModuleWrite() in allen Schreib-Actions (TASKS, CRM, PROJECTS, OFFERS, NOTES). getModulePageAccess() für FOLLOWUPS + PODCAST_MEDIA (kein READ_MATRIX-Fallback – default offen). Rückwärtskompatibel: User ohne Permission-Rows = identisches Verhalten wie bisher.

Implementierung · Write-Enforcement

requireModuleWrite(): ✅ effective-permissions.ts – ADMIN-Bypass → DB → Fallback
getModulePageAccess(): ✅ page-access.ts – für Prisma-Keys ohne READ_MATRIX-Eintrag
/follow-ups: ✅ Route-Lücke geschlossen – getModulePageAccess(FOLLOWUPS)
/podcast-media: ✅ Route-Lücke geschlossen – getModulePageAccess(PODCAST_MEDIA)
tasks/actions.ts: ✅ createTask · updateTask · deleteTask · restoreTask
crm/actions.ts: ✅ createContact · updateContact · archiveContact · restoreContact
crm/company-actions.ts: ✅ createCompany · updateCompany · archiveCompany · restoreCompany
crm/lead-actions.ts: ✅ createLead · updateLead · archiveLead · restoreLead
projects/actions.ts: ✅ createProject · updateProject · archiveProject · restoreProject
offers/actions.ts: ✅ createOffer · updateOffer · updateOfferStatus · archiveOffer · restoreOffer
notes/actions.ts: ✅ createNote (READ threshold) · deleteNote + restoreNote (WRITE threshold)
Fail-safe: ✅ DB-Fehler → null (kein Ausperren) · Rollencheck bleibt primärer Guard
Rückwärtskompatibilität: ✅ Kein Permission-Row = identisches Verhalten wie bisher

Folge-APs

AP177-E: ✅ Permission Management UI – umgesetzt (Abschnitt 113)
AP177-F: ✅ User Create/Deactivate – umgesetzt (Abschnitt 114)
AP177-G: ✅ Smoke Test + Hardening – umgesetzt (Abschnitt 115)

AP177-E – Permission Management UI

Admin-UI zur Verwaltung von Modulrechten pro Benutzer. ADMIN-only Seite /settings/users/[userId]/permissions mit 12-Modul-Matrix. Server Actions: setUserModulePermission (Upsert) + resetUserModulePermission (Row löschen). Self-Lockout-Schutz: SETTINGS darf für eigenen Account nicht auf NONE gesetzt werden.

Neue Dateien

permissions/page.tsx: ✅ /settings/users/[userId]/permissions – ADMIN-only, 3-Layer-Guard
PermissionMatrix.tsx: ✅ Client Component – 12 Module, Select + Save, Dirty-State, Feedback
permission-actions.ts: ✅ Server Actions – setUserModulePermission + resetUserModulePermission

Geänderte Dateien

UserList.tsx: ✅ „Rechte"-Link je Zeile → /settings/users/[id]/permissions
user-queries.ts: ✅ getUserByIdForWorkspace() – workspace-gefiltert, maskierte E-Mail
page-access.ts: ✅ ReadPageAccess + dbUserId (für Self-Check serverseitig)

Sicherheit

ADMIN-Only Guard: ✅ Layer 1: getReadPageAccess(settings) · Layer 2: normalizedRole=ADMIN
Workspace-Isolation: ✅ assertTargetInWorkspace() in beiden Server Actions
Self-Lockout-Schutz: ✅ Server Action + Client-UI: SETTINGS→NONE für eigenen Account geblockt
Enum-Validierung: ✅ VALID_MODULE_KEYS + VALID_ACCESS_LEVELS gegen Prisma-Enums geprüft
Kein Client-Secret: ✅ workspaceId + dbUserId bleiben serverseitig
Rückwärtskompatibilität: ✅ Kein Permission-Row = identisches Verhalten wie bisher

Folge-APs

AP177-F: ✅ User Create/Deactivate – umgesetzt (Abschnitt 114)
AP177-G: ✅ Smoke Test + Hardening – umgesetzt (Abschnitt 115)

AP177-F – User Create / Deactivate

ADMIN-only Benutzeranlage, Deaktivierung, Reaktivierung und Rollenverwaltung. Keine Migration, kein Passwort-Handling, kein Invite-Flow, keine Entra-API. Neue User erhalten Status ACTIVE ohne automatische Permission Rows. Login über Microsoft Entra mit der angegebenen E-Mail-Adresse.

Server Actions (user-actions.ts)

createWorkspaceUser(): ✅ User anlegen – Name, E-Mail (normalisiert), Rolle, Titel, Abteilung
deactivateWorkspaceUser(): ✅ Status → INACTIVE – eigener Account + letzter ADMIN geschützt
reactivateWorkspaceUser(): ✅ Status → ACTIVE
updateWorkspaceUserRole(): ✅ Rolle ändern – eigene Rolle + letzter ADMIN geschützt

Neue UI-Komponenten

CreateUserDialog.tsx: ✅ Modal-Dialog – Name, E-Mail, Rolle, Titel, Abteilung + Entra-Hinweis
UserStatusButton.tsx: ✅ Deaktivieren / Reaktivieren je User-Zeile (mit Bestätigung)
UserRoleControl.tsx: ✅ Rolle verwalten auf Permissions-Detailseite

Sicherheit

ADMIN-Only: ✅ Alle Server Actions prüfen ctx.userRole === ADMIN
Workspace-Isolation: ✅ getTargetUser() filtert userId + workspaceId – kein Cross-Workspace
Eigener Account: ✅ deactivate/updateRole: targetUserId === ctx.dbUserId → geblockt
Letzter ADMIN: ✅ countActiveAdmins() ≤ 1 → deactivate/updateRole geblockt
Duplicate E-Mail: ✅ Pre-Check + P2002-Catch – klare Fehlermeldung
E-Mail-Normalisierung: ✅ trim + toLowerCase vor DB-Write
Kein Migration: ✅ @@unique([workspaceId, email]) + UserStatus bereits vorhanden
Keine Permission Rows: ✅ Neue User starten ohne Permission Rows – READ_MATRIX-Fallback

Folge-AP

AP177-G: ✅ Smoke Test + Hardening – umgesetzt (Abschnitt 115)

AP177-G – Smoke Test + Hardening

Produktion Smoke-Test-Checkliste (Blöcke A–I) + drei Hardening-Fixes. Keine neue Feature, keine Migration, kein Prisma-Schema-Änderung. Status-Guard in getCurrentWorkspaceContext(): INACTIVE/SUSPENDED User werden auf App-Ebene geblockt. JS-Entity-Bug in zwei Client-Komponenten behoben.

Hardening-Fixes

workspace.ts Status-Guard: ✅ INACTIVE/SUSPENDED → no_db_user – App-Layer-Sperre ohne Entra-API
UserStatusButton.tsx: ✅ JS-Entity-Bug: &amp;hellip; → … (Unicode) für isPending-Label
CreateUserDialog.tsx: ✅ JS-Entity-Bug: &amp;hellip; → … (Unicode) für isPending-Label

Smoke Test Checkliste (docs/user-management-production-smoke-test.md)

A – Admin-Zugriff: 7 Checks: Login, /settings/users, ADMIN-Badge, Permissions-Seite
B – Testuser anlegen: 11 Checks: Modal, Anlage, Duplikat-Test, Fehler-Tests
C – Permissions setzen: 8 Checks: CRM=WRITE, OFFERS=NONE, PROJECTS=READ, TASKS=WRITE, SETTINGS=NONE
D – Permission reset: 3 Checks: TASKS → Vererbt → zurücksetzen
E – Login als Testuser: 6 Checks: InPrivate, Entra-Login, Modul-Zugriff nach Permission
F – Write-Test: 4 Checks: CRM anlegen ✅, Offers gesperrt ✅, Projects read-only ✅, Tasks anlegen ✅
G – Statusverwaltung: 5 Checks: Deaktivieren → Inaktiv, Login geblockt, Reaktivieren → Aktiv
H – Rollenverwaltung: 5 Checks: Member → Manager → Member
I – Self-lockout: 7 Checks: Kein Deaktivieren-Button, Rolle gesperrt, letzter ADMIN-Schutz

AP177-Serie: Operativ einsatzbereit

AP177-A: ✅ ADMIN-only /settings/users, UserList, maskierte E-Mails
AP177-B: ✅ UserModulePermission-Modell, 12 Module, NONE/READ/WRITE/ADMIN
AP177-C: ✅ ModuleKey/ModuleAccessLevel Enums, Tabelle, Migration, Mapping
AP177-D: ✅ Lookup-Kette, page-access, Navigation-Filter, rückwärtskompatibel
AP177-D2: ✅ requireModuleWrite, getModulePageAccess, Action-Guards
AP177-E: ✅ /settings/users/[id]/permissions, PermissionMatrix, Server Actions
AP177-F: ✅ createWorkspaceUser, deactivate/reactivate, updateRole
AP177-G: ✅ Status-Guard, JS-Fixes, Smoke-Test-Dok – Serie abgeschlossen