Commit Graph

32 Commits

Author SHA1 Message Date
schalli e0e163ec63 docs(quick-260911-cwh): Bereich calendar Etappe 2 abgeschlossen — restliche Dokumentstellen nachgezogen (Aufgabe 3)
- docs/mandantentrennung-zugriffsklassifikation.md: Uebersichtszeile
  calendar auf 0/12 gezogen (war 12/0, kein ungebundener Rest — erster
  Bereich in Folge ohne begruendeten Rest), Summenzeile auf 83/159
  fortgeschrieben, Klassen-Verteilung unveraendert mit ausdruecklichem
  Stand-260911-cwh-Vermerk, Hintergrunddienst-Abschnitt um den
  Sonderfall refreshCacheInBackground (abgekoppelte Fortsetzung, kein
  sechster Fall) ergaenzt, Abschnitt "Was diese Etappe NICHT entscheidet"
  um calendar ergaenzt
- .planning/WINDOWS.md: neuer offener Eintrag (#26) zur lautlosen
  Auspraegung der umgekehrten Fehlerrichtung im Bereich calendar, ueber
  gsd-tools windows append angelegt
- Beide Falsifizierungsnachweise durchgefuehrt (falscher Stand macht
  rls-access-inventory.spec.ts rot; falsche Uebersichtszahl macht das
  herleitende Gate rot), zurueckgenommen
- Etappe 2, neunter Bereich (calendar) abgeschlossen: alle zwoelf
  Zugriffe gebunden, Baseline gehalten (883 Tests, 57 Dateien, 101
  Werkzeugpruefungen, Typpruefung sauber)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-11 09:59:17 +02:00
schalli 67b50240d6 feat(quick-260910-krx): Suchmaschinen gebunden, Katalog begruendet offen, Klassifikation nachgezogen
Aufgabe 3 — TDD zuerst (7 weitere Faelle in dashboard.service.spec.ts, 20
vorher/27 nach dieser Aufgabe), dann die Umstellung:

- getSearchProviders/addSearchProvider/removeSearchProvider laufen ueber
  forTenant(); removeSearchProvider fuehrt Besitzpruefung UND Schreibzugriff
  ueber DENSELBEN gebundenen Klienten. Die drei Vorgabe-Suchmaschinen aus
  der Konstante bleiben unveraendert vorangestellt.
- Der eine Katalogzugriff (this.prisma.module in getWidgets) bleibt
  begruendet ungebunden: Messung und Bedingung getrennt (Tabelle traegt
  heute keinen Zeilenschutz, wirkungslos statt katastrophal — katastrophal
  erst, wenn Etappe 3 eine Regel gibt), unter Berufung auf die bestehende
  Werkzeugpruefung module-tabelle-traegt-keinen-zeilenschutz statt einer
  neuen Behauptung. Ein Wachhund-Testfall haelt den Katalogzugriff aus dem
  Bindungsprotokoll heraus (und beweist zuerst, dass der Katalogpfad
  tatsaechlich durchlaufen wird, nicht nur theoretisch geprueft ist).
- docs/mandantentrennung-zugriffsklassifikation.md an allen fuenf
  handgepflegten Stellen nachgezogen: vier Bestandsaufnahme-Zeilen (inkl.
  eigenstaendiger Nachpruefung der widerlegten SearchProvider-Praemisse),
  Uebersichtszeile (13/0 -> 1/12), Summenzeile (95/147), Klassen-Verteilung
  (unveraendert 63 Paare, ausdruecklich vermerkt), Hintergrunddienst-
  Abschnitt (dashboard hat keinen sechsten Fall, mit Messanweisung), "Was
  diese Etappe NICHT entscheidet" (dienst-interner forTenant()-Weg wie alle
  sieben Bereiche vor ihm).
- .planning/WINDOWS.md traegt Eintrag #25 (offen, Tabelle + JSON): die
  beweisvernichtende Schleife (leeres Dashboard -> Neuaufbau ->
  automatisches Zurueckschreiben -> ueberschriebene Anordnung, Widget-
  Dubletten) samt der Vorabpruefung fuer Etappe 4 und dem Verweis auf #22
  fuer die verwandte Eindeutigkeitsfrage.

Zwei weitere Falsifizierungsnachweise durchgefuehrt: (1) den Katalogzugriff
probeweise gebunden (tenantPrisma.module.findMany) — acht Tests werden rot
mit "TypeError: Cannot read properties of undefined (reading 'findMany')",
weil `module` bewusst nicht in der Testdouble-Bindungsliste steht; Rueckbau
zurueckgenommen, 27/27 wieder gruen. (2) den Stand von dashboardLayout in
der Klassifikationsdatei probeweise auf "ungebunden" gesetzt —
rls-access-inventory.spec.ts wird rot mit "Abweichender Stand (Dokument vs.
Quelltext): ... dokumentiert=ungebunden, gemessen=gebunden"; Ruecknahme,
Testlauf wieder gruen (10/10).

