diff --git a/.gitignore b/.gitignore
index fc6050e..e2e09ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,6 @@ user-files/
# GSD research cache
.planning/research/.cache/
+
+# GSD runtime scratch (Dispatch-Sentinel, pro Sitzung neu geschrieben)
+.gsd/
diff --git a/.planning/.continue-here.md b/.planning/.continue-here.md
deleted file mode 100644
index af1d967..0000000
--- a/.planning/.continue-here.md
+++ /dev/null
@@ -1,121 +0,0 @@
----
-context: default
-phase: 16-ad-gruppen-synchronisation
-task: null
-total_tasks: 0
-status: between-milestones
-last_updated: 2026-08-11T12:59:01.215Z
----
-
-## Critical Anti-Patterns
-
-| Pattern | Description | Severity | Prevention Mechanism |
-|---------|-------------|----------|---------------------|
-| 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 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`. |
-
-
-v1.2 Plattform-Berechtigungen ist inhaltlich fertig. Phase 15 steht auf 8/8,
-Phase 16 auf 5/5, beide in ROADMAP.md und STATE.md nachgezogen. Kein Phase-Work
-in Arbeit, Arbeitsverzeichnis sauber, alles auf origin/main (`f38b9f2`).
-
-Die Sitzung war UAT-getrieben: der Browser-Durchlauf zu Phase 16 hat einen
-kritischen Fehler in der Loescherkennung gefunden, der ohne den einen
-ausfuehrbaren Test unbemerkt in Produktion gegangen waere.
-
-
-
-
-- Phase-16-UAT: Tests 5, 6, 7 im Browser bestanden; Test 2 read-only gegen das
- echte AD; Tests 1, 3, 4, 8 auf Entscheidung des Users uebersprungen
-- **KRITISCH behoben** (`d2019dc`): objectGUID-Existenzpruefung baute ihren
- Filter als escapten String; ldapts wandelt das nicht in Rohbytes. Beide
- Suchen der Pruefung teilten sich den Filter, damit haette der erste echte
- Sync jede AD-gebundene Gruppe samt Mitgliedschaften und Modulfreigaben
- geloescht. Jetzt EqualityFilter mit rohem Buffer, am echten AD gemessen.
-- DOE-Bekanntmachungslinks repariert (`ecf7872`) inkl. Backfill von 2846 Zeilen
-- Fehlender Abschlussbericht 15-04 nachgezogen (`149b5aa`) — Phase 15 damit 8/8
-- LDAP-Bind-Passwort verschluesselt (`4f687ea`), Stack startet nicht mehr ohne
- Schluessel (`7bda56d`), Schluessel umbenannt mit Rueckfallebene (`f574884`)
-- Vier Backlog-Punkte geschrieben, zwei alte geschlossen
-
-
-
-
-Vier Backlog-Punkte, alle unter `.planning/todos/pending/`:
-
-1. `2026-08-11-modulaktivierung-ohne-lizenzpruefung.md` — gross, Produktfragen offen
-2. `2026-08-11-tender-radar-einstellungen-mischen-rollen.md` — mittel, Grundsatzfrage offen
-3. `2026-08-11-compose-datei-auf-server-driftet.md` — mittel, Weg offen
-4. `2026-08-11-verschluesselungsschluessel-vorgabewert.md` — klein, ohne Rueckfrage machbar
-
-Offen ausserdem: ob nach v1.2 ein neuer Meilenstein geplant wird.
-
-
-
-
-- Phase 16 mit A1 (objectGUID uebersteht Umbenennung) als offener, dokumentierter
- Annahme abgeschlossen — der User hat keinen AD-Schreibzugriff, und der eine
- substanzielle Fehler war bereits gefunden
-- Ein Plattform-Schluessel statt eines eigenen fuer das AD-Bind-Passwort: zwei
- Schluessel in derselben .env auf demselben Host erhoehen die Sicherheit nicht
-- Alter Schluesselname bleibt lesbar mit Veraltet-Warnung — ein harter Rename
- haette jede bestehende Installation gestoppt
-- Verschluesselungs-Backfill zur Laufzeit statt in der Migration: SQL kann nicht
- verschluesseln, der Schluessel liegt in der Anwendungsumgebung
-
-
-
-- Kein AD-Schreibzugriff: UAT 1/3/4 der Phase 16 sind dort dauerhaft nicht
- ausfuehrbar. Als Annahme dokumentiert, kein offener Arbeitsauftrag.
-- `.env.example` / `.env.prod.example` waren in der Sitzung durch Berechtigungen
- gesperrt — die Dokumentation des Schluessels haengt daran.
-
-
-## Required Reading (in order)
-
-1. `.planning/STATE.md` — Position, Quick-Task-Tabelle mit den Fixes dieser Sitzung
-2. `.planning/phases/16-ad-gruppen-synchronisation/16-UAT.md` — Abschnitt
- "Entscheidung 2026-08-11", warum vier Tests uebersprungen sind
-3. `.planning/quick/260811-f9i-*/SUMMARY.md` — der kritische Fund und warum er
- durch Review und Tests kam
-4. `.planning/todos/pending/` — die vier offenen Punkte
-
-## Infrastructure State
-
-- **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. Web-Image vom 7.8. —
- korrekt, die Web-Quellen sind seit dem 6.8. unveraendert.
-- **`/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 11.8. 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), mit Sicherung vorher.
-
-
-Der rote Faden der Sitzung war, dass genau ein ausfuehrbarer Test (UAT 2, weil
-read-only) den einen Fehler gefunden hat, den alle Reviews und 500+ Unit-Tests
-durchgelassen hatten. Die uebersprungenen Tests sind bewusst uebersprungen, nicht
-vergessen — die Begruendung steht in 16-UAT.md.
-
-Die vier Backlog-Punkte sind allesamt "vor dem Verkauf an externe Kunden".
-Intern draengt keiner davon. Drei brauchen zuerst Entscheidungen des Users, der
-vierte ist reine Umsetzung.
-
-
-
-Mit dem User klaeren, was drankommt: einer der vier Backlog-Punkte oder die
-Planung eines neuen Meilensteins nach v1.2. Ohne seine Entscheidungen sind die
-drei groesseren Punkte nicht sinnvoll zu starten — der kleine
-(Vorgabewert entfernen, Schluessel in den Beispiel-Umgebungsdateien
-dokumentieren) laesst sich sofort umsetzen, sobald der Dateizugriff auf die
-`.env*`-Vorlagen moeglich ist.
-
diff --git a/.planning/STATE.md b/.planning/STATE.md
index a08e86c..d0f4166 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -5,10 +5,10 @@ milestone_name: Plattform-Berechtigungen
current_phase: 17
current_phase_name: eigene-ausschreibungs-quellen-je-nutzer
status: verified
-stopped_at: "Sitzung sauber beendet, alles auf origin/main (8629889), Arbeitsbaum leer. Erledigt: Phase 17 auf passed; alte Browser-Gegenproben #1,#2,#3,#5 nachgeholt; zwei echte Defekte behoben (#10 Zugriffs-Guard griff nicht auf modul-eigenen Routen, #11 Fingerabdruck-Dedup war wirkungslos) plus #13 (Tailwind-Dunkelvariante nie an .dark gebunden, ueber 100 dark:-Angaben ohne Wirkung); Logo und Favicon eingebaut; Umlaute, Anrede und Ein-/Mehrzahl korrigiert; Meilenstein-Buchhaltung auf v1.2 abgeschlossen. Offen: WINDOWS #4/#6 (echtes AD), #12 (echtes Exchange) — alle als Deferred Items fuer den Live-Test vorgemerkt; Backlog: Mandanten-Branding."
-last_updated: "2026-09-07T12:30:00.000Z"
+stopped_at: "Aufraeumen abgeschlossen. Zwei veraltete .continue-here-Dateien entfernt, ihre noch gueltigen Inhalte (5 Anti-Patterns, Infrastruktur-Stand alpha, Blocker 'kein AD-Schreibzugriff') nach STATE.md Accumulated Context ueberfuehrt; .gsd/ in .gitignore. Naechster Schritt: Live-Tests WINDOWS #4, #6 (echtes AD) und #12 (echtes Exchange-Postfach). Achtung: #4 braucht AD-Schreibrechte, die am 2026-08-11 noch fehlten."
+last_updated: "2026-09-07T14:40:00.000Z"
last_activity: 2026-09-07
-last_activity_desc: Sitzung abgeschlossen — Phase 17 zu, zwei Defekte behoben, Logo eingebaut, Sprache bereinigt, Meilenstein-Buchhaltung korrigiert
+last_activity_desc: Checkpoint-Aufraeumen — veraltete .continue-here-Dateien entfernt, Pitfalls und Infrastruktur-Stand nach STATE.md gerettet
progress:
total_phases: 17
completed_phases: 17
@@ -289,6 +289,42 @@ Recent decisions affecting current work:
- [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.
@@ -298,7 +334,7 @@ None yet.
- [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 aus dem Plan-Verification-Block nicht ausgefuehrt (kein Browser-Tool in dieser Session) — vor /gsd-ship nachholen, siehe WINDOWS.md unrun-verify #1
+- ~~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
@@ -352,7 +388,7 @@ sind. Kein Anlass, sie vorher erneut vorzulegen.
## Session Continuity
-Last session: 2026-09-07T12:30:00.000Z
-Stopped at: Nichts in Arbeit. Naechster Schritt frei waehlbar — Mandanten-Branding waere der naechste Bau-Kandidat.
+Last session: 2026-09-07T14:40:00.000Z
+Stopped at: Aufraeumen erledigt (veraltete Checkpoint-Dateien entfernt, ihr Wissen nach STATE.md gerettet, .gsd/ ignoriert). Als naechstes: die drei zurueckgestellten Live-Tests WINDOWS #4, #6, #12 gegen echtes AD und echtes Exchange-Postfach.
Resume file: None
-Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
+Last activity: 2026-09-07 - Checkpoint-Aufraeumen; Pitfalls und Infrastruktur-Stand in STATE.md ueberfuehrt
diff --git a/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/.continue-here.md b/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/.continue-here.md
deleted file mode 100644
index c767b37..0000000
--- a/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/.continue-here.md
+++ /dev/null
@@ -1,73 +0,0 @@
----
-context: phase
-phase: 10-ausschreibungs-radar-foundation-d-e-ingestion
-task: null
-total_tasks: 16
-status: code_complete_uat_pending
-last_updated: 2026-07-21T09:34:48.800Z
----
-
-# Phase 10 — Ausschreibungs-Radar Foundation & DÖE Ingestion
-
-_No blocking anti-patterns were discovered this session. Execution was clean._
-
-## Critical Anti-Patterns
-
-| Pattern | Description | Severity | Prevention Mechanism |
-|---------|-------------|----------|---------------------|
-| Stale live container masks completion | Code + migration + tests are complete and green, but the running `tessera-ctl-api-1` container is on the pre-Phase-10 image, so nobody has observed the boot-seed or a live HTTP request against the new routes. This is why verification is `human_needed`, NOT a code defect. | advisory | Rebuild the container (`docker compose up -d --build api web`) before treating a "green tests" state as user-visible done. |
-
-
-Phase 10 is code-complete: all 6 plans executed, 22 commits on `main`. Full monorepo test suite green (API 74/74, Web 111/111), `tsc --noEmit` clean for both apps. Independent goal-backward verification (`10-VERIFICATION.md`) confirmed 5/5 must-haves at the code/DB/test level. Verification status is `human_needed` — the only open item is a live UAT run after a Docker rebuild.
-
-
-
-
-- Plan 10-01: Prisma `Tender` + `TenderSourcePollConfig` (global, no tenantId/RLS) + migration APPLIED to local DB — SCHEMA-01. Commit 6cfda90.
-- Plan 10-02: Marketplace self-seed `tender-radar` + web module-loader whitelist + placeholder page — CONFIG-01. Commit f80d491.
-- Plan 10-03: `DoeOpenDataAdapter` + `TenderNormalizerService` (TDD, real DÖE fixtures, D-02 positive tag-match filter, zip-bomb guard T-10-07) — INGEST-01, SCHEMA-01. Commit 7610778.
-- Plan 10-04: `TenderIngestionService` + `TenderSchedulerService` (day-cursor, single global cron, contentHash upsert-update, 90-day retention with null-deadline exemption, two-tenant safety test) — SCHEMA-02, INGEST-06. Commit 9227cc9.
-- Plan 10-05: `TendersController` (global read, ModuleGuard-gated) + admin `GET/PUT /modules/tender-radar/source-config` (live-applies interval to scheduler) — INGEST-06. Commit 6d63022.
-- Plan 10-06: Admin config UI (`tender-radar-api.ts` client + `SourceConfigForm.tsx` + `settings/page.tsx`) — INGEST-06 admin-facing. Commit 4f3c6cf.
-
-
-
-
-Code: none. Pending human UAT (after Docker rebuild), all from `10-VERIFICATION.md`:
-- UAT-1: activate "Ausschreibungs-Radar" for a tenant from the marketplace, confirm the module page loads.
-- UAT-2: settings form — change interval / toggle isActive, save → GET/PUT source-config roundtrip works.
-- UAT-3: after rebuild, `TenderSourcePollConfig` has 1 row (boot-seed fired).
-
-
-
-
-- DÖE is a daily-batch export ZIP (not a paginated feed); today/future days return HTTP 400 → scheduler uses a day-cursor, not a since-timestamp. The admin-configurable hourly poll mostly no-ops by design.
-- Parse eForms-DE XML as primary (OCDS drops tenderPeriod/value for Unterschwelle notices); OCDS only for ocid/party resolution.
-- `Tender` + `TenderSourcePollConfig` are platform-global: no tenantId, no forTenant()/RLS (D-03) — verified in the live schema.
-- Single global cron, `findUnique` on fixed sourceType, no `activeTenantId`/`findFirst` (poll-once-fan-out-many, not the DKV single-tenant pattern). Two-tenant test proves 0 extra DÖE calls / cron jobs / rows on 2nd activation.
-- adm-zip approved via the supply-chain human-verify checkpoint (cthackers/adm-zip, MIT, since 2012; RESEARCH `[SUS]` flag was a too-new heuristic false positive).
-
-
-
-- Live container stale: `tessera-ctl-api-1` runs the pre-Phase-10 image. Resolve with `docker compose up -d --build api web`. Not a code defect.
-
-
-## Required Reading (in order)
-1. `.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-VERIFICATION.md` — the 3 structured human-verification items + per-criterion findings.
-2. `.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-RESEARCH.md` — the live-verified DÖE API contract (daily-batch ZIP, day-cursor, eForms-primary).
-3. Memory `project_local_db_migrations` — how to reach the local dev DB (no host port; use db container IP).
-
-## Infrastructure State
-- Local Docker stack STARTED this session (was down 2 days after a reboot). `db` + `api` up.
-- `api` container is on the OLD image — needs rebuild to run Phase 10 code.
-- DB migrations applied from host (tender-radar + an older LDAP migration never run locally). `prisma migrate status` = up to date.
-- DB reachable only via container IP (no host port): `postgresql://tessera:tessera_dev@172.19.0.2:5432/tessera` (IP ephemeral).
-- Boundary: the no-docker-rebuild rule in memory is about the TEST SERVER (192.168.13.12); the local dev machine is fine to rebuild.
-
-
-The phase went cleanly through the full GSD pipeline this session: research → validation strategy → pattern map → plan (5) → checker (1 blocker: missing admin config UI) → replan (+Plan 06) → verification passed → execute all 6 waves sequentially on main (worktrees auto-degraded, origin/HEAD unresolved) → goal-backward verify. The only reason this isn't marked fully Complete is the stale live container — everything else is proven.
-
-
-
-Start with: `docker compose up -d --build api web`, then run the 3 UAT checks in `10-VERIFICATION.md`. If green, proceed to `/gsd-discuss-phase 11` (Filter Engine, Results UI & Saved Searches).
-