Files
tessera-ctl/.planning/STATE.md
T
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

43 KiB

gsd_state_version, milestone, milestone_name, current_phase, current_phase_name, status, stopped_at, last_updated, last_activity, last_activity_desc, progress
gsd_state_version milestone milestone_name current_phase current_phase_name status stopped_at last_updated last_activity last_activity_desc progress
1.0 v1.2 Plattform-Berechtigungen 17 eigene-ausschreibungs-quellen-je-nutzer verified Live-Test gegen das echte AD (balios.ctl.local) auf alpha durchgefuehrt. BELEGT: #4/A2 (byteweise Filter-Syntax, read-only gemessen) und #6 Teile (a) drei Zahlenzeilen und (c) Spaltensuche unter internem und AD-Namen. OFFEN und auf den User angewiesen: #4/A1 und #6/(b) brauchen AD-Schreibzugriff (Weg: Wegwerf-Gruppe im AD anlegen, einbinden, umbenennen bzw. loeschen); #12 braucht Postfachadresse, EWS-Adresse und Zugangsdaten — auf alpha ist TenderEmailConfig leer. NEU gefunden: #14 (Matrix-Suche leert die jeweils andere Achse, Matrix damit unbenutzbar sobald gesucht wird) und #15 (rohe Prisma- und englische Techniktexte im Sync-Ergebnis, vier AD-Konten werden wegen E-Mail-Kollision nie importiert). 2026-09-07T14:50:00.000Z 2026-09-07 Live-Test gegen echtes AD — WINDOWS
total_phases completed_phases total_plans completed_plans
17 17 83 83

Project State

Project Reference

See: .planning/PROJECT.md (updated 2026-07-17)

Core value: Eine zentrale Plattform, in der beliebige Workflow-Tools als Module lizenziert, aktiviert und genutzt werden koennen -- ohne zwischen verschiedenen Anwendungen wechseln zu muessen. Current focus: Kein laufender Meilenstein — v1.2 ist abgeschlossen, ein neuer wurde noch nicht begonnen.

Current Position

Phase: 17 (eigene-ausschreibungs-quellen-je-nutzer) — VERIFIED / passed Plan: 3 of 3 Status: Phase abgeschlossen und im Browser gegengeprueft — bereit fuer /gsd-ship Last activity: 2026-09-07 — Browser-Gegenproben #7/#8/#9 nachgeholt, alle bestanden

Progress: [██████████] 100%

Performance Metrics

Velocity:

  • Total plans completed: 2
  • Average duration: 12 min
  • Total execution time: 0.38 hours

By Phase:

Phase Plans Total Avg/Plan
01-foundation-portal-shell 2/3 23 min 12 min

Recent Trend:

  • Last 5 plans: 01-01 (16 min), 01-02 (7 min)
  • Trend: improving

Updated after each plan completion | Phase 02-authentication-multi-tenancy P02 | 8min | 3 tasks | 26 files | | Phase 02-authentication-multi-tenancy P03 | 5min | 2 tasks | 20 files | | Phase 05-dashboard-calendar P01 | 14min | 4 tasks | 28 files | | Phase 05-dashboard-calendar P02 | 4min | 4 tasks | 16 files | | Phase 05-dashboard-calendar P03 | 11min | 4 tasks | 14 files | | Phase 05-dashboard-calendar PP04 | 5min | 2 tasks | 11 files | | Phase 06-desktop-client-ci-cd P01 | 5min | 2 tasks | 13 files | | Phase 06 P03 | 3min | 3 tasks | 3 files | | Phase 07-dkv-fleet-module P03 | 5min | 4 tasks | 8 files | | Phase 07-dkv-fleet-module P04 | 7min | 3 tasks | 8 files | | Phase 07-dkv-fleet-module P05 | 8min | 3 tasks | 11 files | | Phase 07-dkv-fleet-module P06 | 5min | 2 tasks | 7 files | | Phase 09 P03 | 17 | 3 tasks | 6 files | | Phase 09-cert-manager-module P04 | 4 | 3 tasks | 5 files | | Phase 09-cert-manager-module P05 | 8 | 3 tasks | 6 files | | Phase 09 P06 | 7 | 3 tasks | 7 files | | Phase 10 P01 | 15min | 3 tasks | 4 files | | Phase 10 P02 | 20min | 3 tasks | 6 files | | Phase 10 P03 | 35min | 3 tasks | 9 files | | Phase 10 P04 | 25min | 3 tasks | 5 files | | Phase 10 P05 | 20min | 3 tasks | 5 files | | Phase 10 P06 | 3min | 2 tasks | 4 files | Per-Plan Metrics:

Plan Duration Tasks Files
Phase 11 P01 8min 3 tasks 11 files
Phase 11 P02 4min 3 tasks 12 files
Phase 11 P03 9min 4 tasks 12 files
Phase 11 P04 12min 2 tasks 5 files
Phase 11 P05 24min 3 tasks 15 files
Phase 11 P06 35min 3 tasks 12 files
Phase 12 P01 35min 3 tasks 7 files
Phase 12 P02 8min 3 tasks 5 files
Phase 12 P03 15min 2 tasks 3 files
Phase 12 P04 12min 3 tasks 10 files
Phase 13 P01 35min 3 tasks 6 files
Phase 13 P02 20min 2 tasks 4 files
Phase 13 P03 45min 3 tasks 5 files
Phase 13 P06 15min 2 tasks 5 files
Phase 13 P04 55min 3 tasks 6 files
Phase 13 P05 40min 2 tasks 4 files
Phase 14 P01 13min 2 tasks 10 files
Phase 14 P02 30min 3 tasks 21 files
Phase 14 P04 25min 2 tasks 6 files
Phase 14 P05 50min 3 tasks 19 files
Phase 15 P01 24min 3 tasks 10 files
Phase 15-modul-berechtigungen-gruppen-user-grants P02 11min 2 tasks 11 files
Phase 15 P05 9min 2 tasks 5 files
Phase 15 P03 32min 3 tasks 8 files
Phase 15 P06 35min 3 tasks 8 files
Phase 15 P07 30min 3 tasks 7 files
Phase 15 P08 30min 2 tasks 12 files
Phase 16 P01 34min 3 tasks 10 files
Phase 16-ad-gruppen-synchronisation P02 5min 3 tasks 5 files
Phase 16-ad-gruppen-synchronisation P03 12min 2 tasks 3 files
Phase 16-ad-gruppen-synchronisation P04 3min 3 tasks 5 files
Phase 16 P05 6min 2 tasks 4 files
Phase 17 P01 76min 3 tasks 12 files
Phase 17 P02 58min 3 tasks 14 files
Phase 17 P03 62min 3 tasks 13 files