Baseline gehalten: 858 Tests / 56 Dateien gruen, Typpruefung sauber,
Wegwerf-Werkzeug 87/87. Schalter bleibt aus.
2026-09-11 09:03:23 +02:00
schalli 03fb3bf9c7 docs(quick-260910-jab): Aktenstand kohaerent machen — Ledger, Klassifikation, Kritikschrift, Betriebsanleitung
- WINDOWS.md: #19 auf fixed gesetzt (Tabelle + JSON-Block), mit Beleg
  (Migrationsname + benannte Pruefungen) und ausdruecklicher Feststellung,
  dass die SearchProvider-Haelfte als widerlegte Praemisse schliesst, nicht
  als geloestes Problem. #18/#20/#21/#22/#23 bleiben unveraendert offen. Ein
  neuer Eintrag #24 haelt den fehlenden Verwaltungsweg fuer plattformweite
  Zeilen unter der Anwendungsrolle offen (verschwindet nicht mit #19). Die
  vier Kopfzahlen sind aus dem JSON-Block abgeleitet (6 offen, 17 behoben, 1
  zurueckgestellt, 24 gesamt).
- Klassifikation: #19-Block von offener Frage zu beantwortet, die
  Uebersichtszeile tenders (35/27) und Summenzeile (107/135) aus dem
  Quelltext neu abgeleitet, vier Bestandsaufnahme-Zeilen nachgezogen
  (searchProvider, groups.service.ts/user, module-grants.service.ts/
  moduleGrant, tender-rss-feed.service.ts/tenderRssFeedSource), der Punkt in
  "Was diese Etappe NICHT entscheidet" aufgeloest.
- Kritikschrift: neuer Abschnitt "Regelschluss T-JTS-02, T-JTS-03 und
  WINDOWS #19" mit tatsaechlich beobachteter Ausgabe, Regelliste der
  lebenden Datenbank, Signaltabelle mit beiden Fehlerrichtungen je Regel,
  der neuen Stelle aus Befund F, der selbst ausgefuehrten Messung zur
  fehlenden Benutzerdimension (keine zweite Sitzungsvariable gefunden) und
  dem, was dieser Durchlauf nicht loest/nicht anfasst. Fuenf ueberholte
  Bestandsstellen mit Nachtraegen versehen (g4, t4, die Signaltabellenzeile
  zu den RSS-Pfaden, drei aufgezeichnete Werkzeugausgaben, m4), die alten
  Messprotokolle bleiben woertlich stehen.
- Betriebsanleitung: die eine Stelle, die #19 als offen fuehrte, nennt jetzt
  den Aufloesungsstand und WINDOWS #24.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 14:44:34 +02:00
schalli 9c0eefee90 feat(quick-260910-exd): ModuleRegistryService binden, Klassifikation abschliessen
- module-registry.service.ts: findActiveForTenant, activateForTenant,
  isModuleActive binden je einen Aktivierungszugriff, deactivateForTenant
  bindet beide (Lesen+Schreiben) ueber EINEN Klienten unter tenantPrisma;
  alle sechs Katalogzugriffe (findAll/findBySlug/beide
  Existenzpruefungen/isModuleActive-Katalogsuche/seedModule) bleiben
  bewusst ungebunden, mit Kommentar der Messung von Bedingung trennt
- isModuleActive-Kopfkommentar richtiggestellt: der Waechter ruft sie
  nicht auf (0 Aufrufer, TEIL 3 von Aufgabe 1) — Waechter nimmt findBySlug
  + ModuleAccessService.getAccessibleModuleIds
- module-registry.service.spec.ts: NEU, Zwei-Klienten-Nachweis, deckt die
  bislang ungetestete Datei mit elf der siebzehn Zugriffe des Bereichs ab,
  inkl. der lauten (deactivate ohne Aktivierung) und stillen (isModuleActive
  ohne Aktivierung) Richtung und dem Katalog-Wachhund
- tender-scheduler.service.spec.ts: forTenant() auf Identitaet gemockt
  (dieselbe Konvention wie ldap.service.spec.ts) — cross-area Bruch durch
  die Umstellung von activateForTenant behoben (Rule 1/3)
- docs/mandantentrennung-zugriffsklassifikation.md: alle fuenf
  handgepflegten Stellen nachgezogen (Bestandsaufnahme, Uebersichtszeile
  7/10, Summenzeile 108/134, Klassen-Verteilung unveraendert bei 63 Paaren,
  Hintergrunddienst-Abschnitt haelt die Abwesenheit eines sechsten Falls
  fest) — alle gemessen, nicht abgeschrieben, Befund K haelt exakt
- docs/mandantentrennung-etappe2-fehlerrichtung.md: Nachtrag mit
  tatsaechlich umgesetzten Pfaden, beiden Falsifizierungsnachweisen
  (Testname+Meldung), und der Feststellung zum unveraenderten
  Controller-Kommentar
- .planning/WINDOWS.md: neuer offener Eintrag #23 (deviation) — kein Signal
  unterscheidet "keine Freigabe" von "Abfrage fand nichts", mit
  Vorabpruefung fuer Etappe 4 und begruendeter Verwerfung einer
  Laufzeitwarnung
- 833 Tests gruen (56 Dateien), Typpruefung sauber, Wegwerf-Werkzeug 66/66

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 11:30:37 +02:00
schalli 888f66003c feat(quick-260910-das): user-Dienst binden, Linie ziehen, Startsperre entschaerfen
- user.service.ts: findById/update/deactivate/delete bekommen einen
  Pflicht-Mandanten und laufen ueber forTenant(); create/update
  uebersetzen die plattformweite Eindeutigkeitsverletzung (P2002) in eine
  deutsche Konfliktmeldung ohne Halter/Mandant zu nennen; zwei neue
  Methoden (findAllForPlatformAdmin, findByIdForPlatformAdmin) bilden die
  Plattform-Administratorsicht als Schleife ueber alle Mandanten mit je
  einem gebundenen Lesezugriff nach; findByUsername bleibt bewusst
  ungebunden, Kopfkommentar richtiggestellt (Anmeldeweg laeuft seit
  Etappe 1 ueber SECURITY-DEFINER-Funktionen, kein Aufrufer mehr)
- admin-seed.service.ts: Erstanlage-Pruefung bleibt ungebunden (mit
  Begruendung), Erstanlage des Administrators bindet an den unmittelbar
  zuvor angelegten Mandanten (Befund J-Korrektur); P2002 bei der Anlage
  wird wie "Administrator existiert bereits" behandelt statt den Start
  abzubrechen -- jeder andere Fehler bricht weiterhin ab
- user.controller.ts: die vier Aufrufstellen der geaenderten Signaturen
  auf currentUser.tenantId umgestellt (Signatur-Minimalanpassung; die
  Rollenlogik inkl. Plattform-Administratorsicht folgt in Aufgabe 3)
- Zwei-Klienten-Nachweis in beiden Testdateien (Muster
  groups.service.spec.ts), Falsifizierungsnachweis fuer beide Bereiche
  durchgefuehrt und zurueckgenommen (siehe SUMMARY)
- docs/mandantentrennung-zugriffsklassifikation.md: Zwischenstand fuer
  (user.service.ts, user) und (admin-seed.service.ts, user) auf gemischt
  korrigiert, neue Zeile (user.service.ts, tenant) ergaenzt -- volle
  Klassenkorrektur mit Begruendung sowie die vier handgepflegten
  Uebersichtstabellen folgen in Aufgabe 3
- .planning/WINDOWS.md: offener Eintrag fuer die plattformweite
  Eindeutigkeit von username/email (Produktentscheidung fuer Etappe 3)
- 802 Tests gruen (13 neue in user.service.spec.ts, 4 neue in
  admin-seed.service.spec.ts), Typpruefung sauber, Wegwerf-Werkzeug
  meldet weiterhin alle 53 Pruefungen bestanden

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-10 10:27:14 +02:00
schalli 222f453747 feat(quick-260909-mir): dkv-Testlage herstellen, Konfigurationspfade binden, Planer-Pfad benennen
- apps/api/src/dkv/dkv.service.spec.ts (neu): Zwei-Klienten-Nachweis nach
  dem Muster aus groups.service.spec.ts/tender-triage.service.spec.ts —
  dieser Bereich hatte vorher KEINE Testdatei (Befund J). 7 Testfaelle
  decken getConfigForApi, saveConfig (Zugangsdaten-Erhaltung), testConnection,
  die Verarbeitungsstrecke und den bewusst ungebundenen Planer-Startpfad ab
- dkv.service.ts: loadConfig(tenantId?) in zwei Methoden geteilt —
  loadConfig(tenantId) [Pflicht-Mandant, gebunden] und die neue, eigene
  Methode loadAnyActiveConfigForScheduler() [bewusst UNGEBUNDEN, eigener
  Kopfkommentar mit beiden Zustaenden]. getConfigForApi/saveConfig/
  testConnection/_runPipeline binden je EINEN Klienten pro Methode
  vollstaendig ueber forTenant()
- dkv-scheduler.service.ts: Kopfkommentar fortgeschrieben (beide Zustaende,
  Praezedenzfall, Unsymmetrie), Aufruf auf loadAnyActiveConfigForScheduler()
  umgestellt — an der Ablauflogik des Planers nichts geaendert
- .planning/WINDOWS.md: Eintrag #21 (deviation) fuer die benannte Altlast
  des Planer-Startpfads angelegt
- docs/mandantentrennung-zugriffsklassifikation.md: dkvModuleConfig-Zeile
  auf den jetzt gemessenen Stand "gemischt" nachgezogen (Rule 3 — noetig,
  damit rls-access-inventory.spec.ts nach der Aufteilung von loadConfig()
  gruen bleibt; die uebrigen zwei dkv-Zeilen und die Uebersichtstabelle
  bleiben Aufgabe 3 vorbehalten)
- Falsifizierungsnachweis erbracht: getConfigForApi's erster gebundener
  Client probeweise durch this.prisma ersetzt, genau Test 1 wurde rot
  (6 andere blieben gruen), Rueckbau zurueckgenommen, Dateien identisch
  zum Ausgangsstand bestaetigt

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
2026-09-09 16:46:09 +02:00
schalli da0ac04e73 fix(quick-260909-eor): WINDOWS #20 offen halten bis Wirkung nach Scharfschalten belegt ist
Aufgabe 4: die Browser-Gegenprobe der Anmeldung ist bestanden (siehe SUMMARY).
Auf ausdruecklichen Wunsch bleibt WINDOWS #20 (forTenant()-Verbindungsdefekt)
dennoch OPEN statt fixed — die technische Reparatur ist nachgewiesen
(rls-scratch-check.mjs, 8/8 gegen eine Wegwerf-Datenbank mit Rolle ohne
BYPASSRLS), aber die Wirkung unter der echten Anwendungsrolle tessera_app
ist erst nach dem Scharfschalten (#18) beobachtbar. #20 bleibt damit an
dieselbe Bedingung gebunden wie #18 und #19.

Nebenbefund beim Zuruecksetzen: die vorherige Markierung als "fixed" hatte
nur die Markdown-Tabelle veraendert, nicht den massgeblichen JSON-Block am
Dateiende (die eigentliche Quelle der Wahrheit fuer gsd-tools windows status)
— `gsd-tools windows status` scheiterte seitdem mit
"Ledger counts disagree with entries". Behoben durch Neuaufbau aus der
Vorversion via der broken-windows.cjs-Bibliothek (parseLedger/renderLedger),
damit Tabelle und JSON-Block wieder uebereinstimmen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:11:28 +02:00
schalli 5f3a39c2c3 docs(quick-260909-eor): alle 227 Datenbankzugriffe klassifiziert und maschinell abgesichert
WINDOWS #18/#20, Aufgabe 3: docs/mandantentrennung-zugriffsklassifikation.md
haelt fuer jede der 227 this.prisma.*-Fundstellen (32 Dateien, zusammengefasst
zu 59 Datei-Modell-Paaren) eine Klasse fest — muss-mandantengebunden (31),
keine-mandantengebundene-tabelle (16), bewusst-uebergreifend (3, mit
ausgeschriebenem Grund) oder beides (9, der Hintergrunddienst-Sonderfall:
uebergreifend lesen, je Zeile mandantengebunden schreiben — betrifft
ldap.service.ts, tender-digest.scheduler.ts, tender-matching.service.ts).

rls-access-inventory.spec.ts ermittelt die Fundstellen bei jedem Testlauf neu
aus dem Quelltext und vergleicht sie gegen die Tabelle im Dokument — Datei und
Modellname als Schluessel, keine Zeilennummer. Scheitert nachweislich, sobald
eine Fundstelle fehlt oder ein Eintrag verwaist (per Testlauf geprueft, danach
zurueckgesetzt).

Zwei belegte Befunde im Dokument festgehalten: req.tenantPrisma wird gesetzt,
aber nirgends gelesen; WINDOWS #19 (nullbares tenantId bei SearchProvider/
TenderRssFeedSource) bleibt benannter Blocker fuer Etappe 3.

docs/mandantentrennung-datenbankrolle.md verweist jetzt auf das neue
Dokument und korrigiert die ueberholte Zahl 182 auf den nachgemessenen Stand
(227/59). WINDOWS.md #18/#19 um Nachtrag auf diesen Plan ergaenzt; #20 (der
in Aufgabe 1 gemessene und behobene forTenant()-Verbindungsdefekt) als
"fixed" markiert.

