Commit Graph

885 Commits

Author SHA1 Message Date
schalli bf7f250c81 docs(quick-260907-hyi): Abnahme dokumentiert, Umlaut-Korrektur abgeschlossen
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m51s
Die Browser-Abnahme hat drei uebersehene Stellen gefunden — "Aenderungen speichern",
"Oeffnen" und einen LDAP-Hinweis — und damit die Luecke im Waechter aufgedeckt, durch
die sie geschluepft sind: sein Verdachtsmuster war case-sensitiv. Beides mit 85d2d77
behoben.

In der SUMMARY festgehalten, wie geprueft wurde: eine unabhaengige Analyse aller
Tokens in de.json findet keine Ersatzschreibung mehr, der Schluesselsatz ist
unveraendert bei 787, und die Modulbeschreibung in der bestehenden Datenbank ist beim
API-Neustart per Seed-Upsert mitgewandert — ohne Migration, wie geplant.

Ausserdem vermerkt, dass eine erste Sichtpruefung per fetch aus der laufenden Seite
faelschlich Entwarnung gab. Dieselbe Methode hatte in dieser Sitzung schon einmal ein
falsches Ergebnis geliefert; die berichteten Zahlen stammen aus echten
Seitenaufrufen.

Backlog-Punkt zu den Umlauten nach completed verschoben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:20:36 +02:00
schalli 85d2d772b6 fix(i18n): drei uebersehene Umlaute und die Luecke, durch die sie schluepften
Die Gegenprobe im Browser hat drei Stellen gefunden, die der erste Durchgang nicht
erwischt hat — darunter zwei gut sichtbare Schaltflaechen:

  Aenderungen speichern  ->  Änderungen speichern
  Oeffnen                ->  Öffnen
  Eine Aenderung ...     ->  Eine Änderung ...

Die Ursache ist dieselbe fuer alle drei und steckte im Waechter selbst: sein
Verdachtsmuster /(ae|oe|ue|ss)/ war case-sensitiv. "Aenderungen" beginnt mit "Ae",
nicht mit "ae", und ist deshalb durchgerutscht — der Waechter konnte gar nicht
anschlagen. Muster jetzt case-insensitiv; damit erfasst es auch die
grossgeschriebenen Formen.

Durch die schaerfere Pruefung melden sich neu die Abkuerzungen RSS, RSSGenerator und
SSL. Sie tragen ein doppeltes S ohne Umlaut-Bezug und stehen jetzt auf der
Positivliste.

Ausserdem zwei Meldungen des LDAP-Abgleichs korrigiert, die dem Administrator in der
Oberflaeche angezeigt werden (result.errors landet in der Fehlerliste der
LDAP-Seite): "ungueltiger ldapObjectGuid-Wert" und "Base-DN-Konfiguration pruefen.
Nicht geloescht." Drei Tests pinnen diese Texte bewusst und wurden mitgezogen.

Bewusst NICHT angefasst: die Warnung in crypto.service.ts. Sie geht ueber
logger.warn ins Protokoll und nicht an einen Nutzer.

Unabhaengig gegengeprueft: von allen Tokens in de.json, die ae/oe/ue tragen, ist
keines mehr eine Ersatzschreibung. 642 API-Tests und 225 Web-Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:16:57 +02:00
schalli cb6ba7188a docs(quick-260907-hyi): SUMMARY fuer Tasks 1-3, Task 4 (Browser-Abnahme) offen
Dokumentiert die drei ausgefuehrten Tasks (Woerterbuch + de.json,
Vitest-Waechter, API-Texte), die zwei waehrend Task 2 selbst
entdeckten und behobenen Woerterbuch-Luecken sowie die realen
Test-/Build-Ergebnisse fuer beide Apps. Task 4 (Sichtpruefung nach
Docker-Rebuild) bleibt dem Orchestrator ueberlassen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:08:53 +02:00
schalli d9f1422d6f fix(api): Umlaute in Passwort-Mail und Modulbeschreibung
Korrigiert die deutschen Zeichenketten in beiden Mails aus
mail.service.ts (Betreff, Passwort-Reset-Fliesstext,
Willkommens-Mail) sowie die domaincheck-Modulbeschreibung in
domaincheck.seed.ts auf echte Umlaute. Wortlaut identisch, nur
Zeichen korrigiert; die englischen Zweige bleiben unveraendert.

seedModule() ist ein upsert mit update-Zweig und laeuft in
onModuleInit — ein API-Neustart schreibt die korrigierte
Beschreibung ueber bestehende Datenbankzeilen, ohne Migration oder
Backfill.

pnpm --filter @tessera/api run build: sauber.
pnpm --filter @tessera/api run type-check: sauber.
pnpm --filter @tessera/api run test: 46 Testdateien, 642 Tests gruen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:07:38 +02:00
schalli caa4827360 test(i18n): Waechter gegen neue Umlaut-Ersatzschreibweisen
Legt umlaut-guard.spec.ts an, das ausschliesslich das geparste JSON
von de.json/en.json liest (nie repo-weit ueber Quelldateien greift,
sonst schluege es am Woerterbuch selbst an) und drei Dinge prueft:
kein Token aus UMLAUT_REPLACEMENTS mehr in de.json, jedes verbliebene
ae/oe/ue/ss-Token steht auf UMLAUT_ALLOWLIST, und de.json/en.json
haben denselben Schluesselsatz. Manuell mit einem probeweise
eingefuegten "fuer" verifiziert: Meldung nennt "für" und den
Schluesselpfad, danach zurueckgenommen.