Accumulated Context

Roadmap Evolution

  • Phase 17 added (2026-08-12): Eigene Ausschreibungs-Quellen je Nutzer. TenderEmailConfig (heute tenantId @unique) und TenderRssFeedSource (heute url @unique, plattformweit) wandern auf userId; die Rollenpruefung faellt fuer diese beiden Abschnitte weg, das Abrufintervall der oeffentlichen Quelle bleibt Admin-Sache. Ausschreibungsdaten bleiben plattform-global (D-03 aus Phase 10 unangetastet) — geaendert wird nur, wer Quellen einspeist, nicht wer Treffer sieht. Ausloeser: Backlog 2026-08-11-tender-radar-einstellungen-mischen-rollen.md; die urspruengliche Zustimmung zur gemeinsamen Konfiguration beruhte auf einer missverstaendlichen Erklaerung.
  • Phase 15 added (2026-08-04): Modul-Berechtigungen — Gruppen & User-Grants. Zweistufiger Modulzugriff (Mandanten-Aktivierung + Grants pro Gruppe/User), Gruppen mit optionaler AD-Bindung, default geschlossen, ADMIN/SUPER_ADMIN umgehen Grants, nur Zugriff an/aus. Startet Milestone v1.2 Plattform-Berechtigungen.

Decisions

