diff --git a/.planning/.continue-here.md b/.planning/.continue-here.md
index 3b89586..af1d967 100644
--- a/.planning/.continue-here.md
+++ b/.planning/.continue-here.md
@@ -1,96 +1,121 @@
---
context: default
-phase: null
+phase: 16-ad-gruppen-synchronisation
task: null
-total_tasks: null
-status: idle
-last_updated: 2026-07-14T08:16:00.000Z
+total_tasks: 0
+status: between-milestones
+last_updated: 2026-08-11T12:59:01.215Z
---
-# BLOCKING CONSTRAINTS — Read Before Anything Else
-
-> These are not suggestions. Each constraint below was discovered through failure.
-> Acknowledge each one explicitly before proceeding.
-
-- [ ] CONSTRAINT: no-docker-on-testserver — User explicitly forbids running `docker compose pull/up/down/restart/rebuild` on the test server (root@192.168.13.12, "ViCoTest", app at https://alpha.tessera.ctl.de). Only read-only debugging (`docker compose logs`, `psql` queries, `\dt`) is allowed there. Deployment (pull/rebuild/restart) is the user's own action — they do it and tell you when done, then you test via Playwright in the browser.
-
-**Do not proceed until all boxes are checked.**
-
## Critical Anti-Patterns
| Pattern | Description | Severity | Prevention Mechanism |
|---------|-------------|----------|---------------------|
-| ldapts missing-attribute shape | `ldapts` represents an absent LDAP attribute as an empty array `[]`, not `undefined`. Naive `Array.isArray(value) ? String(value[0]) : String(value)` turns a missing attribute into the literal string `"undefined"` — identical across every entry lacking that attribute, which then collides on DB unique constraints (this broke LDAP sync's email field against real AD). | advisory | Already fixed in `ldap.service.ts` (resolve to first array element / raw value, treat `undefined`/`null`/`''` as absent). Apply the same resolve-then-check pattern to any new LDAP attribute mapping code. |
-| Customer-specific values in shared UI | Hardcoding one tenant's/customer's actual infra values (server hostnames, domain, example text) into Tessera's generic admin UI as defaults, even when that exact customer asked for it — Tessera is a shared multi-tenant product that gets sold to other customers too. | advisory | Ask *where* a customer-specific value should live (env var, per-deployment seed, docs) before hardcoding it into shared component code. |
-
-_Remove rows that do not apply. The discuss-phase and execute-phase workflows parse this table and enforce a mandatory understanding check for any `blocking` rows._
+| 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`. |
-No active GSD phase — the v1.0 milestone was already at 100% before this session. Everything in this session (2026-07-07 through 2026-07-09) was reactive quick-task-style work, driven by live-testing on two real systems the user stood up specifically for this purpose:
-- Test/staging deploy: https://alpha.tessera.ctl.de, server 192.168.13.12 ("ViCoTest"), SSH root access, app deployed via `docker compose` pulling `git.vicolab.de/schalli/tessera-ctl/{web,api}:latest` from Gitea CI.
-- Zentyal/Samba AD test directory: 192.168.13.13 (LDAP), domain `intern.vicolab.de`, base DN `dc=intern,dc=vicolab,dc=de`, bind as `Administrator@intern.vicolab.de`.
+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`).
-Last confirmed-working state (verified live via Playwright against alpha.tessera.ctl.de): full LDAP sync now works end-to-end. The per-user exclude/denylist filter (commit 9d1323f) was built, migration applied on the live DB, and verified live: with administrator/krbtgt/guest/dns-ldap/ldap$ on the denylist, "Jetzt synchronisieren" returned Erstellt:0/aktualisiert:2/deaktiviert:4, and a psql check confirmed the 4 excluded service accounts are isActive=false while 2 real LDAP users stay active and 0 were wrongly created. Connection test + group/OU discovery (36 entries) + search box all still working.
-
-All three items that were open at the last pause are now DONE: (1) per-user exclude filter built+verified, (2) live full-sync verified, (3) STATE.md quick-tasks table caught up. No open LDAP work remains.
+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.
-Completed this session (all committed + pushed to `origin/main`, CI green on each):
-- `afef9b2` — fix(db): add missing FavoriteLink migration (found live: prod 500 on every `GET /favorites` because the model existed in schema.prisma but was never captured in a migration file)
-- `8e8305c` — revert(ldap): remove CTL-specific AD connection prefill (user: "das war nie das Ziel" after a `dcdown -v` reinstall surfaced it as baked-in defaults)
-- `39aa4bf` — feat(ldap): allow testing connection before saving a config (test button was hidden until a config already existed)
-- `010aceb` — feat(ldap): support anonymous bind (bindDn/bindPassword now fully optional, schema made nullable via migration)
-- `baff7ce` — fix(auth): case-insensitive usernames everywhere (login, admin seed, LDAP sync, plus a data migration lowercasing existing rows)
-- `246dc89` — fix(ldap): ldapts empty-array-attribute bug (see Anti-Patterns table above) — found live syncing against real Zentyal AD, every entry after the first crashed with a unique-constraint violation on email
-- `aaa2922` — feat(ldap): search box for the discovered groups/OUs list (user liked the existing group/OU filter, asked for a search-as-you-type box over it)
-- Earlier same session: Favorites icon proxy for CORP-restricted sites (claude.ai logo wasn't rendering — browser blocked the cross-origin hotlink via `Cross-Origin-Resource-Policy: same-origin`), plus a realistic-User-Agent fix for the same feature (Cloudflare WAF blocked the default `tessera/1.0` UA on some sites).
-
-All of the above were verified live either on the local dev stack or directly on alpha.tessera.ctl.de via Playwright browser automation (not just type-check/build — actual click-through testing).
+- 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
-None open. The three items carried from the previous pause are all resolved:
-- ✅ **Per-user LDAP exclude/denylist filter** — built (commit 9d1323f) and live-verified. Excludes individual usernames (service accounts) from sync, independent of the group/OU include-filter. Skips matches case-insensitively BEFORE recording the DN, so an already-imported user added to the denylist gets deactivated on the next sync.
-- ✅ **Live full-sync verification** — ran a real "Jetzt synchronisieren" against Zentyal with the denylist saved; Erstellt:0/aktualisiert:2/deaktiviert:4, DB-confirmed.
-- ✅ **STATE.md bookkeeping** — Quick Tasks table caught up with the direct-fix commits (8e8305c, 39aa4bf, 010aceb, baff7ce, 246dc89, aaa2922, 9d1323f).
+Vier Backlog-Punkte, alle unter `.planning/todos/pending/`:
-No GSD phase active; v1.0 milestone was already 100%. Next work is likely new module development — ask the user.
+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.
-- Reverted CTL-specific AD server/domain values that had been hardcoded as LDAP form defaults — Tessera is a generic multi-tenant product, must not bake one customer's infra into shared admin UI, even when that same customer asked for it the day before.
-- Made LDAP `bindDn`/`bindPassword` fully optional end-to-end (DTO, service, Prisma schema via migration, `LdapService.bind()` helper falling back to RFC 4513 anonymous bind) rather than just relaxing frontend validation — user wants to connect to directories that allow anonymous read.
-- Centralized username lowercasing in `UserService.create`/`update`/`findByUsername` plus `AuthService.validateUser`, `AdminSeedService`, and the LDAP sync loop, rather than only fixing the login comparison — closes the door on duplicate accounts differing only by case from any creation path (including AD sync where sAMAccountName casing varies).
+- 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
-None currently blocking. Two open items are queued as "remaining_work" above but nothing is stuck.
+- 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/HANDOFF.json` — structured version of this same state, for programmatic resume
-2. This file's Blocking Constraints table — the no-docker-on-testserver rule specifically; it was corrected by the user mid-session and must hold going forward
-3. `.planning/STATE.md` — general project state (note: its Quick Tasks table is stale per remaining_work above)
-## Critical Anti-Patterns (do NOT repeat these)
-- See the Blocking Constraints / Anti-Patterns tables above (ldapts empty-array shape; customer-specific hardcoding in shared UI).
+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
-- Local dev stack (docker compose, this repo checkout): running, all migrations applied, LDAP config currently empty/reset from testing (was deleted mid-session more than once to test the "no config yet" prefill states).
-- Test/staging (alpha.tessera.ctl.de / 192.168.13.12): running the latest pushed images as of `aaa2922` (user rebuilt web specifically to pick up the search-box commit and it was confirmed live). LDAP config on that tenant is currently SAVED with real Zentyal values (server `ldap://192.168.13.13:389`, base DN `dc=intern,dc=vicolab,dc=de`, bind `Administrator@intern.vicolab.de`, real password entered by the user directly in that session's browser). No `groupFilterDns` selection has been saved yet (still "Keine Auswahl - importiert alle Benutzer unter der Basis-DN").
-- Zentyal/Samba AD (192.168.13.13): live, reachable from the test server via IP (NOT the FQDN `intern.vicolab.de` — that resolved but connections to it timed out/failed; using the raw IP worked). Contains real groups/OUs/users per the `user-files/zentyal/*.png` screenshots the user provided.
-- Admin credentials: alpha.tessera.ctl.de admin password is `admin1234` (changed from the `admin123` default multiple times across DB resets this session — if login fails with that, try `admin123` first since a fresh `dcdown -v` reinstall resets to the env-var default and forces a change again).
+
+- **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.
-This was a long, reactive debugging/feature session almost entirely driven by "test this live and see what breaks" rather than planned phase work. The pattern that worked well: make a fix, verify locally (type-check + rebuild + Playwright), commit + push, wait for the user to confirm the real test-server deploy, then verify again live via Playwright against alpha.tessera.ctl.de. Several real bugs were only found this way (the FavoriteLink missing-migration 500, the ldapts empty-array/email-collision bug) — they would not have been caught by type-check or unit tests alone.
+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.
-The user is clearly hands-on and technical, corrects scope/boundary violations immediately and specifically (the CTL-prefill revert, the no-docker-on-testserver rule), and is currently mid-flow testing LDAP against a real AD they just stood up for this purpose. The natural continuation is either the per-user exclude filter or a live full-sync test — let them pick rather than assuming.
+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.
-No open LDAP work. Ask the user what to pick up next — most likely new module development now that v1.0 plus the LDAP hardening pass are complete. If they resume LDAP, the natural next candidates would be surfacing deactivated LDAP users in the admin UI (currently only visible via the isActive flag) or a sync-preview before applying, but neither has been requested.
+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/HANDOFF.json b/.planning/HANDOFF.json
index 3e91705..e6c755c 100644
--- a/.planning/HANDOFF.json
+++ b/.planning/HANDOFF.json
@@ -1,40 +1,48 @@
{
"version": "1.0",
- "timestamp": "2026-07-21T09:34:48.800Z",
- "phase": "10",
- "phase_name": "ausschreibungs-radar-foundation-d-e-ingestion",
- "phase_dir": ".planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion",
- "plan": 6,
+ "timestamp": "2026-08-11T12:59:01.215Z",
+ "phase": "16",
+ "phase_name": "ad-gruppen-synchronisation",
+ "phase_dir": ".planning/phases/16-ad-gruppen-synchronisation",
+ "plan": null,
"task": null,
- "total_tasks": 16,
+ "total_tasks": 0,
"status": "paused",
+ "context": "between-milestones",
"completed_tasks": [
- {"id": "10-01", "name": "Data + dependency foundation (Tender/TenderSourcePollConfig models, migration)", "status": "done", "commit": "6cfda90"},
- {"id": "10-02", "name": "Marketplace self-seed + module-loader whitelist + page", "status": "done", "commit": "f80d491"},
- {"id": "10-03", "name": "DOE adapter + normalizer (TDD, real fixtures, D-02 filter, zip-bomb guard)", "status": "done", "commit": "7610778"},
- {"id": "10-04", "name": "Ingestion + shared scheduler (day-cursor, two-tenant safety test)", "status": "done", "commit": "9227cc9"},
- {"id": "10-05", "name": "TendersController global read + admin source-config route", "status": "done", "commit": "6d63022"},
- {"id": "10-06", "name": "Admin config UI (SourceConfigForm)", "status": "done", "commit": "4f3c6cf"}
+ {"id": "16-UAT", "name": "Phase-16 UAT: Browser-Durchlauf 5/6/7 bestanden, Test 2 gegen echtes AD, Tests 1/3/4/8 bewusst uebersprungen", "status": "done", "commit": "ba21b0c,234f80b"},
+ {"id": "260811-f9i", "name": "KRITISCH: objectGUID-Existenzpruefung fand nie etwas (escapter String statt Rohbytes) — haette beim ersten Sync alle AD-gebundenen Gruppen samt Mitgliedschaften und Modulfreigaben geloescht", "status": "done", "commit": "d2019dc"},
+ {"id": "260811-j04", "name": "DOE-Ausschreibungen verlinkten auf die API statt auf die Bekanntmachung; Adapter + Backfill fuer 2846 Zeilen", "status": "done", "commit": "ecf7872"},
+ {"id": "15-04", "name": "Fehlender Abschlussbericht nachgezogen, Phase 15 damit 8/8", "status": "done", "commit": "149b5aa"},
+ {"id": "ldap-crypto", "name": "LDAP-Bind-Passwort AES-256-GCM verschluesselt, Spalte encryptedBindPassword, Bootstrap-Backfill", "status": "done", "commit": "4f687ea"},
+ {"id": "key-hardening", "name": "Stack startet nicht mehr ohne Verschluesselungsschluessel (beide Compose-Dateien)", "status": "done", "commit": "7bda56d"},
+ {"id": "key-rename", "name": "CALENDAR_ENCRYPTION_KEY -> TESSERA_ENCRYPTION_KEY mit Rueckfallebene; CryptoService in eigenem globalem Modul", "status": "done", "commit": "f574884"}
+ ],
+ "remaining_tasks": [
+ {"id": "backlog-lizenz", "name": "Lizenzpruefung bei der Modulaktivierung — jeder Mandanten-Admin kann sich jedes Modul selbst freischalten", "status": "not_started"},
+ {"id": "backlog-settings", "name": "Persoenliche vs. Admin-Einstellungen in Modul-Einstellungsseiten trennen", "status": "not_started"},
+ {"id": "backlog-compose", "name": "Compose-Datei auf dem Testserver driftet vom Repository ab", "status": "not_started"},
+ {"id": "backlog-envkey", "name": "Vorgabewert entfernen und Schluessel in den Beispiel-Umgebungsdateien dokumentieren", "status": "not_started"}
],
- "remaining_tasks": [],
"blockers": [
- {"description": "Running tessera-ctl-api-1 container is on the pre-Phase-10 image; new TendersModule/routes/boot-seed not live yet", "type": "human_action", "workaround": "docker compose up -d --build api web"}
+ {"description": "Kein Schreibzugriff im AD (balios.ctl.local) — UAT 1/3/4 der Phase 16 koennen dort nie ausgefuehrt werden", "type": "external", "workaround": "Bewusst als offene Annahme dokumentiert (16-UAT.md, Entscheidung 2026-08-11); A1 stuetzt sich auf die Microsoft-Dokumentation"},
+ {"description": "Zugriff auf .env.example / .env.prod.example war in der Sitzung durch Berechtigungen gesperrt", "type": "technical", "workaround": "Dokumentation des Schluessels bleibt im Backlog-Punkt 2026-08-11-verschluesselungsschluessel-vorgabewert.md"}
],
"async_jobs": [],
"human_actions_pending": [
- {"action": "docker compose up -d --build api web, then run 3 UAT checks from 10-VERIFICATION.md", "context": "Verifier status=human_needed: code/tests prove 5/5 must-haves but nobody observed live boot-seed or HTTP against new routes. Container rebuild is the user's job per project convention.", "blocking": true},
- {"action": "UAT-1: activate Ausschreibungs-Radar for a tenant from marketplace, confirm page loads", "context": "Success Criterion 1", "blocking": false},
- {"action": "UAT-2: settings form interval/isActive save -> GET/PUT source-config roundtrip", "context": "INGEST-06 admin UI", "blocking": false},
- {"action": "UAT-3: after rebuild TenderSourcePollConfig has 1 row (boot-seed)", "context": "Singleton poll config seed", "blocking": false}
+ {"action": "Entscheidung zur Lizenzierung: wer vergibt Lizenzen, was passiert beim Ablauf, braucht es Laufzeiten, wie kommt eine Lizenz in eine Kundeninstallation", "context": "Blockiert den groessten Backlog-Punkt; ohne diese Antworten ist der Rest Spekulation", "blocking": true},
+ {"action": "Grundsatzentscheidung, wo persoenliche Einstellungen in Tessera leben (eigene Nutzerseite vs. im Modul abgetrennt)", "context": "Betrifft tender-radar und dkv-fleet gleichermassen — einmal grundsaetzlich statt pro Modul", "blocking": true},
+ {"action": "Entscheiden, wie /opt/tessera an die Compose-Datei kommt (Arbeitskopie, CI-Artefakt, oder Abgleich-Skript)", "context": "Vorher klaeren, WARUM die Datei dort von Hand gepflegt wird — sie kann host-spezifische Anpassungen tragen", "blocking": true},
+ {"action": "Deploys auf alpha (pull + up -d --force-recreate) macht der User selbst", "context": "Stehende Regel; Konfigurationsdateien dort darf Claude seit 2026-08-11 bearbeiten", "blocking": false}
],
"decisions": [
- {"decision": "DOE is a daily-batch export ZIP, not a paginated feed; scheduler uses a day-cursor (today/future rejected HTTP 400)", "rationale": "Live-verified in RESEARCH; hourly poll interval mostly no-ops by design", "phase": "10"},
- {"decision": "eForms-DE XML is the primary parse target; OCDS only for ocid/party resolution", "rationale": "OCDS drops tenderPeriod/value for Unterschwelle notices", "phase": "10"},
- {"decision": "Tender + TenderSourcePollConfig are platform-global: NO tenantId, NO forTenant()/RLS", "rationale": "D-03 tender data is global; verified in live schema + grep gates", "phase": "10"},
- {"decision": "Single global cron (poll-once-fan-out-many), findUnique on fixed sourceType, no activeTenantId/findFirst", "rationale": "Avoids DKV single-tenant anti-pattern; two-tenant test proves 0 extra calls/jobs/rows", "phase": "10"},
- {"decision": "adm-zip approved after supply-chain human-verify checkpoint", "rationale": "cthackers/adm-zip, MIT, since 2012, millions DL/week; RESEARCH [SUS] flag was a too-new heuristic false positive", "phase": "10"}
+ {"decision": "Phase 16 mit A1 als offener, dokumentierter Annahme abgeschlossen statt einen Wegwerf-Domaenencontroller zu bauen", "rationale": "User hat keinen AD-Schreibzugriff; der eine substanzielle Fehler an der Stelle war bereits gefunden und behoben, der Rest-Erkenntnisgewinn rechtfertigte den Aufwand nicht", "phase": "16"},
+ {"decision": "Ein Plattform-Schluessel, umbenannt, statt eines eigenen Schluessels fuer das AD-Bind-Passwort", "rationale": "Zwei Schluessel in derselben .env auf demselben Host erhoehen die Sicherheit nicht, verdoppeln aber was bei Backup und Wiederherstellung schiefgehen kann", "phase": null},
+ {"decision": "Alter Schluesselname CALENDAR_ENCRYPTION_KEY bleibt lesbar, mit Veraltet-Warnung", "rationale": "Ein harter Rename haette jede bestehende Installation beim naechsten Start gestoppt — erst recht, seit Compose ohne Schluessel abbricht", "phase": null},
+ {"decision": "CryptoService in ein eigenes globales Modul statt in CalendarModule", "rationale": "Vier Module importierten CalendarModule nur fuer die Verschluesselung, was eine Abhaengigkeit vom Kalender vortaeuschte, die es nie gab", "phase": null},
+ {"decision": "Backfill der Verschluesselung zur Laufzeit beim Bootstrap statt in der Migration", "rationale": "SQL kann nicht verschluesseln — der Schluessel liegt in der Anwendungsumgebung; die Migration benennt nur die Spalte um", "phase": null}
],
"uncommitted_files": [],
- "next_action": "docker compose up -d --build api web; then run the 3 UAT checks in 10-VERIFICATION.md; if green, /gsd-discuss-phase 11",
- "context_notes": "Phase 10 code+tests complete (22 commits, API 74/74 + Web 111/111 green, tsc clean). Verifier status=human_needed only because the live container is stale. Local DB stack was down 2 days after a reboot; started it (docker compose up -d db api) and applied 2 pending migrations from host via prisma against the db container IP (tender-radar + an older LDAP migration never run locally). DB has no host port; reach it at postgresql://tessera:tessera_dev@:5432/tessera (IP ephemeral, currently 172.19.0.2). See memory project_local_db_migrations. No blocking anti-patterns hit during execution. NOTE: the no-docker-rebuild boundary in memory is about the TEST SERVER (192.168.13.12); this session's stack start was the LOCAL dev machine."
+ "next_action": "Mit dem User klaeren, welcher der vier Backlog-Punkte drankommt oder ob ein neuer Meilenstein nach v1.2 geplant wird. Die drei groesseren Punkte brauchen zuerst seine Entscheidungen (siehe human_actions_pending), der vierte (Vorgabewert + Doku) ist ohne Rueckfrage machbar.",
+ "context_notes": "v1.2 Plattform-Berechtigungen ist inhaltlich fertig: Phase 15 (8/8) und Phase 16 (5/5) abgeschlossen, beide in ROADMAP.md und STATE.md nachgezogen. Kein Phase-Work in Arbeit, Arbeitsverzeichnis sauber, alles auf origin/main. Die Sitzung war UAT-getrieben und hat dabei einen kritischen Fehler gefunden, der ohne den einen ausfuehrbaren Test unbemerkt in Produktion gegangen waere. Roter Faden fuer die Fortsetzung: die vier Backlog-Punkte sind alle 'vor dem Verkauf an externe Kunden', keiner davon draengt im internen Betrieb."
}