Beim Aufbau des Waechters kamen zwei Luecken der Task-1-Wortliste ans
Licht: "Bestaetigen" (Grossschreibung, common.confirm) fehlte in
UMLAUT_REPLACEMENTS und blieb faelschlich falsch geschrieben; die
Ersetzungen zu Passwoerter/vertrauenswuerdigen/ausschliessen/
entschluesselt ergeben nach der Korrektur korrektes Deutsch, das aber
weiterhin ae/oe/ue/ss enthaelt und deshalb auf UMLAUT_ALLOWLIST
ergaenzt werden musste. Beide Luecken behoben (Rule 1 - Bug).

pnpm --filter @tessera/web test: 38 Testdateien, 225 Tests gruen.
pnpm --filter @tessera/web run type-check: sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:06:25 +02:00
schalli 3d351c6a99 fix(i18n): Umlaute in de.json korrigieren
Die deutsche Oberflaeche schrieb Umlaute als Buchstabenpaare aus
("fuer" statt "für", "Loeschen" statt "Löschen"). Legt
apps/web/src/messages/umlaut-dictionary.ts als einzige Wahrheitsquelle
fuer eine explizite Token-Ersetzungsliste sowie eine Positivliste
bereits korrekter Woerter (Passwort, Quelle, manuell, neue, ...) an
und korrigiert damit 67 falsche Tokens (140 Fundstellen) in de.json.
Ersetzt wird ausschliesslich per Ganzwort-Treffer, nie per
Zeichenregel, damit Woerter wie "Quelle" oder "manuell" unangetastet
bleiben. Der Platzhalterwert "empfaenger@example.com" bleibt
unveraendert, da er eine Beispiel-E-Mail-Adresse ist. Schluessel und
Reihenfolge in de.json sind unveraendert, de.json und en.json tragen
weiterhin denselben Satz von 787 Schluesseln.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 13:03:42 +02:00
schalli bec0530a2b docs(quick-260907-fgv): Abnahme dokumentiert, Quick-Task abgeschlossen
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 1m51s
Task 3 (Browser-Abnahme) ist durchgefuehrt. Von den beiden zur Freigabe gestellten
Gestaltungsfragen wurde eine zurueckgewiesen und eine blieb offen:

Die Anordnung auf dem Anmelde-Panel hat der User weder als "Marke oben" noch als
"Marke unten" abgenommen, sondern eine dritte Loesung verlangt — Logo links, Text
rechts daneben, wie in der gelieferten Schriftzug-Datei. Nachgezogen mit 11c0ec6.

Zur Schriftzugfarbe hat er sich nicht geaeussert; sie bleibt vorerst dunkel, die
Rueckkehr zu Gelb wurde als jederzeit moeglich angeboten.