Decisions are logged in PROJECT.md Key Decisions table. Recent decisions affecting current work:

  • [Roadmap v1.1]: 5 phases (10-14) derived from 29 v1.1 requirements, standard granularity — DÖE-only MVP (10: ingestion, 11: filter/UI, 12: notifications) ships first as legally-clean demoable slice, then scraping adapters + cross-source dedup (13), then RSS + email-alert long tail (14)
  • [Roadmap v1.1]: Tender data modeled as platform-global (no tenantId on Tender) — only TenderSavedSearch/TenderMatch are tenant+user-scoped; deviates deliberately from the DKV per-tenant template
  • [Roadmap v1.1]: Scheduler must be poll-once-fan-out-many from Phase 10 onward — explicitly NOT the DKV findFirst() single-tenant pattern (documented pitfall)
  • [Roadmap v1.1]: Notification phase (12) requires an explicit matched-vs-notified state with backfill suppression to avoid first-activation email floods and duplicate digest+instant sends
  • [Roadmap v1.1]: INGEST-07 (vergabe24/aumass hard denylist) enforced in the adapter registry in Phase 13, not just documented
  • [Roadmap v1.1]: inbox/ module extraction (ImapProvider/ExchangeInboxProvider out of dkv/) scoped as a one-time prerequisite refactor inside Phase 14, not done earlier
  • [Roadmap]: 6 phases derived from 44 v1.0 requirements, standard granularity
  • [Roadmap]: Research recommends NestJS + Next.js + PostgreSQL RLS + Keycloak + Tauri stack
  • [01-01]: Used Traefik v2.11 instead of v3.4 due to Docker API version incompatibility on host
  • [01-01]: Traefik placed on frontend-net + backend-net for routing to both web and api services
  • [01-01]: API Dockerfile copies full monorepo node_modules structure for pnpm workspace compatibility
  • [01-02]: OKLCH color space for all design tokens (Tailwind v4 native, perceptually uniform)
  • [01-02]: Dark mode uses oklch(0.17 0.01 260) dark gray-blue for comfortable contrast with yellow primary
  • [01-02]: Cookie-based locale (NEXT_LOCALE) instead of URL routing for portal app
  • [01-02]: CSS custom property --current-sidebar-width for responsive main content margin
  • Phase ?: Route groups (auth)/(portal) for layout separation: auth pages standalone, portal pages wrapped in AppShell
  • Phase ?: Controller-level tenant isolation for ADMIN role as defense-in-depth alongside RLS
  • Phase ?: Remember-me controls cookie maxAge (30d session vs browser-session) not separate token type
  • Phase ?: react-grid-layout v2 uses dragConfig/resizeConfig instead of isDraggable/isResizable
  • [05-02]: SearchProvider defaults as constants merged with user DB rows (no seed migration)
  • [05-02]: useRef with explicit undefined initial value for React 19 strict TypeScript
  • [05-03]: DAVClient class constructor instead of createDAVClient factory (tsdav v2 type compatibility)
  • [05-03]: Dynamic imports for ews-javascript-api and @microsoft/microsoft-graph-client (lazy-load)
  • [05-03]: In-memory Map cache with 5-min TTL for calendar events (Redis not needed at current scale)
  • [05-03]: ews-javascript-api imported as any (no TypeScript definitions available)
  • Phase ?: Optimistic UI for calendar visibility toggle — reverts on API error
  • Phase ?: Auto-run testSource after adding new calendar source for immediate feedback
  • Phase ?: URL constructor for client-side https-only validation (T-05-14)
  • Phase ?: StoreExt trait import required for app.store() in Tauri 2.x
  • Phase ?: frontendDist=../src local page, navigate() for runtime URL override
  • Phase ?: CSP connect-src wildcard for configurable server URL (D-02)
  • Phase ?: Plain docker compose build statt build-push-action (Gitea JWT Pitfall 4)
  • Phase ?: Ephemeral runner mode (GITEA_RUNNER_EPHEMERAL=1) fuer Credential-Revokation pro Job
  • Phase ?: Separate docker-compose.ci.yml fuer opt-in CI-Infrastruktur
  • [07-01]: DKV PDF uses two extraction formats: single-tx (tab-separated) vs multi-tx (columnar) — both handled in DkvParserService
  • [07-01]: Research Pattern 4 regex replaced with empirical dual-format tab/columnar parser after testing against real invoice.pdf
  • [07-01]: CalendarCryptoService exported from CalendarModule for DKV credential encryption reuse
  • [07-02]: Max attachment size 25MB enforced in both ImapProvider and ExchangeInboxProvider before buffering (T-07-05)
  • [07-02]: ExchangeInboxProvider uses WellKnownFolderName.Inbox + FindItems (not FindAppointments — email vs calendar EWS API)
  • [07-02]: export type {} required for type-only re-exports under isolatedModules TypeScript setting
  • [07-03]: SettingsService.getStartupSmtpConfig uses findFirst (tenant-agnostic) for MailModule startup transport
  • [07-03]: MailModule forRootAsync factory priority: DB SmtpConfig → MAIL_* env → TESSERA_SMTP_* env → localhost:1025 fallback
  • [07-03]: DkvMailService injects SettingsService (not PrismaService directly) to reuse decryption logic
  • [07-03]: user-files/ path resolved via path.resolve(__dirname, 4 levels up) from apps/api/dist/dkv/ to monorepo root
  • [07-03]: Export prune sorted by mtime ascending (oldest first), delete all beyond last 10
  • [07-04]: DkvScheduler v1 uses findFirst() — single-tenant; multi-tenant scheduling deferred
  • [07-04]: Circular dep DkvService<->DkvScheduler avoided via controller coordination after PUT config
  • [07-04]: CronJob resolved via require() workaround (pnpm strict isolation: transitive dep)
  • [07-04]: rechnungsnummer from email subject regex /d{2}-d{9}-d{3}/; fallback=email-{uid}
  • [07-05]: refreshKey lift: parent increments on checkNow success; InvoiceHistoryTable reruns useEffect
  • [07-05]: onItemsLoaded callback: InvoiceHistoryTable notifies parent; parent passes items to ExportFileList (avoids second fetch)
  • [07-05]: Password blank on load: configToForm() always sets password=''; hasPassword boolean drives UX hint only
  • [07-05]: CsvImportButton replace: two-step inline confirm (not full modal); accept=".csv" client-side guard
  • Phase ?: splitCerts PEM path reuses parsePemChain; P7B sniffs first bytes
  • Phase ?: splitCerts format crt comparison removed — detectFormat returns pem|der|pfx|p7b only
  • [Phase 10]: Tender ist plattform-global (D-03): kein tenantId, keine RLS/forTenant() — Verhindert versehentliches Ausblenden globaler Daten fuer einen zweiten Mandanten
  • [Phase 10-02]: category: 'procurement' fuer Ausschreibungs-Radar im Marketplace gewaehlt (freies kebab-case, keine Enum-Beschraenkung)
  • [Phase 10-02]: Singleton doe-opendata Poll-Config wird direkt in TendersModule.onModuleInit() upserted, isActive:true per Default (D-04)
  • [Phase 10-02]: Platzhalter-Seite tender-radar/page.tsx nutzt hartkodierten deutschen Text statt next-intl (volle i18n ist CONFIG-03, Phase 14)
  • [Phase 10-03]: Real DÖE fixtures live-captured (pubDay=2026-07-19), not synthetic — 8 notices spanning all D-02 tag classes
  • [Phase 10-03]: sourceNoticeId = OCDS release.id (stable, no version suffix), not the zip entry filename
  • [Phase 10-03]: eForms-DE XML primary for deadlineAt/estimatedValue/procedureType; OCDS primary for ocid/buyerName/title/cpvCodes/region/plz
  • [Phase 10-03]: bundesland left null this plan — NUTS-to-Bundesland mapping deferred to Phase 11 filter UI
  • Phase ?: Poll-once-fan-out-many scheduler: single named cron job, no tenant parameter — deliberately drops DKV's activeTenantId/findFirst per-tenant framing (Pitfall D)
  • Phase ?: SCHEMA-02 change detection implemented via prisma.tender.upsert({ where: { dedupKey } }) — identical notice never duplicates, changed contentHash updates in place
  • Phase ?: D-05 retention as two-phase updateMany/deleteMany with deadlineAt:{lt} filters — null-deadline rows structurally excluded, never auto-expired
  • Phase ?: TendersController talks to PrismaService directly (no intermediate service layer) — source-config upsert and global read are simple enough for this plan's scope
  • Phase ?: Comment wording avoids the literal tenantId token in tenders.controller.ts to prevent false-positive grep-gate failures (same pattern as Plan 10-04)
  • Phase ?: TenderQueryDto.status defaults to active at the controller call site, not baked into the DTO, mirroring DkvController's page/limit default-at-usage pattern
  • Phase ?: Admin-Settings-Formular fuer den DOE-Poll (Intervall 5-1440, Aktiv-Toggle) spiegelt InboxConfigForm ohne Credential-Felder, da die DOE-Quelle keine Auth-Oberflaeche hat
  • [Phase 10]: Keine module-loader-Whitelist noetig fuer settings/page.tsx (verschachtelte Route unter bereits whitelisteter tender-radar-Seite)
  • Phase ?: estimatedValue kommt als String (Prisma Decimal) im JSON-Response — formatValue() im Frontend prüft explizit auf null/NaN statt zu koerzieren
  • Phase ?: openOnly/includeNullValue Default-Semantik lebt im Builder, nicht im DTO
  • Phase ?: deadlineFrom/deadlineTo URL-Param-Namen sind 1:1 identisch zu TenderQueryDto-Feldnamen fuer den Saved-Search-Vertrag aus Plan 11-06
  • Phase ?: bundesland-Filter matcht exakt gegen die befüllte, indexierte Spalte (statt region-startsWith); region bleibt als eigenständiger Präfixfilter erhalten.
  • Phase ?: BUNDESLAND_OPTIONS im Web als kleine Konstante gespiegelt (kein Shared-Package) — web nutzt @tessera/shared nicht, 16-Werte-Katalog rechtfertigt keine neue Cross-Package-Abhängigkeit.
  • Phase ?: CPV-Divisions-Kurzkatalog (2-stellig, ~45 Einträge) statt EU-Vollkatalog; hasSome-Match gegen precomputed cpvDivisions-Spalte statt Raw-SQL-Präfix-Match
  • Phase ?: cpv-URL-Param als wiederholter Key (?cpv=45&cpv=71) statt Komma-Join; DTO normalisiert Einzelwert per @Transform zu Array
  • Phase ?: TenderDetail fetcht selbstständig via getTender(tenderId) im useEffect (Muster SourceConfigForm); page.tsx bleibt reiner ?tender-Param-Reader
  • Phase ?: ResultsList.tsx modifiziert (nicht im Plan gelistet) — Rule 3: Zeilen-Klick-Handler war notwendig, um den Plan-eigenen key_link/Done-Kriterium zu erfüllen
  • Phase ?: TenderTriage (neu, per-user, kein forTenant/RLS) + favOnly-Sentinel-ID 'none' für garantierten Zero-Match statt versehentlich ungefilterter Liste
  • Phase ?: TenderSavedSearch hat keinen Tender-FK — speichert nur Filterkriterien, unkritisch bei Retention.
  • Phase ?: page/tender-URL-Params bewusst aus dem Suchprofil-Payload ausgeschlossen (Navigations-/View-State, kein Filter-State).
  • Phase ?: Single notifiedAt field (not two per-channel timestamps) as the matched-vs-notified eligibility gate (12-01, D-06)
  • Phase ?: Delta-only matching (no backfill/suppression table) structurally prevents backfill-flood for new saved-search profiles (12-01, D-07)
  • Phase ?: TenderMailService swallows missing-SmtpConfig and send-failure into a single boolean (never throws) so the digest cron gets one clean success signal for stamping notifiedAt
  • Phase ?: TenderDigestScheduler.runDigest(now) takes an injectable clock parameter for testable Monday-only weekly-digest gating
  • Phase ?: Instant-Dispatch filtert die bereits geladenen savedSearches (kein zweiter Query); notifiedAt/channel='instant' nur nach Erfolg gestempelt — identisches Gate wie Digest (D-06)
  • Phase ?: Digest-interval selector inline in settings/page.tsx (already 'use client'); instantAlert toggle uses plain checkbox for chip-based SavedSearchBar UI
  • Phase ?: SCHEMA-03 fingerprint: title+buyer dominant, CPV division, value-bucket, deadline-day, sha256; dedupKey untouched, fingerprint additive
  • Phase ?: One-time TS backfill scripts run via compiled dist/ output (not raw .ts execution) to keep tsc --noEmit clean
  • Phase ?: SourceRegistry.register() throws DeniedPortalError for any portal in DENYLISTED_PORTALS (vergabe24, aumass), enforced at DI-registration time not just documented (INGEST-07)
  • Phase ?: TenderDedupService tier-2 (source:noticeId) match is unconditional (not gated by dedupActive) — idempotent same-source re-poll must work even with a single active source
  • Phase ?: Fingerprint always computed on tender.create regardless of dedupActive, so DÖE-only tenders become fingerprint-matchable the instant a 2nd source activates without further backfill
  • Phase ?: 13-06: portalLabel()-Map bleibt lokal in TenderDetail.tsx (nicht geteilt) — einziger Consumer bisher; Fallback auf sourceUrl-Block bei fehlendem/leerem sources[].
  • Phase ?: 13-04: cheerio (nicht node-html-parser) fuer NetServer-HTML-Parsing gewaehlt — Legitimacy-Gate flaggte node-html-parser als 'too-new' (Fehlmessung: Latest-Version-Datum statt Package-Alter); cheerio bestand das Gate sauber, Human-Approval eingeholt
  • Phase ?: cosinex/DTVP-Selektoren voll befuellt statt needs-JS-deferred (D-01) — Trefferliste ist server-gerendert, live geprueft 2026-07-23
  • Phase ?: cosinex/DTVP liefert echte Notice-Deep-Links (pid=) — besser als NetServer-Fallback (Such-URL); Rule-1-Fix: arrayBuffer()+TextDecoder('iso-8859-1') statt res.text(), da Portal ISO-8859-1 mit rohen Latin-1-Bytes sendet
  • Phase ?: [quick-260723-e7i]: TenderNormalizerService dispatcht per sourceType (normalizeDoe/normalizeBag) mit geteiltem assemble()-Tail — schliesst den Normalizer-Gap aus 13-VERIFICATION.md fuer NetServer/Cosinex-Bag-Records
  • Phase ?: 14-01: DKV inbox providers moved verbatim into shared apps/api/src/inbox/ module; dkv.types.ts re-exports InboxConfig/InboxAttachment/InboxEmail so no DKV consumer import changed (D-01)
  • Phase ?: 14-01: fetchMessages() added as a sibling method (findBodyParts/getItemBodySoap) on both providers without touching fetchPdfAttachments (D-02); DKV not switched to it
  • Phase ?: 14-01: httpntlm loaded via raw require() bypasses vi.mock — tests seed require.cache with a stub before dynamically importing the provider
  • Phase ?: 14-02: fast-xml-parser does not decode numeric HTML entities (Ü) — added explicit decodeNumericEntities() in RssAdapter so service.bund.de titles render correctly
  • Phase ?: 14-02: TenderRssFeedSource save-time hostname/SSRF guard is a SEPARATE enforcement point from the code-level SourceRegistry denylist (RSS feed URLs are runtime admin input, not covered by the DI-boot-time gate)
  • Phase ?: 14-02: TenderSourcePollConfig.pollGranularity ('day'|'tick') added — 'day' sources keep the byte-unchanged lastIngestedDay gate, 'tick' sources (rss) fetch every active scheduler tick (D-15)
  • Phase ?: 14-02: seeded service.bund.de active-by-default RSS feed; zero subreport-elvis rows (no single canonical URL, admin adds relevant municipality feeds)
  • Phase ?: 14-03: D-13 read filter fails CLOSED for an unresolved requesting tenant (no auth context) — only global tenders visible, never a private-tenant leak
  • Phase ?: 14-03: email-alert TenderSourcePollConfig seeded isActive=false (no safe default mailbox, unlike RSS's service.bund.de) — framework-ready-activation-deferred
  • Phase ?: PORTAL_URLS typed as Record<(typeof DENYLISTED_PORTALS)[number], string> so the compiler enforces a URL for every denylisted portal (no re-declared set, no silent gap)
  • Phase ?: CoverageBanner's denylist block is independent of the onlyDoe coverage-note condition — component renders when either block has content, not gated behind the DOE-only check
  • Phase ?: 14-05: tenderRadar i18n namespace added; Bundesland/CPV filter option values stay canonical German for backend compatibility, only labels translated; portal display slugs left untranslated as proper nouns
  • Phase ?: [260728-lih]: DELIBERATE back-compat break — empty groupFilterDns now means 'sync nothing' (was 'import everyone under baseDn'); early-return guard in syncUsersForTenant runs before search/deactivation so an empty selection never mass-deactivates existing LDAP users
  • Phase ?: [260729-d3k]: syncUsersForTenant no-op guard re-keyed from empty groupFilterDns to empty parsed base-DN list — Base-DN(s) are now the sync scope, groupFilterDns is an optional extra restriction (ou= = extra bases, group DN = memberOf constraint)
  • Phase ?: [15-01]: D-01/D-05/D-06/D-02 wie in 15-CONTEXT.md gesperrt umgesetzt (Nutzer-Checkpoint mit 'proceed' bestaetigt)
  • Phase ?: [15-01]: RLS fuer Group/GroupMembership/ModuleGrant aktiviert (T-15-11) statt sie wie Tender* RLS-frei zu lassen
  • Phase ?: [15-02]: isDefault:true läuft in this.prisma.$transaction([updateMany, update]); partieller Unique-Index aus 15-01 bleibt Sicherheitsnetz
  • Phase ?: [15-02]: remove() fängt zusätzlich P2025 ab (NotFoundException statt unbehandeltem 500) — Rule 2, für Concurrency-Anforderung aus must_haves
  • Phase ?: [15-02]: UserService.create ist die einzige Codestelle für D-11/D-12 — LdapService erbt die Regel ohne eigene Kopie (ldap.service.ts unverändert)
  • Phase ?: [15-05]: WIDGET_MODULE_MAP bleibt am Ende dieser Phase bewusst leer (D-22) — kein neuer Widget-Typ, keine Schemaänderung, nur die Filtermechanik
  • Phase ?: [15-05]: vi.hoisted() für die je-Testfall mutierbare WIDGET_MODULE_MAP-Mock-Referenz — vi.mock wird an den Dateianfang gehoben, ein normaler top-level const wäre zur Factory-Ausführungszeit noch nicht initialisiert
  • Phase ?: [15-03]: assertTargetBelongsToTenant als eigenständige Cross-Tenant-Prüfung eines referenzierten Fremdobjekts vor jedem Grant-Insert (T-15-01), kein Vorbild im Bestandscode
  • Phase ?: [15-03]: Kein Import von ModuleRegistryModule in GroupsModule — ModuleGrantsService injiziert ausschließlich PrismaService
  • Phase ?: [15-03]: getCatalogFlags liefert Map nur für aktive Module, Controller mappt fehlenden Eintrag auf beide Flags false
  • Phase ?: [15-06]: Task 2/3-Split von page.tsx haelt jeden Task-Commit fuer sich buildbar (Task 2 ohne Import der erst in Task 3 entstehenden Komponenten)
  • Phase ?: [15-06]: Gruppen-Erstellung mit sofortiger AD-Bindung laeuft zweistufig (POST /groups, dann PATCH ldapDn), weil CreateGroupDto aus 15-02 nur name entgegennimmt
  • Phase ?: [15-06]: matrixCheckboxLabel/directCheckboxLabel (Wave-4-Schluessel) als next-intl-ICU-select mit granted-Parameter modelliert, ein Schluessel bedient freigeben/entziehen
  • Phase ?: [15-07]: aria-label des Grant-Checkboxes beschreibt die vom Klick ausgeloeste Aktion (granted: String(!isGranted)), nicht den aktuellen Haekchen-Zustand
  • Phase ?: [15-07]: UserAccessModal leitet Gruppenmitgliedschafts-Chips ausschliesslich aus der Vereinigung aller viaGroups-Namen von GET /module-grants/users/:userId ab, kein zweiter Endpoint
  • Phase ?: [15-07]: ActivateModuleDialog ruft onSuccess bereits nach dem erfolgreichen activate-Call auf, unabhaengig vom Ausgang des nachfolgenden module-grants-Calls
  • Phase ?: [15-08]: isAdmin-Gate auf /marketplace und /marketplace/[slug] entfernt (D-08: Katalog bleibt Schaufenster fuer jeden authentifizierten Benutzer) — isAdmin gated jetzt nur noch die Aktivieren/Deaktivieren-Aktion (canManage-Prop)
  • Phase ?: [15-08]: MarketplaceCard-Klick delegiert an getrennte onOpenDetail/onLockedClick-Callback-Props statt eigener Router-Logik in der Karte — bleibt praesentational und ueber vi.fn() testbar
  • Phase ?: [quick-260805-fok]: ensureDefaultGroup-Waechter prueft ausschliesslich group.count === 0, nie die fehlende isDefault-Markierung (D-13); Reparatur laeuft als zweiter sequenzieller await-Schritt in AdminSeedService.onApplicationBootstrap statt als eigener Hook in GroupsModule (Ordering-Falle wie in tender-scheduler.service.ts)
  • Phase ?: Checkpoint 1 (16-01 Task 1): approve-both — Group.internalName + ldapObjectGuid + Unique-Index in einer Migration, freigegeben 2026-08-06
  • Phase ?: Migrationsverfahren angepasst: prisma migrate dev verweigert nicht-interaktive Shell — Ersatz via migrate diff + Handdatei + migrate deploy, inkl. Baseline der 24 Altmigrationen per migrate resolve --applied
  • Phase ?: [16-02]: reassignDefaultBeforeDelete() nutzt bewusst nicht findOwned() — eigenes still-false-Muster fuer Batch-Sync-Laeufe (D-06), kein NotFoundException-Abbruch
  • Phase ?: [16-02]: Namenssperre fuer importierte Gruppen (D-03) ist eine Backend-Invariante in GroupsService.update() (BadRequestException), nicht nur ein UI-Disable
  • Phase ?: [16-02]: PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] — dieser Plan liefert nur den Backend-Teil, das Requirement schliesst erst mit Plan 16-05
  • Phase ?: [16-03]: syncBoundGroupsForTenant() als eigene, in Task 1 noch unverdrahtete Methode gebaut; Task 2 liefert ausschliesslich die Verdrahtung als Schritt 5a vor 5b samt Call-Order-Test — Reihenfolge ist die zentrale Korrektheitsbedingung der Phase
  • Phase ?: [16-03]: A1/A2-Live-Pruefung gegen ViCoTest nicht durchfuehrbar (kein erreichbares AD in dieser Sandbox) — als WINDOWS.md #4 (unrun-verify) festgehalten, negatives Ergebnis ist Stopp-Grund fuer die D-05-Loeschsemantik
  • Phase ?: [16-03]: PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] — schliesst erst mit Plan 16-05
  • Phase ?: [16-04]: Group.internalName vorgezogen von Task 2 nach Task 1 (Rule 3) - GroupFormModal.tsx kompiliert sonst nicht
  • Phase ?: [16-04]: PERM-02 bleibt in REQUIREMENTS.md bewusst auf [ ] - schliesst erst mit Plan 16-05
  • Phase ?: [16-05]: PERM-02 in REQUIREMENTS.md auf [x] gesetzt - alle 5 Phase-16-Erfolgskriterien code-vollstaendig ueber 16-01..16-03; dieser Plan liefert die Sichtbarkeitsschicht (Sync-Bericht D-05/D-06) und die dritte D-04-Anzeigestelle (Freigabe-Matrix)
  • Phase ?: [16-05]: A1/A2-Live-Pruefung gegen echtes AD (WINDOWS.md #4) bleibt trotz PERM-02-Abschluss offen - Korrektheitsannahme unter SC-3/SC-4, kein eigenes Erfolgskriterium; negatives Ergebnis waere Stopp-Grund fuer D-05-Loeschsemantik
  • Phase ?: [17-01]: Checkpoint 1 (gate=blocking) 'weiter' — beide Datenbank-Umbauten der Phase freigegeben, gestuetzt auf gemessene 0 Bestandszeilen (lokal + alpha)
  • Phase ?: [17-01]: TenderEmailConfig.userId @unique ersetzt tenantId @unique (D-01); tenantId bleibt denormalisiert, wird auf create UND update mitgeschrieben
  • Phase ?: [17-01]: email-config-Routen von @Roles(ADMIN,SUPER_ADMIN) auf @UseModule('tender-radar') umgestellt — Postfach ist jetzt Nutzereinstellung (D-01, T-17-06 accept)
  • Phase ?: [17-01]: eigene Seite /modules/tender-radar/my-sources statt Erweiterung von /settings/general/account (D-01 offener Punkt 4)
  • Phase ?: [17-02]: Checkpoint-Freigabe aus 17-01 deckte diese Migration bereits ab, kein zweiter Halt (gemessene 1 Bestandszeile blieb plattformweit)
  • Phase ?: [17-02]: Startbestueckung (tenders.module.ts) vorgezogen aus Task 3 nach Task 1 (Rule 3) - find-then-create statt upsert-on-url, Prismas Compound-Unique-Typ verlangt userId als Pflicht-String
  • Phase ?: [17-02]: seedServiceBundRssFeed() aus TendersModule.onModuleInit extrahiert (tenders.seed.ts), damit die Bestueckungs-Idempotenz echten Produktivcode testet
  • Phase ?: [17-02]: DELETE /rss-feeds/:feedId von @Roles auf @UseModule umgestellt - Besitzpruefung im Dienst ersetzt die Rollenpruefung vollstaendig (T-17-07)
  • Phase ?: [17-03]: RssFeedListForm scope-Prop (personal/platform) bedient beide Seiten; Berechtigung entscheidet der Server (T-17-08)
  • Phase ?: [17-03]: createRssFeed-Antwort traegt kein isPlatformWide (nur GET mappt es) — Komponente leitet es lokal aus dem verwendeten scope ab (Rule 1)
  • Phase ?: [17-03]: Anzeige-Rollenpruefung auf settings/page.tsx ueber useAuthStore (unbekannt/erlaubt/verweigert); verbindliche Pruefung bleibt serverseitig
  • Phase ?: [17-03]: REQUIREMENTS.md SRC-01..05 nachtraeglich ergaenzt — Luecke aus 17-01/17-02, dort schon in SUMMARY-Frontmatter gefuehrt

Pitfalls & Anti-Patterns

Lehren aus abgeschlossenen Sitzungen. Gerettet aus den Checkpoint-Dateien (.continue-here.md), die am 2026-09-07 entfernt wurden.

Muster Beschreibung Schwere Vermeidung
Tautologischer Test Der Test der objectGUID-Suche baute seinen Erwartungswert mit derselben Hilfsfunktion, die der Produktionscode benutzte. Er bestaetigte damit, dass die Funktion zu sich selbst passt — nicht, dass ein Verzeichnis den Filter versteht. Der Fehler ueberlebte Review und 500+ Unit-Tests und haette beim ersten echten Sync alle AD-gebundenen Gruppen geloescht. blocking Tests gegen externe Systeme muessen die Form des Aufrufs pruefen (Buffer statt String, Objekt statt interpoliertem Text), nicht seinen mit Produktionscode erzeugten Inhalt. Siehe ldap.service.spec.ts, die beiden Tests am Ende des syncBoundGroupsForTenant-Blocks.
HTTP 200 als Funktionsbeleg oeffentlichevergabe.de/ui/... ist eine Single-Page-App und antwortet auf JEDE Kennung mit 200 und identischen 1309 Bytes, auch auf NONSENSE123. Ein Statuscode-Test haette "funktioniert" gemeldet. advisory Bei SPA-Zielen den gerenderten Inhalt pruefen (Playwright), nie den Statuscode.
Image-Datum als Aktualitaets-Beleg Das Web-Image trug den 7. August und sah veraltet aus. Tatsaechlich hatten sich die Web-Quellen seither nicht geaendert; Docker-Layer-Caching erzeugt ein bit-identisches Image mit altem Erstellungsdatum. advisory Vor einer Aussage ueber Rueckstand git log -- apps/web pruefen, nicht das Image-Datum.
Server-Compose ist kein Checkout /opt/tessera ist keine Git-Arbeitskopie. Aenderungen an Compose-Dateien im Repository kommen dort nie an; der Deploy holt nur Images. blocking Aenderungen an docker-compose*.yml wirken NICHT auf alpha. Was dort gelten soll, muss zusaetzlich in /opt/tessera/docker-compose.yml eingetragen werden (erlaubt, mit Sicherung). Siehe Backlog 2026-08-11-compose-datei-auf-server-driftet.md.
Stale live container maskiert Fertigstellung Code, Migration und Tests waren gruen, aber der laufende tessera-ctl-api-1 lief auf dem Image von vor der Phase. Niemand hatte je einen echten HTTP-Aufruf gegen die neuen Routen gesehen. Das war kein Code-Defekt, sondern eine unbeobachtete Fertigstellung. advisory Container neu bauen (docker compose up -d --build api web), bevor ein "Tests gruen"-Zustand als nutzersichtbar fertig gilt. Gilt fuer die lokale Entwicklungsmaschine; auf dem Testserver macht der User das selbst.

Infrastruktur-Stand (alpha, Stand 2026-08-11)

Gerettet aus .continue-here.md. Relevant fuer die noch offenen Live-Tests.

  • alpha (192.168.13.12, https://alpha.tessera.ctl.de): API-Image mit allen Fixes; Migrationen 20260811120000_doe_notice_url_backfill und 20260811140000_encrypt_ldap_bind_password angewandt.
  • /opt/tessera/.env: traegt TESSERA_ENCRYPTION_KEY UND den alten CALENDAR_ENCRYPTION_KEY mit gleichem Wert. Sicherung .env.bak.20260811. Die alte Zeile kann weg, sobald kein Rueckfall auf aeltere Images mehr denkbar ist.
  • /opt/tessera/docker-compose.yml: von Hand um die neue Variable ergaenzt, Sicherung docker-compose.yml.bak.20260811. Driftet vom Repository ab.
  • Testdaten auf alpha: 405 Benutzer und die AD-gebundene Gruppe Claude_VT (9 Mitglieder) aus dem Sync vom 2026-08-11. Der User wirft vor dem Go-live ohnehin alles raus.
  • Deploy-Regel: pull/up/restart macht der User. Konfigurationsdateien auf dem Server darf Claude bearbeiten (seit 2026-08-11), vorher sichern.
  • Kein AD-Schreibzugriff (Stand 2026-08-11): Phase-16-UAT 1/3/4 waren deshalb nicht ausfuehrbar und wurden als dokumentierte Annahme geschlossen. Betrifft direkt den offenen Live-Test WINDOWS #4 — dessen Annahme A1 (objectGUID uebersteht Umbenennung) verlangt eine echte Umbenennung im Verzeichnis, also Schreibrechte. Vor dem Live-Test klaeren, ob diese inzwischen vorliegen.

Pending Todos

None yet.

Blockers/Concerns

  • [Roadmap v1.1]: DÖE OpenData API pagination/rate-limit parameters unverified (Swagger UI is JS-rendered) — resolve via a live API call during Phase 10 planning, not assumed from docs.
  • [Roadmap v1.1]: Whether AI-AG NetServer / cosinex VMP search pages require JS rendering is unverified — needs a Phase 13 start-of-phase spike before committing to playwright.
  • Phase 14 Plan 03 (14-03): Task 4 human-verify OPEN — needs a real portal-alert mailbox (incl. Exchange/EWS live path) from the operator before INGEST-05's Exchange path is production-ready. Tasks 1-3 complete and committed (4d6fbb1, 8983231, 1be6b15, 48e1252); API 387/387, web 144/144 green.
  • Phase 15 Plan 06 (15-06): manueller Browser-Durchklick nicht ausgefuehrt — ERLEDIGT 2026-09-07, Gegenprobe WINDOWS.md unrun-verify #1 nachgeholt und bestanden

Quick Tasks Completed

# Description Date Commit Directory
260630-gbh User Settings: Passwort ändern (nur non-LDAP) + Profilbild setzen 2026-06-30 merge 260630-gbh-user-settings-passwort-ndern-nur-non-lda
260701-abc Fix i18n: marketplace.accessDenied + calendar form hardcoded EN strings 2026-07-01 e5b76b7 260701-abc-i18n-missing-keys
260707-csw LDAP AD Anbindung: Zugangsdaten aus XWiki vorbefuellen und Import-Filter fuer Benutzer/Gruppen 2026-07-07 a5c500d 260707-csw-ldap-ad-anbindung-zugangsdaten-aus-xwiki
260707-lgh Favoriten-Widget: Icon-Proxy fuer Cross-Origin-Resource-Policy-Seiten (claude.ai) 2026-07-07 f06a2ff 260707-lgh-favoriten-widget-icon-proxy-fuer-cross-o
260708-cuc Fix: FavoriteLink-Tabelle fehlt in Prod-DB, nie als Migration committed (500 auf GET /favorites) 2026-07-08 afef9b2 260708-cuc-fix-favoritelink-tabelle-fehlt-in-prod-d
260708-rev LDAP: CTL-spezifisches AD-Prefill entfernt (Multi-Tenant, "das war nie das Ziel") 2026-07-08 8e8305c (direct)
260708-tst LDAP: Verbindung testen vor dem Speichern einer Config moeglich 2026-07-08 39aa4bf (direct)
260709-abd LDAP: anonymous bind (bindDn/bindPassword optional, Schema nullable + Migration) 2026-07-09 010aceb (direct)
260709-ciu Auth: Benutzernamen ueberall case-insensitive (Login, Seed, LDAP-Sync + Daten-Migration) 2026-07-09 baff7ce (direct)
260709-lda LDAP: ldapts empty-array-Attribut-Bug (E-Mail-Kollision auf Unique-Constraint) 2026-07-09 246dc89 (direct)
260709-sbx LDAP: Suchbox fuer die entdeckten Gruppen/OUs-Liste 2026-07-09 aaa2922 (direct)
260714-lex LDAP: Per-User Exclude/Denylist-Filter (Service-Accounts vom Sync ausschliessen) — live verifiziert: deaktiviert 4 Accounts, 2 echte User aktiv 2026-07-14 9d1323f (direct)
13 Normalizer-Gap Phase 13 schliessen: NetServer/Cosinex-Bag-Dispatch (TenderNormalizerService) 2026-07-23 9881005 —
260728-lih LDAP: Sync strikt selektiv (leere Auswahl = No-Op statt Voll-Import) + Auto-Sync-Default aus (syncIntervalMin 60→0) 2026-07-28 c54e424,57bc7f9,63a07ab 260728-lih-ldap-sync-selektiv-und-auto-sync-default
260729-d3k LDAP: Multi-Base-DN und Base-DN als Sync-Scope (statt leerer Gruppenfilter = No-Op) 2026-07-29 5cbd530,96be7e1 260729-d3k-ldap-multi-base-dn-und-base-dn-als-scope
260805-d0r Benutzer-Detail zeigte Gruppenmitgliedschaften aus Freigaben statt aus Mitgliedschaften — GET /module-grants/users/:userId liefert jetzt { groups, modules }, Chips mit Herkunfts-Badge; im Browser gegengeprüft: Mitgliedschaft bleibt sichtbar, auch wenn die Gruppe kein Modul freigibt 2026-08-05 ecadf69,f8ff74b,8ce3748 260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch
260811-enc LDAP-Bind-Passwort war das einzige Zugangsdatum im Klartext in der DB. Jetzt AES-256-GCM ueber den bestehenden CalendarCryptoService, Spalte umbenannt zu encryptedBindPassword, Entschluesselung zentral in getConfig()/getAllActiveConfigs(), idempotenter Bootstrap-Backfill fuer Altbestand. Falscher Schluessel wirft, statt still "kein Passwort" zu liefern (sonst wuerde aus einem authentifizierten Bind unbemerkt ein anonymer) 2026-08-11 4f687ea (direct)
260811-j04 DOE-Ausschreibungen verlinkten auf die API (rohes JSON) statt auf die Bekanntmachung — betraf Trefferliste, Detailansicht und Alarm-Mails. Adapter baut die URL jetzt aus der Bekanntmachungsnummer, Migration schreibt 2846 bestehende Zeilen in Tender und TenderSource um. Zielseite im Browser fuer beide Kennungsformen verifiziert (numerisch und UUID) — ein HTTP-Statuscheck taugt dort nicht, die Seite antwortet auf jede Kennung mit 200 2026-08-11 ecf7872 260811-j04-doe-tender-links-point-to-the-api-instea
260811-f9i KRITISCH: objectGUID-Existenzpruefung fand nie etwas — der Filter wurde als \xx-escapter String gebaut, ldapts wandelt das nicht in Rohbytes; beide Suchen (Base-DNs und WR-03-Fallback) teilten ihn, also haette der erste echte Sync JEDE AD-gebundene Gruppe samt Mitgliedschaften und Modulfreigaben geloescht. Jetzt EqualityFilter ueber den rohen Buffer, escapeLdapFilterBuffer() entfernt. Read-only am echten AD gemessen (escapter String 0 Treffer, EqualityFilter 1 korrekter Treffer); Regressionstests gegengeprueft (alter Code = 8 rote Tests) 2026-08-11 d2019dc 260811-f9i-fix-objectguid-existence-sweep-to-use-eq
260805-fok Standardgruppe bei Mandanten-Anlage + Startup-Reparatur — GroupsService.ensureDefaultGroup(tenantId) mit D-13-Waechter (null Gruppen, nicht fehlende Markierung), verdrahtet in TenantService.create und AdminSeedService.ensureDefaultGroupsForAllTenants; schliesst die Migrations-Backfill-Luecke auf frischen Installationen (Testserver: tenants=1 users=4 groups=0) 2026-08-05 9d1254c,0d7d8a5 260805-fok-standardgruppe-bei-mandanten-anlage-und-
21 Verschluesselungsschluessel in den Beispiel-Umgebungsdateien dokumentiert: .env.example hatte gar keinen Eintrag, .env.prod.example nannte noch den alten Namen CALENDAR_ENCRYPTION_KEY. Compose-Teil des Backlog-Punkts war bereits mit 7bda56d erledigt (Vorgabewert raus, :?-Abbruch statt Ersatzwert) 2026-08-11 379606e —

Deferred Items

Items acknowledged and carried forward from previous milestone close:

Category Item Status Deferred At
Live-Test AD WINDOWS #4 — A2 am 2026-09-07 gegen das echte AD belegt (EqualityFilter ueber rohen Buffer: 1 Treffer, escapter String: 0 Treffer; Bericht 16-LIVETEST-2026-09-07.md). A1 (objectGUID uebersteht Umbenennung) weiterhin offen — braucht Schreibzugriff im Verzeichnis teilweise belegt, A1 offen 2026-09-07
Live-Test AD WINDOWS #6 — Teil (a) drei Zahlenzeilen und Teil (c) Spaltensuche unter beiden Namen am 2026-09-07 auf alpha bestanden. Teil (b) Amber-Zeile weiterhin offen — nur ausloesbar, wenn eine AD-gebundene Gruppe im Verzeichnis wirklich verschwindet (Schreibzugriff noetig; ueber den Suchbereich nicht nachstellbar, das verhindert die WR-03-Weitsuche zu Recht) teilweise bestanden, (b) offen 2026-09-07
Live-Test E-Mail WINDOWS #12 — echtes Portal-Alert-Postfach ueber Exchange/EWS anbinden und eine echte Alarm-Mail ingestieren; der handgeschriebene NTLM/SOAP-Weg ist bisher nur gegen Mocks geprueft. Stand 2026-09-07: auf alpha ist kein Postfach hinterlegt (TenderEmailConfig leer) — der Test kann erst starten, wenn Postfachadresse, EWS-Adresse und Zugangsdaten vorliegen offen, wartet auf Zugangsdaten 2026-09-07

Entscheidung des Users vom 2026-09-07 zur Mandantenfaehigkeit: Tessera wird zunaechst nur intern eingesetzt. Die Mandantentrennung ist damit vorerst zweitrangig — sie bleibt in der Architektur verankert und wird nicht zurueckgebaut, aber ihre Abnahme hat keine Dringlichkeit. Konkret betrifft das den offenen Abnahmeplan 02-05 (Phase 2), dessen Erfolgskriterium 4 die Datentrennung zweier Mandanten im Browser nachweisen soll: bewusst zurueckgestellt, nicht vergessen. Vor einem Einsatz bei externen Kunden ist er nachzuholen.

Entscheidung des Users vom 2026-09-07: Diese drei Punkte werden spaeter am Live-System geprueft, nicht in der Entwicklungsumgebung. Sie bleiben im Ledger ausdruecklich open und sind NICHT auf waived gesetzt — sie sollen nachgeholt werden, nur eben dort, wo ein echtes Verzeichnis und ein echtes Postfach erreichbar sind. Kein Anlass, sie vorher erneut vorzulegen.

Session Continuity

Last session: 2026-09-07T14:50:00.000Z Stopped at: Live-Test gegen das echte AD durchgefuehrt. Belegt: WINDOWS #4/A2 und WINDOWS #6 Teile (a) und (c). Offen bleiben #4/A1 und #6/(b) — beide brauchen Schreibzugriff im Verzeichnis — sowie #12, das ein Exchange-Postfach samt Zugangsdaten braucht. Zwei neue Defekte gefunden und im Ledger erfasst (#14 Matrix-Suche leert die jeweils andere Achse, #15 rohe Techniktexte im Sync-Ergebnis). Bericht: .planning/phases/16-ad-gruppen-synchronisation/16-LIVETEST-2026-09-07.md Resume file: None Last activity: 2026-09-07 - Live-Test AD auf alpha; #4/A2 und #6 (a)+(c) bestanden, zwei neue Defekte erfasst