Deviation (Rule 3, blockierend fuer die Bestandsaufnahme-Pruefung):
auth.service.ts-Kommentar umformuliert, der zuvor woertlich
"this.prisma.user.findUnique" als erklaerenden Text enthielt und dadurch
einen Eigentreffer der grep-basierten Inventur-Pruefung erzeugte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:04:29 +02:00
schalli d0393ac4af docs: forTenant setzt den Kontext auf der falschen Verbindung (#20)
Empirisch reproduziert, nicht hergeleitet: set_config landete auf Backend-PID
254999, die eigentliche Abfrage auf 255000, dort war app.current_tenant NULL.
Die Erweiterung setzt den Kontext auf tx, dispatcht die Abfrage aber ueber den
aeusseren Client.

Folge: die Mandantentrennung hat nie funktioniert, auch nicht an den Stellen,
die sie scheinbar nutzen. Heute unsichtbar, weil die Rolle ohnehin BYPASSRLS
hat (#18). Nach dem Scharfschalten kehrt es sich um — die Abfragen liefern
dann null Zeilen, und der LDAP-Loeschzweig deutet das als "Gruppe im
Verzeichnis verschwunden" und loescht sie samt Mitgliedschaften und
Modulfreigaben.

Zusatz: von 36 vermeintlichen forTenant-Vorkommen sind die meisten Kommentare,
die dessen Fehlen erklaeren — echte Aufrufstellen sind 6. Und das von
tenant.middleware.ts:44 und tenant.guard.ts:41 gesetzte req.tenantPrisma liest
niemand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:44:39 +02:00
schalli 775a938f32 docs: Dateisicherung auf dem Server wirksam — WINDOWS #17 geschlossen
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 55s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
Beim Nachtragen kam heraus, dass auf dem Server gar nicht docker-compose.yml
gilt: die .env setzt COMPOSE_FILE=docker-compose.prod.yml. Meine erste Angabe
nannte deshalb die falsche Datei — aufgefallen, weil nach dem Eintragen
'docker compose config' weiterhin nur pgdata rendern wollte.

Die drei Zeilen sind jetzt in docker-compose.prod.yml ergaenzt, Sicherung
liegt als docker-compose.prod.yml.bak.20260909-0818 daneben.

Nach dem Neuerstellen durch den User belegt statt behauptet:
- Mount tessera_user-files -> /app/user-files am laufenden Container
- der Ordner gehoert uid 1001, das benannte Volume hat die Eigentuemerschaft
  beim ersten Einhaengen uebernommen (genau der Grund, warum es kein
  Bind-Mount wurde)
- Schreiben als Dienstnutzer funktioniert
- eine Probedatei lag unter /var/lib/docker/volumes/tessera_user-files/_data
  auf dem Host, also ausserhalb des Containers; danach wieder entfernt

Ledger: 16 behoben, 1 zurueckgestellt, offen nur noch #18 und #19.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:23:17 +02:00
schalli ea003d44fe docs(quick-260909-dgj): Mandantentrennung vorbereitet — Plan, Bericht, Befunde
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 52s
Tessera CI/CD / Build & Publish Images (push) Successful in 28s
Die Arbeit liefert Rolle, Verbindungstrennung, Pruefwerkzeug und Policies fuer
alle 16 offenen Tabellen, schaltet die Trennung aber bewusst NICHT scharf.

Grund, gemessen statt vermutet: im Code stehen 182 Datenbankzugriffe ohne
Mandantenkontext gegen 19 mit. Der Anmeldeweg ist zwingend darunter — er liest
die Benutzerzeile, bevor der Mandant bekannt ist, weil der Mandant erst aus
dieser Zeile kommt. Unter einer Rolle ohne Umgehungsrecht liefert diese Abfrage
nichts, und niemand koennte sich mehr anmelden. Das Scharfschalten ist damit
ein eigener Vorgang, kein Nebeneffekt dieser Arbeit.

Neu im Ledger als #19: SearchProvider und TenderRssFeedSource haben ein
nullable tenantId. Die einfache Policy vergleicht NULL nie gleich, wodurch die
plattformweiten Zeilen nach dem Scharfschalten fuer JEDEN Mandanten
verschwinden wuerden — nicht nur fuer fremde. Heute wirkungslos, beim
Scharfschalten zwingend mitzuloesen. Die richtige Semantik ist eine
Produktentscheidung, deshalb bewusst nicht eigenmaechtig anders geloest.

673/673 Tests gruen, Typpruefung sauber. #18 bleibt offen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 10:08:59 +02:00
schalli fbb0d09e42 docs: Mandantentrennung greift nicht — Anwendungsrolle umgeht RLS (#18)
Beim Vorbereiten der RLS-Ausweitung gemessen: die API verbindet als Rolle
'tessera' (docker-compose.yml:33), und diese Rolle hat rolsuper=t und
rolbypassrls=t. PostgreSQL wendet Row-Level-Security auf solche Rollen nicht
an; FORCE ROW LEVEL SECURITY hilft nicht, das betrifft nur den
Tabelleneigentuemer.

Praktisch belegt statt hergeleitet: ohne gesetztes app.current_tenant liefert
SELECT count(*) FROM "Group" zwei Zeilen, obwohl die Policy bei NULL-Kontext
null liefern muesste.

Folge: alle sieben bisher mit RLS ausgestatteten Tabellen sind faktisch
ungeschuetzt. Die Trennung haengt allein am manuellen where-tenantId im Code.
Die Migration 20260804130918 beschreibt RLS als 'zweites Sicherheitsnetz' —
dieses Netz existiert derzeit nicht.

Das aendert die Reihenfolge der geplanten Arbeit: erst eine Anwendungsrolle
ohne Superuser- und BYPASSRLS-Recht, dann greifen die vorhandenen Policies,
erst danach lohnt das Ergaenzen fehlender Tabellen. Sonst baut man Regeln, die
nichts tun und Sicherheit vortaeuschen.

Kein akutes Risiko: Tessera laeuft intern mit einem einzigen Mandanten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 09:42:26 +02:00
schalli 3501eb4dd1 docs: Anleitungen fuer Kollegen — Anwender, Administration, Betrieb, Entwicklung
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 54s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
Bisher gab es fuer Kollegen keine Dokumentation: im Projekt lagen nur das
CI/CD-Runbook und die Arbeitsanweisungen fuer die Entwicklung. Diese Luecke
schliessen vier Anleitungen plus eine Einstiegsseite unter docs/.

Alle vier wurden gegen den Quelltext geschrieben, nicht aus der Planung
abgeleitet, und anschliessend unabhaengig gegengeprueft: jede zitierte
Beschriftung ist woertlich aus de.json belegt, jede beschriebene Funktion im
Code nachgewiesen, alle Befehle und Pfade gegen die echten Compose-Dateien,
Dockerfiles und package.json-Skripte verifiziert. Die Gegenpruefung fand keine
falsche Aussage.

Die Einstiegsseite hebt die drei Punkte hervor, die in der Praxis am meisten
Zeit gekostet haben: Anmeldung ueber den Benutzernamen statt der E-Mail,
der Unterschied zwischen aktiviert und freigegeben, und dass ein blosses
'up -d' die laufenden Container nicht ersetzt.

Nebenbefund beim Schreiben des Betriebshandbuchs, als #17 im Ledger erfasst:
user-files/ ist in keiner Compose-Datei als Volume eingebunden — hochgeladene
Profilbilder und DKV-Exporte ueberleben kein --force-recreate. Noch ohne
Schaden, da bisher kein Nutzer ein Profilbild hinterlegt hat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 08:46:33 +02:00
schalli ceb94e424b test(admin): WINDOWS #14 und #15 abgenommen — Ledger ohne offene Punkte
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 50s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
Abnahme im Browser auf alpha gegen das echte AD, nachdem der User die Container
neu erstellt hatte. Voraussetzungen gemessen statt angenommen: Container
tatsaechlich getauscht, Migration 20260909120000_user_email_optional angewandt,
User.email is_nullable = YES. Die Migration lief beim API-Start automatisch mit.

#15 — der Sync meldet jetzt "Erstellt: 4" statt vier stiller Fehlschlaege. Die
kollidierenden Konten sind angelegt und aktiv, ohne Adresse; mbuntz behaelt als
erster Anspruch seine. Der Bericht zeigt zwei verstaendliche deutsche
Abschnitte statt Prisma-Text und englischer Meldungen. Die beiden Folgestellen
vertragen Konten ohne Adresse: die Benutzerliste zeigt einen Gedankenstrich,
und die Suche im Mitglieder-Dialog liefert die Konten sauber statt
abzustuerzen — das war der eigentliche Fallstrick.

#14 — die Matrix-Suche laesst die nicht getroffene Achse stehen. Alle vier
Faelle geprueft: Modulname behaelt die Gruppenspalten, Gruppenname behaelt die
Modulzeile, der AD-Name findet dieselbe Spalte wie der interne Name
(Regression #6c intakt), und eine Suche ohne Treffer erklaert sich in einem
verstaendlichen Satz.

Am Active Directory wurde nichts veraendert; es wurde nur gelesen. Der fuer die
Regressionsprobe gesetzte interne Name ist wieder geleert.

Ledger danach: 0 offen, 15 behoben, 1 zurueckgestellt (#12 — es gibt intern
kein Postfach fuer Ausschreibungs-Alarme).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 08:26:15 +02:00
schalli f1ecbf542e test(tender-radar): Verbindungstest abgenommen (#16 zu), Postfach-Test zurueckgestellt
Abnahme im Browser auf alpha gegen den echten Exchange owa.ctl.de, nachdem die
Container mit --force-recreate getauscht waren. Der blosse pull genuegte nicht:
die neue Route fehlte im laufenden Container und war im gezogenen Image
vorhanden — hart belegt statt vermutet.

Alle vier Punkte bestanden:
- gespeicherte Konfiguration mit LEEREM Passwortfeld meldet "Verbindung
  erfolgreich" — belegt zugleich den Rueckgriff auf die gespeicherten
  verschluesselten Zugangsdaten
- absichtlich falscher Server meldet "Verbindung fehlgeschlagen: getaddrinfo
  ENOTFOUND owa-gibtsnicht.ctl.de"
- keine Zugangsdaten im API-Protokoll (die Treffer einer Mustersuche waren
  Routennamen wie /auth/reset-password)
- der Test schreibt nichts; die gespeicherte Konfiguration blieb unveraendert

Nebenbefund mit Gewicht: das Protokoll zeigt eine echte Antwort von Exchange
2019 (ServerVersionInfo 15.2) samt aufgeloester FolderId. Der in Plan 14-03 als
fragil markierte, handgeschriebene NTLM/SOAP-Weg ist damit erstmals gegen einen
echten Server gemessen — bisher lag nur gemocktes httpntlm.post vor.

WINDOWS #12 zurueckgestellt (waived): es gibt intern kein Postfach, in das
Ausschreibungs-Alarme hereinkommen. Ein frueheres Missverstaendnis hatte eines
angenommen. Nachzuholen, sobald ein solches Postfach existiert; offen ist dann
allein das Einlesen einer echten Alarm-Mail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 07:21:36 +02:00
schalli eb36a962ac docs: Fehlender Verbindungstest beim Postfach als #16 erfasst
Das Postfach-Formular im Ausschreibungs-Radar hat keinen Verbindungstest,
obwohl beide Inbox-Provider testConnection() mitbringen und das DKV-Modul
sie ueber POST /dkv/test-connection samt Knopf bereits nutzt.

Historie geprueft: der Knopf war nie vorhanden — die Formular-Datei hat nur
zwei Commits (Erstellung 48e1252, Sprachumstellung 849aa9b), und eine Suche
ueber alle Zweige nach testConnection, test-connection, "Verbindung testen"
und passenden Uebersetzungsschluesseln findet fuer tender-radar nichts. Es
ist eine Luecke, keine Regression.

Ausserdem der Bildschirmabzug des Postfach-Formulars im Exchange-Modus als
Referenz fuer die Einrichtung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 15:25:39 +02:00
schalli b6b964b69d test(ldap): WINDOWS #4 und #6 belegt und geschlossen — ohne Aenderung am AD
Am Active Directory wurde nichts veraendert; es wurde ausschliesslich gelesen.
Die beiden verbliebenen Pruefpunkte liessen sich auf der Tessera-Seite
ausloesen, weil der Sync nur vergleicht, was er gespeichert hat, mit dem, was
im Verzeichnis steht — ob eine Abweichung aus einer AD-Aenderung stammt oder
aus einem verfaelschten Group-Datensatz, kann er nicht unterscheiden.

#4 / A1 (Umbenennung bricht die Bindung nicht): Gruppe Albphone_Technik_VT neu
importiert, danach in der Datenbank Name und DN auf einen veralteten Stand
gesetzt, objectGUID echt gelassen. Der Sync fand die Gruppe allein ueber den
objectGUID und schrieb den AD-Namen zurueck — "1 umbenannt". Nicht gemessen,
weil dafuer das Verzeichnis geaendert werden muesste: dass AD den objectGUID
bei einer Umbenennung stabil haelt. Das ist zugesicherte AD-Eigenschaft und
kein Tessera-Code; der Anteil, der schiefgehen konnte, ist gemessen.

#6 / b (Amber-Zeile): dieselbe Gruppe zur Standardgruppe gemacht, dann ihren
gespeicherten objectGUID ins Leere zeigen lassen. Ergebnis: "1 geloescht" plus
die amber gefaerbte vierte Zeile "Standardgruppen-Markierung musste neu
vergeben werden (1x)". Die Markierung wanderte vor der Loeschung zurueck — die
Korrektheitszusage von D-06 haelt.

Ledger: #4 und #6 auf fixed. Offen bleiben #12 (braucht ein echtes
Exchange-Postfach), #14 und #15 (in dieser Sitzung neu gefunden).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 15:12:00 +02:00
schalli a1a8b4fa7b test(ldap): Live-Test gegen echtes AD — A2 belegt, zwei neue Defekte gefunden
Auf alpha gegen das echte Active Directory balios.ctl.local geprueft.

Belegt:
- WINDOWS #4 / Annahme A2 (byteweise Filter-Syntax): read-only gemessen.
  EqualityFilter ueber rohen Buffer findet Claude_VT (DN und objectGUID
  stimmen mit der Tessera-DB ueberein), der frueher verwendete escapte
  Hex-String findet nichts. Der erwartete DN stammt aus der Datenbank,
  nicht aus der Filter-Hilfsfunktion — sonst waere die Messung tautologisch.
- WINDOWS #6 Teil (a): Sync ausgeloest, alle drei Zahlenzeilen erscheinen.
- WINDOWS #6 Teil (c): die Spalte ist in der Freigaben-Matrix sowohl unter
  dem internen Namen als auch unter dem AD-Namen auffindbar.

Weiterhin offen, weil Schreibzugriff im Verzeichnis noetig:
- #4 / A1 (objectGUID uebersteht Umbenennung)
- #6 Teil (b) (Amber-Zeile bei verschobener Standardmarkierung). Ueber den
  konfigurierten Suchbereich nicht nachstellbar — die WR-03-Weitsuche
  verhindert das zu Recht.

Neu im Ledger:
- #14: Die Suche in der Freigaben-Matrix filtert beide Achsen mit demselben
  Begriff. Ein Begriff, der nur eine Achse trifft, leert die andere ganz —
  es bleibt nie ein Kaestchen zum Klicken. Damit scheitert genau der Zweck
  der Suche.
- #15: Der Sync reicht rohe Prisma-Fehler und englische Techniktexte an den
  Administrator durch. Vier AD-Konten aus OU=CTL_PWS_Gruppen werden wegen
  einer geteilten E-Mail-Adresse nie importiert, ohne verstaendlichen Hinweis.

Der Testzustand wurde zurueckgesetzt: Cert Manager wieder deaktiviert,
interner Name wieder geleert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-07 14:49:07 +02:00
schalli f8d7da073b fix(web): Dunkelvariante an die .dark-Klasse binden — 100+ dark:-Angaben waren wirkungslos
Beim Einbau des Logos fiel auf, dass dessen Plattenkontur dark:stroke-white/25 im
dunklen Modus nicht griff. Die Ursache reicht weit ueber das Logo hinaus.

globals.css definierte die Farbtokens unter .dark, deklarierte aber kein
@custom-variant dark. In Tailwind 4 haengt die dark:-Utility-Variante per Vorgabe an
prefers-color-scheme, also an der Einstellung des Betriebssystems. Die Anwendung
schaltet den Modus jedoch ueber next-themes mit attribute=class. Beides lief damit
auseinander: sobald ein Nutzer im Portal auf dunkel stellte, waehrend sein System hell
stand, blieb JEDE dark:-Utility im Quellcode wirkungslos — ueber 100 Vorkommen in mehr
als zehn Dateien, darunter Status-, Warn- und Fehlerfarben, Hinweisboxen und Badges in
der Administration.

Unentdeckt geblieben ist es, weil Hintergrund und Textfarbe NICHT ueber dark: laufen,
sondern ueber die CSS-Variablen unter .dark. Die Oberflaeche wurde also grundsaetzlich
dunkel und nur die Feinheiten fehlten.

Am laufenden System in beide Richtungen gemessen. Vorher, bei html.dark und hellem
System: dark:bg-gray-800 ergab transparent, dark:text-green-400 blieb ohne Wirkung.
Nachher greifen beide korrekt, und im hellen Modus greifen sie weiterhin nicht — die
Logo-Kontur ist dort unsichtbar, im dunklen Modus weiss mit 25 Prozent Deckkraft.
221/221 Web-Tests gruen, Typpruefung sauber.

Erfasst als WINDOWS.md #13 und mit diesem Commit geschlossen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:35:49 +02:00
schalli 389a1fd717 docs(260907-efh): complete Cross-Source-Dedup-Fingerabdruck-Fix
WINDOWS.md #11 als fixed markiert — Fingerprint-Stufe hasht nur noch
[buyerName, title, deadlineAt], Wert kehrt als exakter Widerspruchs-Veto
zurueck, 16.255 Bestandszeilen wurden gegen die lokale DB nachgerechnet
(0 -> 26 Duplikat-Gruppen, davon 2 durch den Veto geschuetzt).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:44:39 +02:00
schalli 0645cac618 docs(15): WINDOWS #10 geschlossen — Browser-Gegenprobe nach dem Fix bestanden
Der am Vormittag gefundene Zugriffs-Guard-Defekt ist behoben (260907-e8k) und im
echten Browser gegen ein frisch gebautes Web-Image nachgeprueft.

nutzer2 ohne Freigabe bekommt jetzt auf /modules/tender-radar, dessen Unterseiten
my-sources und settings sowie auf /modules/dkv-fleet die 403-Seite als
Serverantwort. Auf /modules/cert-manager, wo er eine Freigabe hat, kommt er
weiterhin durch — das ist die Gegenprobe dagegen, dass die Sperre zu weit greift.
admin sieht alle vier Adressen unveraendert, nutzer1 sieht Meine Quellen mit allen
drei Abschnitten; die Phase-17-Funktion ist unbeschaedigt.

Im Bericht zusaetzlich festgehalten, dass ein erster Messversuch per fetch() aus der
laufenden Seite heraus faelschlich alle Seiten als gesperrt meldete, auch fuer den
Administrator. Der Aufruf fuehrt die Sitzung nicht wie eine echte Navigation mit.
Alle berichteten Zahlen stammen aus echten Seitenaufrufen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:40:48 +02:00
schalli 5aca441bb3 docs: Roadmap-Status ehrlich gemacht, zwei unsichtbare Befunde ins Ledger
Die Phasen 11 bis 14 standen in der Roadmap-Tabelle auf "In Progress", obwohl an
keiner davon noch gearbeitet wird. Der Status wurde gegen die Verifikationsberichte
gehalten und dabei zeigte sich, dass es kein reiner Buchhaltungsrueckstand war:

11 und 12 stehen auf passed und sind jetzt Complete.

13 traegt einen offenen Befund. tenderFingerprint() haengt buyerName, title,
cpvDivisionKey, deadlineKey und valueBucket zu einem String und hasht ihn. Die
Scraper liefern cpvDivisions immer leer, DOE meist gefuellt — dieselbe
Ausschreibung ergibt aus beiden Quellen verschiedene Fingerabdruecke. Die
Fingerprint-Stufe greift also gerade dort nicht, wo sie greifen soll. Das stand
seit dem 2026-07-23 in 13-VERIFICATION.md, aber nicht im Ledger und war damit
praktisch unsichtbar. Jetzt WINDOWS.md #11.

14 wartet auf den Live-Test gegen ein echtes Exchange-Postfach; der
NTLM/SOAP-Weg ist nur gegen Mocks geprueft. Vom User am 2026-07-24 bewusst
zurueckgestellt, stand ebenfalls nur im Verifikationsbericht. Jetzt WINDOWS.md #12.

Beide Phasen tragen den wahren Status statt "In Progress", mit Verweis auf den
jeweiligen Ledger-Eintrag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:09:28 +02:00
schalli 2c4558164e test(15,16): alte Browser-Gegenproben nachgeholt — ein Defekt gefunden
Die sechs seit Anfang August offenen Browser-Durchlaeufe aus Phase 15 und 16
(WINDOWS.md #1-#6) waren liegengeblieben, weil in den damaligen Sitzungen kein
Browser-Tool verfuegbar war. Der Stack lief fuer die Phase-17-Gegenproben ohnehin,
also wurden alle nachgeholt, die ohne echtes Active Directory pruefbar sind.

Geschlossen:

#1 (15-06) Gruppenverwaltung: anlegen, umbenennen, Standardmarkierung exklusiv
setzen und zuruecknehmen, Mitglied hinzufuegen und entfernen, Loeschdialog nennt
beide Zahlen konkret. Bei gestoppter API meldet der Loeschvorgang deutschen
Klartext und laesst die Gruppe stehen. Die AD-Bindung ist an dieser Stelle
gegenstandslos geworden — Phase 16 hat sie in den LDAP-Bereich verlagert.

#2 (15-07) Freigaben-Matrix: setzen und entziehen, Aktivierungsdialog auf beiden
Wegen (Sofort-Freigabe legt den Grant an, "Spaeter konfigurieren" nicht),
Direkt-Grant neben Gruppen-Grant mit sichtbarer Gruppenspalte, langer Gruppenname
bleibt in seinen Massen. Bei gestoppter API springt die Checkbox zurueck und die
Datenbank bleibt unveraendert — kein optimistischer Zustand ueberlebt.

#5 (16-04) Gruppen-Dialog in allen drei Zustaenden, inklusive importierter Gruppe
mit gesperrtem AD-Namen und freiem internen Namen; die Liste zeigt danach den
internen Namen und traegt den AD-Namen im Tooltip. Die AD-Bindung wurde dafuer in
der Datenbank gesetzt, nicht importiert — im Bericht ausdruecklich vermerkt.

#3 (15-08) wurde durchgefuehrt und hat einen Defekt aufgedeckt:

WINDOWS #10 (neu, offen): PERM-04 greift nicht auf den modul-eigenen Routen. Der
Zugriffs-Guard sitzt allein in modules/[category]/[moduleSlug]/page.tsx. Die vier
fest verdrahteten Modulrouten (tender-radar, dkv-fleet, cert-manager, domaincheck)
samt Unterseiten laufen daran vorbei. Ein USER ohne Freigabe bekommt unter
/modules/procurement/tender-radar korrekt die 403-Seite, unter /modules/tender-radar,
/modules/tender-radar/my-sources und /modules/dkv-fleet dagegen die volle Seite mit
bedienbaren Knoepfen. Keine Datenpreisgabe — die API antwortet durchgehend 403 —
aber der Nutzer sieht Bedienelemente, die er nicht benutzen darf, und rohe
englische Techniktexte statt einer verstaendlichen Meldung. Sidebar, Marketplace
und API verhalten sich dagegen korrekt.

Offen bleiben #4 und #6: beide messen den Sync-Vorgang selbst und brauchen ein
erreichbares Active Directory.

Berichte mit Screenshots unter 15-UAT-2026-09-07.md und 16-UAT-2026-09-07.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:08:04 +02:00
schalli 72d0ba7e48 test(17): Browser-Gegenproben #7/#8/#9 nachgeholt — Phase 17 auf passed
Die drei human-check-Punkte aus Phase 17 waren am 2026-08-12 offen geblieben,
weil in der Ausfuehrungssitzung kein Browser-Tool verfuegbar war. Sie wurden
jetzt gegen frisch gebaute Images aus main (79015dd) mit echtem Chrome und
leerer Datenbank durchgeklickt.

#7 (17-01 Task 2): nutzer1 speichert sein Postfach, die Werte ueberleben den
Reload; nutzer2 sieht ein leeres Formular statt der Werte von nutzer1. Danach
zwei TenderEmailConfig-Zeilen mit je eigenem userId.

#8 (17-03 Task 1): Meine Quellen zeigt alle drei Abschnitte, ein eigener
RSS-Feed erscheint sofort mit Entfernen-Knopf, der plattformweite service-bund
steht darunter ohne, und das Digest-Intervall "Woechentlich" ueberlebt den
Reload.

#9 (17-03 Task 2): USER sieht auf /settings keine Bedienelemente, nur Hinweis
und Verweis; SUPER_ADMIN sieht Abrufintervall und plattformweite Feeds, aber
kein Postfach und keine Benachrichtigung mehr. Das Zahnrad fuehrt in beiden
Faellen nach Meine Quellen.

Zusaetzlich am laufenden Server gemessen, weil eine ausgeblendete Schaltflaeche
kein Beweis fuer eine serverseitige Sperre ist: als nutzer2 liefert GET
/rss-feeds nur den plattformweiten Feed, DELETE auf den plattformweiten wie auf
den fremden Feed antwortet 404 ohne die Zeile anzufassen, und POST mit
scope=platform wird mit 403 abgewiesen.

WINDOWS.md #7/#8/#9 auf fixed (open_count 9 -> 6), Verifikationsbericht mit
Nachtrag und Screenshots auf passed, ROADMAP auf Complete, STATE nachgezogen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 09:53:02 +02:00
schalli 4453e86626 docs(17-03): complete UI-Aufteilung Meine Quellen vs. Administration plan
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 53s
Tessera CI/CD / Build & Publish Images (push) Successful in 1m58s
2026-08-12 12:06:31 +02:00
schalli 71dcb302a3 docs(17-01): complete eigenes Alert-Postfach je Nutzer plan 2026-08-12 11:27:28 +02:00
schalli a0c5e470c3 docs(16-05): add plan summary, close PERM-02, log open verification items
- 16-05-SUMMARY.md documents the two-task plan (sync-report wiring,
  grants-matrix internal-name fallback).
- PERM-02 marked complete in REQUIREMENTS.md: all five Phase-16 success
  criteria are code-complete across plans 16-01..16-03; this plan
  delivered the last missing visibility layer (D-05/D-06) and the
  third D-04 display site.
- WINDOWS.md #6: this plan's own manual browser walkthrough (sync
  report three-line render, amber default-marker line, grants-matrix
  two-name search) not executed — no browser tool in this session.
2026-08-06 16:40:43 +02:00
schalli 184a3a1174 docs(16-04): complete Gruppen-Dialog-Umbau plan 2026-08-06 16:33:59 +02:00
schalli 5a687a9d01 docs(16-03): complete Gruppen-Rekonziliation plan 2026-08-06 16:24:25 +02:00
schalli d15475d6d7 docs(15-08): complete Serverseitige Modulsperre und Marketplace-Sperr-Badge plan 2026-08-04 19:53:13 +02:00
schalli c6086ba750 docs(15-07): complete Freigabe-Matrix, Aktivierungsdialog und Benutzer-Detail-Grants plan 2026-08-04 19:28:18 +02:00
schalli a3e8d0a158 docs(15-06): complete Gruppenverwaltung im Admin-UI plan 2026-08-04 19:06:54 +02:00