Ausserdem in der SUMMARY festgehalten, dass die Abnahme einen Defekt ausserhalb dieses
Plans aufgedeckt hat: die Tailwind-Dunkelvariante war nie an die .dark-Klasse gebunden
(WINDOWS #13, behoben mit f8d7da0). Der Logo-Einbau hat ihn nur sichtbar gemacht,
verursacht hat er ihn nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 12:50:43 +02:00
schalli b8bd4456df docs: zwei Punkte aus dem Logo-Einbau festgehalten
Beim Einbau des Logos im Browser aufgefallen und als Backlog-Punkte abgelegt, nicht
mitrepariert:

Mandanten-Branding — der Wunsch des Users, dass ein Administrator das Aussehen spaeter
selbst anpassen kann. Der Logo-Einbau hat dafuer schon vorgearbeitet: die Oberflaeche
zieht die Marke ausschliesslich aus einer zentralen Komponente, ein spaeterer Austausch
setzt an genau einer Stelle an. Was noch fehlt (Ablage, Mandantenfeld, Rueckfall,
Verwaltungsseite, Zusammenspiel mit hell/dunkel) steht im Punkt.

Umlaute — die deutschen Oberflaechentexte schreiben Umlaute in 29 Zeilen als
Buchstabenpaare aus: "fuer", "Loeschen", "Zurueck zum Dashboard", "Verfuegbar". Das
sieht jeder Nutzer auf jeder Seite. Mit Hinweis auf die Stolpersteine beim Ersetzen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:41:59 +02:00
schalli 11c0ec63cb feat(quick-260907-fgv): Anmeldeseite zeigt Logo links, Text rechts daneben
Auf Wunsch des Users stehen Bildmarke und Text auf dem gelben Anmelde-Panel jetzt
nebeneinander statt untereinander — dieselbe Anordnung wie in der gelieferten Datei
tessera-logo-schriftzug.svg: links die Marke, rechts der Name und darunter der
Untertitel.

Dafuer nimmt die horizontale Variante der Marken-Komponente einen optionalen
Untertitel entgegen. Die gestapelte Variante entfaellt — sie war nach dieser
Umstellung nirgends mehr im Einsatz, alle uebrigen fuenf Stellen standen schon vorher
nebeneinander.

Der Test auf die entfallene Variante wurde nicht geloescht, sondern durch zwei Tests
auf das neue Verhalten ersetzt: einer prueft, dass ohne Angabe kein Untertitel
erscheint, der andere die tatsaechliche Reihenfolge im Dokument (Marke vor Name, Name
vor Untertitel) statt nur deren Vorhandensein. Die Erwartungstexte sind von Hand
geschrieben und nicht aus derselben Quelle gebildet, aus der die Komponente sie zieht.

222/222 Web-Tests gruen, Typpruefung sauber, im Browser gegengeprueft.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:41: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 83e8cedcd4 docs(quick-260907-fgv): SUMMARY fuer Tasks 1+2, Task 3 (Browser-Abnahme) offen
Dokumentiert die uebernommenen/ausgelassenen Lieferdateien, die Begruendung
fuer Text- statt Bild-Schriftzug, die Konturloesung der dunklen Platte, die
Zusammenfuehrung des Gelbwerts sowie zwei waehrend Task 2 automatisch
behobene Deviationen. Status halted, weil Task 3
(checkpoint:human-verify) absichtlich nicht ausgefuehrt wurde.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:27:35 +02:00
schalli c4cc05a942 feat(quick-260907-fgv-02): sechs Marken-Stellen auf TesseraLogo umgestellt, Gelbwert zusammengefuehrt
- header.tsx, sidebar.tsx (Schublade), login/page.tsx (breit + schmal),
  reset-password/page.tsx, reset-password/[token]/page.tsx ziehen jetzt
  alle TesseraLogo statt eines von Hand gesetzten Schriftzugs
- login/page.tsx: handgezeichneter Vier-Quadrate-Platzhalter entfernt,
  gelbes Panel nutzt BRAND_YELLOW per Inline-Style statt eigener
  Tailwind-Klasse, Kopfkommentar ohne Farbwert neu formuliert
- account-settings-form.tsx: DEFAULT_ACCENT liest jetzt BRAND_YELLOW statt
  eines eigenen Literals
- tessera-logo.tsx: Schriftzug setzt keine eigene Textfarbe mehr, sondern
  erbt sie vom umgebenden Container (Rule 1 -- ohne diese Korrektur waere
  der Schriftzug auf dem immer gelben Anmelde-Panel im dunklen
  Erscheinungsbild unlesbar hell auf hell gewesen)
- sidebar.tsx: nicht mehr benoetigtes tCommon entfernt; sidebar.test.tsx
  um common.brand.logoAlt in der Uebersetzungs-Attrappe ergaenzt
- vollstaendige Web-Testsuite (221 Tests) und Typpruefung gruen

Docker-Neubau und Browser-Abnahme (curl-Health-Check gegen den lokalen
Container) bewusst ausgelassen -- der Orchestrator uebernimmt beides in
Task 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:25:14 +02:00
schalli 49d3df4485 feat(quick-260907-fgv-01): Markenbausteine — Icons, Farbkonstanten, TesseraLogo-Komponente
- favicon.svg und apple-touch-icon.png byteidentisch nach src/app/ kopiert
  (dateibasierte Next.js-Metadaten fuer den Browser-Tab)
- brand.ts: einzige Stelle im Web-Quellcode fuer die drei Markenfarben
  (BRAND_YELLOW, BRAND_OLIVE, BRAND_PLATE), aus favicon.svg uebernommen
- TesseraLogo-Komponente mit den Varianten mark/horizontal/stacked, als
  JSX-SVG von Hand gesetzt (kein dangerouslySetInnerHTML), Kontur-Klasse
  fuer die dunkle Platte per plateOutline-Eigenschaft schaltbar
- common.brand.logoAlt in de.json und en.json ergaenzt, Schluesselparitaet
  per Test abgesichert

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:21:40 +02:00
schalli 17a8355f27 docs(quick-260907-fgv): Plan fuer festen Einbau von Tessera-Logo und Favicon
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:19:16 +02:00
schalli 2b4e58799e docs: AD- und Exchange-Pruefungen fuer den Live-Test vorgemerkt
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 50s
Tessera CI/CD / Build & Publish Images (push) Successful in 8s
Entscheidung des Users vom 2026-09-07: die drei verbleibenden Ledger-Punkte werden
spaeter am Live-System geprueft, nicht in der Entwicklungsumgebung. Sie brauchen ein
erreichbares Active Directory (#4, #6) beziehungsweise ein echtes Exchange-Postfach
(#12) und sind hier nicht ersetzbar.

Als Deferred Items in STATE.md eingetragen, mit dem jeweiligen Pruefauftrag im
Klartext, damit beim naechsten Aufsetzen niemand neu recherchieren muss, was genau
zu tun ist.

Im Ledger bleiben sie ausdruecklich `open` und wurden NICHT auf `waived` gesetzt —
sie sollen nachgeholt werden, nur eben dort, wo die Fremdsysteme erreichbar sind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 11:01:01 +02:00
schalli dba261de38 docs(13): Dedup-Fix dokumentiert und am laufenden System gegengeprueft
Tessera CI/CD / Lint & Type Check (push) Successful in 49s
Tessera CI/CD / Tests (push) Successful in 48s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m1s
Die Fingerabdruck-Stufe legte vor dem Fix bei 16.255 Zeilen null Duplikate
zusammen — nicht zu wenige, sondern gar keine. Nach der Umstellung auf die
quellstabilen Felder sind es 26 Gruppen mit 52 Zeilen, davon 2 durch das neue
Wert-Veto getrennt. 374/374 API-Tests unabhaengig nachgelaufen.

Die automatische Nachrechnung wurde scharf geprueft statt nur beobachtet: 500
Zeilen wurden kuenstlich auf ein altes Fingerabdruck-Format zurueckgesetzt, dann
die API neu gebaut und gestartet. Der Start um 08:46:25 meldete "500 Zeile(n) auf
die neue Formel gebracht" und hinterliess null veraltete Zeilen; der Neustart um
08:46:49 erzeugte keine Meldung mehr. Damit ist beides belegt — dass sie repariert
und dass sie nicht bei jedem Start erneut laeuft.

Der Bericht haelt die drei bewusst getragenen Grenzen fest (zweites Los gleicher
Groessenordnung wird falsch zusammengelegt; RSS/E-Mail matchen nur ueber den Titel;
der eine bereits messbare Fall LSA242) und begruendet, warum ein Riegel dagegen
verworfen wurde: er wuerde RSS dauerhaft von der Dublettenerkennung ausschliessen.
Ausserdem festgehalten, dass die Stufe nur beim Einlesen wirkt — der Altbestand
wird nicht rueckwirkend zusammengefuehrt.

Phase 13 steht damit auf Complete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:48:29 +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 ee3b28e505 feat(260907-efh): automatische Fingerabdruck-Nachrechnung beim Start
Nach der Formelaenderung (v1 -> v2) traegt jede Bestandszeile einen Hash
der alten Formel und wuerde nie wieder auf einen neuen treffen. Die
Produktionsdatenbank wird von Hand nicht angefasst, deshalb rechnet ein
neuer Dienst beim Start alle veralteten Zeilen automatisch nach — an der
Versionsmarke aus tender-fingerprint.ts erkannt, gleiche Bauform wie die
LDAP-Bind-Passwort-Nachverschluesselung.

- TenderFingerprintBackfillService: OnApplicationBootstrap, seitenweise
  (500 Zeilen), pro Seite eine Transaktion, Fehler werden geloggt und
  geschluckt statt geworfen
- In tenders.module.ts VOR TenderSchedulerService eingetragen, damit die
  Nachrechnung vor der Cron-Registrierung laeuft
- Vier Tests: leere DB, alter+leerer Hash werden beide erfasst, zweiter
  Lauf schreibt nichts mehr, Datenbankfehler wirft den Haken nicht

Gemessen an der lokalen Datenbank (16.255 Zeilen): Fingerabdruck-Gruppen
mit mehr als einer Zeile vorher=0, nachher=26, veraltete Zeilen
danach=0, davon Gruppen mit widersprechenden Wert-Groessenordnungen=2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:41:58 +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 9e5445e6b7 fix(260907-efh): Wert-Veto in Stufe 3 und Fingerabdruck bei Update mitschreiben
Zwei Schaeden aus derselben Wurzel behoben (WINDOWS.md #11, beim Messen
gefunden): der Aktualisierungszweig aendert Titel/Vergabestelle/Frist
einer bestehenden Zeile, schrieb den Fingerabdruck dabei aber nie mit —
der gespeicherte Wert driftete vom Inhalt der Zeile weg und Stufe 3 fand
die Zeile nie wieder (an der Live-DB an drei Gruppen gemessen).

- fingerprint wird einmal in resolve() berechnet und sowohl im
  Anlegezweig als auch im Aktualisierungszweig geschrieben
- Neue Stufe-3-Pruefung: valueBucketsContradict() als Veto auf einen
  Fingerabdruck-Treffer, exakter Groessenordnungsvergleich, keine
  Aehnlichkeitssuche
- toNumberOrNull() haendelt Prisma Decimal und den einfachen
  Zahlenwert der Test-Fakes gleichermassen
- Vier neue Tests fuer die Wert-Faelle, bestehender Update-Test um die
  Fingerabdruck-Erwartung erweitert, alle vorherigen Tests bleiben gruen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:38:37 +02:00
schalli cb23e3bc78 fix(260907-efh): Fingerabdruck auf Vergabestelle/Titel/Frist verengen
Die dritte Dedup-Stufe hashte bisher fuenf Segmente, darunter CPV-Division
und geschaetzten Wert — beide werden von Nicht-DOE-Quellen systematisch
nie geliefert, wodurch dieselbe Ausschreibung aus DOE und aus einem
Scraper nie denselben Fingerabdruck ergab (WINDOWS.md #11).

- tenderFingerprint() nimmt nur noch buyerName, title, deadlineAt entgegen
- Versionsmarke FINGERPRINT_VERSION_PREFIX ("v2:") vor dem Hash, damit
  Task 3 veraltete Zeilen erkennen kann
- valueBucketsContradict() als eigene, exportierte Funktion fuer den
  Wert-Veto in Task 2 — kein Hash-Bestandteil mehr
- backfill-tender-source.ts an die neue Signatur angepasst
- Testsuite auf das neue Verhalten umgeschrieben inkl. Goldwert-Test

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:36:21 +02:00
schalli 4502664fb2 docs(quick-260907-efh): Plan fuer Cross-Source-Dedup-Fix (WINDOWS #11)
Fingerprint hasht kuenftig nur noch Vergabestelle/Titel/Frist, der Wert
kehrt als Widerspruchspruefung am Treffer zurueck, Versionsmarke plus
Bootstrap-Nachrechnung stellt Bestand und frische Installation gleich.

Beim Erden gemessen und mit aufgenommen: der Aktualisierungszweig in
TenderDedupService schreibt den Fingerabdruck nicht mit, wodurch drei
Gruppen echter Duplikate schon heute allein durch Drift auseinanderstehen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:34:18 +02:00
schalli 5504931af3 docs(quick-260907-e8k): complete Zugriffs-Guard-Fix fuer modul-eigene Routen
Beide Tasks des Plans ausgefuehrt, verifiziert und committed
(4a23e13, 69b6418). Automatisierte Verifikation vollstaendig gruen:
Gate-Tests, Layout-Tests, volle Web-Suite (213/213), Typpruefung und
Produktionsbau. Browser-Gegenprobe (A/B/C) bleibt laut Plan Aufgabe
des Orchestrators nach Docker-Rebuild — WINDOWS #10 bleibt bis dahin
offen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:29:19 +02:00
schalli 69b641861c feat(quick-260907-e8k-02): Layout-Gate vor jedes Modulverzeichnis, generische Route umgestellt
Legt je ein layout.tsx fuer cert-manager, dkv-fleet, domaincheck und
tender-radar an, das ModuleAccessGate mit dem fest eingetragenen
Verzeichnis-Slug umschliesst. Ein Layout im App Router deckt alle
verschachtelten Unterrouten automatisch mit ab — my-sources und
settings unter tender-radar sowie settings und vehicles unter
dkv-fleet schliessen sich ohne eigene Datei (WINDOWS #10, PERM-04).

Die generische Route [category]/[moduleSlug]/page.tsx nutzt jetzt
ebenfalls ModuleAccessGate statt des bisherigen Inline-403-Markups —
das 403-Markup existiert damit nur noch einmal im Code
(module-access-denied.tsx).

module-layouts.test.tsx deckt zwei Threats ab: falscher Slug in einem
der vier Layouts (T-e8k-03) und ein kuenftig hinzugefuegtes
Modulverzeichnis ohne Layout (T-e8k-04, liest das Verzeichnis per
node:fs aus). module-access.test.tsx ist auf die Weitergabe an das
Gate umgeschrieben, die 403-vs-Rendern-Entscheidung ist bereits durch
module-access-gate.test.tsx abgedeckt.

Volle Web-Testsuite (213 Tests), Typpruefung und Produktionsbau sind
gruen. Browser-Gegenprobe folgt durch den Orchestrator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:27:47 +02:00
schalli 4a23e13731 feat(quick-260907-e8k-01): gemeinsame 403-Komponente und ModuleAccessGate
Zieht das bisher inline in [category]/[moduleSlug]/page.tsx stehende
403-Markup in ModuleAccessDenied (uebersetzungsfrei, nimmt fertige Texte
als Props) und legt mit ModuleAccessGate eine wiederverwendbare
Server-Component-Pruefung an, die checkModuleAccess aufruft und bei
jeder Ausnahme ebenfalls als "kein Zugriff" wertet (zweite
Verteidigungslinie ueber das bereits geschlossen ausfallende
checkModuleAccess, T-15-29). Vier Testfaelle decken Durchlassen,
Verweigern, Ausnahme und Slug-Weitergabe ab.

Bereitet Task 2 vor: die vier Modul-Layouts und die generische Route
werden auf dieses Gate umgestellt (WINDOWS #10, PERM-04).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:24:30 +02:00
schalli 74a30fb8ef docs(quick-260907-e8k): plan module access gate for module-owned routes
WINDOWS #10: der Zugriffs-Guard sitzt nur in der generischen Route
modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten
Modulverzeichnisse laufen daran vorbei.

Plan: gemeinsame 403-Komponente + ModuleAccessGate (Server Component),
je ein layout.tsx pro Modulverzeichnis (deckt Unterrouten mit ab),
generische Route auf dieselben Bausteine umgestellt. Zwei Tasks,
Browser-Gegenprobe als human-check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
2026-09-07 10:22:44 +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 79015dd3f4 docs(17): mark phase human_needed in the roadmap
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Publish Images (push) Successful in 6s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:08:59 +02:00
schalli a46006eada docs(17): verify phase against the codebase — human_needed
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 51s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
Every mechanism checked against live schema, migrations and code rather
than the summaries. Three browser click-throughs remain unrun (no browser
tool this session), so the phase lands human_needed, not passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:08:45 +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 809afbce13 test(17-03): Tests fuer beide Seiten, Backlog-Punkt abgeschlossen
- RssFeedListForm.test.tsx auf scope umgestellt: personal zeigt eigene Feeds
  editierbar + plattformweite als schlichte, nicht bedienbare Liste darunter;
  platform zeigt nur plattformweite Feeds, eigene Feeds tauchen dort gar
  nicht auf; beide Faelle pruefen den scope-Parameter von createRssFeed
- settings-roles.test.tsx (neu): USER sieht Hinweis+Verweis statt
  Bedienelemente, ADMIN/SUPER_ADMIN sehen Abrufintervall+RSS-Feeds, unbekannte
  Rolle zeigt weder-noch, entfernte Abschnittsueberschriften kommen nirgends
  mehr vor
- my-sources.test.tsx (neu): alle drei Abschnittsueberschriften vorhanden,
  Verweis auf die Administrationsseite nur bei ADMIN/SUPER_ADMIN
- Erwartete Texte in allen drei Testdateien von Hand geschrieben, nicht aus
  der gleichen next-intl-Zuordnung abgeleitet, die die Komponenten benutzen
- Backlog-Punkt 2026-08-11-tender-radar-einstellungen-mischen-rollen.md nach
  todos/completed/ verschoben, mit Resolution-Abschnitt: beide Nutzer-
  Entscheidungen (eigene Quellen je Nutzer, Trefferliste bleibt
  plattform-global) und die Antwort auf offenen Punkt 4 (eigene
  nutzerseitige Modulseite als Vorbild fuer kuenftige Module) dokumentiert

Verifikation: Web 205/205, API src/tenders 362/362, beide Typpruefungen
fehlerfrei.
2026-08-12 12:02:36 +02:00
schalli 150046e14f feat(17-03): Administrationsseite schrumpft auf das, was Administration ist
- settings/page.tsx zeigt nur noch Abrufintervall + plattformweite Feeds;
  Postfach- und Benachrichtigungsabschnitt entfernt (ziehen auf my-sources um)
- Anzeigepruefung der Rolle aus dem Anmelde-Speicher: unbekannt -> Platzhalter,
  ADMIN/SUPER_ADMIN -> Inhalt, sonst Hinweistext + Verweis auf "Meine Quellen"
  (verbindliche Pruefung bleibt serverseitig, T-17-08/@UseModule, siehe
  Dateikommentar)
- Zahnrad auf der Modulseite fuehrt jetzt nach /my-sources statt /settings;
  neuer Schluessel page.mySourcesTitle, alter page.settingsTitle bleibt als
  Verweistext auf der Nutzerseite (Task 1) in Gebrauch
- settings.emailSectionTitle/emailSectionBody/notificationsSectionTitle aus
  beiden Sprachdateien entfernt (gegengeprueft: nirgends mehr referenziert);
  neue Schluessel settings.accessDeniedText, settings.rssSectionUserNote
2026-08-12 11:58:29 +02:00
schalli 4100bb575e feat(17-03): "Meine Quellen" wird vollstaendig — Postfach, eigene Feeds, Benachrichtigung
- RssFeedSource bekommt isPlatformWide (server-derived), createRssFeed nimmt
  einen scope-Parameter (personal/platform, Vorgabe personal)
- RssFeedListForm bekommt scope-Prop: personal zeigt eigene Feeds editierbar +
  plattformweite als schlichte Aufzaehlung ohne Knoepfe darunter; platform
  zeigt nur plattformweite Feeds editierbar
- DigestIntervalForm aus settings/page.tsx unveraendert herausgeloest (keine
  neuen Beschriftungen, gleiche settings.*-Schluessel)
- my-sources/page.tsx um "Meine Feeds" und "Benachrichtigung" erweitert,
  Verweis auf die Administrationsseite nur fuer ADMIN/SUPER_ADMIN
- settings/page.tsx vorgezogen auf RssFeedListForm scope="platform" (Rule 3,
  eigener Type-Check-Verify sonst rot) — volle Rollenpruesung folgt Task 2

Rule 1: createRssFeed's Antwort traegt kein isPlatformWide (nur GET mappt es
serverseitig) — RssFeedListForm setzt es nach dem Anlegen lokal aus dem
verwendeten scope, sonst wuerde ein frisch angelegter plattformweiter Feed
bis zum naechsten Neuladen aus seiner eigenen Liste verschwinden.
2026-08-12 11:56:42 +02:00
schalli e45d7f2068 docs(17-02): complete RSS-Feed-Besitzer plan 2026-08-12 11:49:07 +02:00
schalli 7dee116254 test(17-02): D-06 ownerTenantId tagging for RSS ingestion + seed idempotency
- RssAdapter.fetchTenders tags every record from a feed with a tenantId
  with the same D-13 ownerTenantId origin marking email-alert records
  carry since Phase 14; platform-wide feeds (no tenantId) stay unmarked.
  The pure parseFeed mapping is untouched — tagging happens in the
  fan-out loop that knows which row a batch came from
- Extracted the service.bund.de seed out of TendersModule.onModuleInit
  into seedServiceBundRssFeed() (tenders.seed.ts, same pattern as the
  existing seedTendersModule), so the find-then-create idempotency added
  in Task 1 is unit-tested directly instead of only via a Nest bootstrap
- New rss-feed-migration-sql.spec.ts: text-only check of the Task 1
  migration file (nullable columns, dropped/created indexes, no
  existing-row mutation, correct ordering)

- Files modified: apps/api/src/tenders/adapters/rss.adapter.ts, apps/api/src/tenders/tenders.module.ts, apps/api/src/tenders/tenders.seed.ts, apps/api/src/tenders/adapters/rss.adapter.spec.ts, apps/api/src/tenders/tenders.seed.spec.ts, apps/api/src/tenders/rss-feed-migration-sql.spec.ts
2026-08-12 11:45:27 +02:00
schalli 96161556db feat(17-02): delete protection and 20-feed cap for personal RSS feeds
- remove(id, {userId, isAdmin}) replaces remove(id): single conditional
  deleteMany (id AND (owned-by-caller OR admin-on-platform-feed)) — no
  TOCTOU window, ownership check lives in the DB condition. Deletes
  nothing -> NotFoundException (never Forbidden, no existence leak)
- createForUser rejects a caller's 21st personal feed with a clear
  German message (T-17-10); platform-wide feeds are not counted
- DELETE /rss-feeds/:feedId moves from @Roles(ADMIN,SUPER_ADMIN) to
  @UseModule('tender-radar') — ownership check does the gating now
- Tests use a Prisma double that actually evaluates the where condition
  (not a double that always "succeeds") for both deleteMany and count

- Files modified: apps/api/src/tenders/tender-rss-feed.service.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tender-rss-feed.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts
2026-08-12 11:41:55 +02:00
schalli adb72f611f feat(17-02): RSS feeds get an owner — platform-wide vs personal (D-02)
- TenderRssFeedSource.userId/tenantId (nullable): null = platform-wide
  (admin-managed, includes the existing service.bund.de default),
  set = personal feed owned by exactly one user
- Migration replaces url @unique with @@unique([userId, url]) — two
  users can now follow the same address independently; existing rows
  keep an empty owner (platform-wide, unchanged behavior)
- Service: listForUser/createForUser/createPlatform replace list/create
- Controller: GET/POST /rss-feeds move from @Roles(ADMIN,SUPER_ADMIN) to
  @UseModule('tender-radar'); POST with scope:'platform' still requires
  ADMIN/SUPER_ADMIN, checked inline (T-17-08)
- tenders.module.ts seed switched from upsert-on-url to find-then-create
  (Rule 3, pulled forward from Task 3): the new compound unique index
  requires a non-null userId in Prisma's generated type, so a
  platform-wide row can no longer be addressed via upsert

- Files modified: apps/api/prisma/schema.prisma, apps/api/prisma/migrations/20260812110000_tender_rss_feed_owner/migration.sql, apps/api/src/tenders/tender-rss-feed.service.ts, apps/api/src/tenders/dto/tender-rss-feed.dto.ts, apps/api/src/tenders/tenders.controller.ts, apps/api/src/tenders/tenders.module.ts, apps/api/src/tenders/tender-rss-feed.service.spec.ts, apps/api/src/tenders/tenders.controller.spec.ts
2026-08-12 11:37:56 +02:00
schalli 71dcb302a3 docs(17-01): complete eigenes Alert-Postfach je Nutzer plan 2026-08-12 11:27:28 +02:00
schalli 55ceb24ee9 test(17-01): prove multi-mailbox fan-out and the userId migration's SQL order
Der mandantenuebergreifende Sammelabruf in email-alert.adapter.ts bleibt
mechanisch unveraendert (findMany({isActive:true}) in einem Zug,
Fehlerbehandlung je Zeile) — geaendert wird nur die Warnmeldung (Zeilen-id
+ Besitzer statt Mandant, T-17-03) und die Klassendoku.

- Neue Tests: zwei aktive Postfaecher DESSELBEN Mandanten werden beide mit
  ihren jeweils eigenen Zugangsdaten abgeholt; ein kaputtes Postfach
  blockiert das andere nicht und protokolliert eine Warnung ohne
  Zugangsdaten/Adresse; die Herkunftsmarkierung folgt dem Mandantenfeld
  der jeweiligen Zeile (zwei Mandanten -> zwei Werte). Erwartungswerte von
  Hand geschrieben, nicht ueber die Produktivfunktion erzeugt.
- tender-email-config.service.spec.ts (bereits in der Task-2-Migration
  mitgeliefert) deckt zusaetzlich: tenantId wird beim Anlegen mitgeschrieben,
  zwei Nutzer desselben Mandanten erzeugen zwei Zeilen statt eine zu
  ueberschreiben.
- email-config-migration-sql.spec.ts (neu, Vorbild
  doe-url-migration-sql.spec.ts): prueft die Reihenfolge der
  Hand-Migration textuell — Zuordnung vor Loeschung, Pflicht erst nach
  Befuellung, alte Eindeutigkeit runter/neue rauf, gewoehnlicher
  tenantId-Index bleibt stehen.

src/tenders: 335/335 gruen. API gesamt: 603/603. Web gesamt: 192/192.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:24:12 +02:00
schalli 05b1d293d8 feat(17-01): move TenderEmailConfig ownership from tenant to user
Alert-Postfach gehoert jetzt dem einzelnen Nutzer (userId @unique) statt
dem Mandanten (D-01) — ein zweiter Kollege desselben Mandanten kann sein
eigenes Postfach anbinden. tenantId bleibt denormalisiert (SMTP-Aufloesung,
Herkunftsmarkierung), wird auf create UND update mitgeschrieben.

- Handgeschriebene Migration (prisma migrate dev verweigert die
  nicht-interaktive Shell): befuellt Bestandszeilen mit dem aeltesten
  aktiven Administrator ihres Mandanten, entfernt verwaiste Zeilen ohne
  Administrator, ersetzt die tenantId-Eindeutigkeit durch userId.
  Lokal getestet (0 Bestandszeilen lokal und auf alpha — Zaehlung im
  Task-1-Checkpoint), Index-Ergebnis verifiziert.
- TenderEmailConfigService.getConfigForApi/saveConfig auf userId als
  Schluessel umgestellt; saveConfig nimmt {userId, tenantId}.
- TendersController: email-config-Routen von @Roles(ADMIN,SUPER_ADMIN)
  auf @UseModule('tender-radar') umgestellt (Postfach ist jetzt
  Nutzereinstellung); Route-Reihenfolge vor @Get(':id') unveraendert.
- Neue Seite /modules/tender-radar/my-sources ("Meine Quellen") mit dem
  unveraenderten EmailAlertConfigForm; Hinweistext benennt D-05 (Tender
  bleibt plattform-global — nur wer Quellen einspeist aendert sich).
- tenders.controller.spec.ts an neue Service-Signatur angepasst (Rule 3,
  nicht im Plan gelistet, aber zum Kompilieren/Bestehen erforderlich).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:19:55 +02:00
schalli 42a2c7703f docs(17): plan per-user tender sources
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 08:28:44 +02:00
schalli 105683b94b docs(17): create phase plan 2026-08-12 08:26:00 +02:00
schalli 614629325d docs: park the module activation note until modules run internally
Tessera CI/CD / Lint & Type Check (push) Successful in 42s
Tessera CI/CD / Tests (push) Successful in 51s
Tessera CI/CD / Build & Publish Images (push) Successful in 8s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:43:08 +02:00
schalli 1fb96dd455 docs: close the compose drift note - server is a working copy now
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 53s
Tessera CI/CD / Build & Publish Images (push) Successful in 6s
/opt/tessera keeps its directory (compose derives the project name from it,
and a rename would have orphaned tessera_pgdata) and now tracks main.
COMPOSE_FILE pins it to the production file so the checkout does not switch
the server onto the dev defaults.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:38:39 +02:00
schalli 9ca71ae92f fix(compose): point the browser at a reachable API address in prod
Tessera CI/CD / Lint & Type Check (push) Successful in 45s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
NEXT_PUBLIC_API_URL is read by the browser, not by the web container, so
http://api:3001 could never work outside Docker. The test server had been
corrected by hand long ago; the fix never came back here, so the file we
would ship to a customer was the broken one.

Also adopts the server's TESSERA_FORCE_CHANGE default of true, so a fresh
install requires the initial admin password to be changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:34:49 +02:00
schalli 61948440ab docs: close the encryption-key backlog note and log the session
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Successful in 49s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
The note predated 7bda56d and f574884, so two of its three points were
already shipped when it was picked up. Records what each commit actually
closed, and that the .env deny rules block the two committed template
files that hold no secrets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:19:13 +02:00
schalli 379606ee34 docs: document TESSERA_ENCRYPTION_KEY in env templates
Both example files pointed at the old CALENDAR_ENCRYPTION_KEY name, and
.env.example did not mention the key at all. Since compose now aborts
startup when it is unset, anyone setting up a fresh install from these
templates hit a failure the templates never explained.

Adds the current variable name, the generation command, and a note that
losing the value makes stored credentials unrecoverable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:18:22 +02:00
schalli 96432a6a7b wip: session paused between milestones, v1.2 complete
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 47s
Tessera CI/CD / Build & Publish Images (push) Successful in 6s
No phase work in flight: Phase 15 (8/8) and Phase 16 (5/5) are both closed and
the roadmap reflects it. The handoff therefore sits at project level rather
than in a phase directory.

Records four anti-patterns discovered through actual failure this session, two
of them marked blocking: the tautological test that let the objectGUID defect
through review, and the fact that /opt/tessera is not a checkout, so compose
changes in this repository never reach the test server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:01:00 +02:00