From eb10bd31169b07b94029350562a05078862694cd Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 21 Jul 2026 11:36:31 +0200 Subject: [PATCH] wip: pause phase 10 (code complete, UAT+rebuild pending) --- .planning/HANDOFF.json | 61 ++++++++-------- .../.continue-here.md | 73 +++++++++++++++++++ 2 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 .planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/.continue-here.md diff --git a/.planning/HANDOFF.json b/.planning/HANDOFF.json index ca9435e..3e91705 100644 --- a/.planning/HANDOFF.json +++ b/.planning/HANDOFF.json @@ -1,37 +1,40 @@ { "version": "1.0", - "timestamp": "2026-07-14T08:16:00.000Z", - "phase": null, - "phase_name": null, - "phase_dir": null, - "plan": null, + "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, "task": null, - "total_tasks": null, - "status": "idle", + "total_tasks": 16, + "status": "paused", "completed_tasks": [ - {"id": 1, "name": "LDAP: fix FavoriteLink 500 (missing migration, prod)", "status": "done", "commit": "afef9b2"}, - {"id": 2, "name": "LDAP: revert CTL-specific AD prefill per user feedback", "status": "done", "commit": "8e8305c"}, - {"id": 3, "name": "LDAP: allow testing connection before saving config", "status": "done", "commit": "39aa4bf"}, - {"id": 4, "name": "LDAP: support anonymous bind (optional bindDn/bindPassword)", "status": "done", "commit": "010aceb"}, - {"id": 5, "name": "Auth: case-insensitive usernames (login, seed, LDAP sync, migration)", "status": "done", "commit": "baff7ce"}, - {"id": 6, "name": "LDAP: fix ldapts empty-array-attribute bug causing email collision on sync", "status": "done", "commit": "246dc89"}, - {"id": 7, "name": "LDAP: search box for discovered groups/OUs list", "status": "done", "commit": "aaa2922"}, - {"id": 8, "name": "Favorites: icon proxy for CORP-restricted sites (claude.ai) + realistic UA fix", "status": "done", "commit": "f06a2ff (and related)"}, - {"id": 9, "name": "LDAP: per-user exclude/denylist filter -- exclude individual usernames (service accounts like administrator/krbtgt/guest/dns-ldap/ldap$) from sync, independent of the group/OU include-filter. Backend (schema+migration, DTO, service skip-before-syncedDns so excluded users get deactivated, controller+scheduler threading), frontend admin section (add/remove/save), de/en i18n, 3 unit tests. Live-verified on alpha.tessera.ctl.de.", "status": "done", "commit": "9d1323f"}, - {"id": 10, "name": "Live full-sync verification against real Zentyal AD -- ran 'Jetzt synchronisieren' with exclude list saved; result Erstellt:0/aktualisiert:2/deaktiviert:4; DB confirmed the 4 excluded service accounts isActive=false, 2 real LDAP users active, 0 wrongly created", "status": "done", "commit": "9d1323f (verification)"}, - {"id": 11, "name": "STATE.md quick-tasks table catch-up -- added rows for the direct-fix commits (8e8305c, 39aa4bf, 010aceb, baff7ce, 246dc89, aaa2922, 9d1323f) that had no /gsd-quick dirs", "status": "done", "commit": "(STATE.md edit)"} + {"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"} ], "remaining_tasks": [], - "blockers": [], - "async_jobs": [], - "human_actions_pending": [], - "decisions": [ - {"decision": "Reverted CTL-specific AD server/domain hardcoded as form defaults", "rationale": "User: 'das war nie das Ziel' -- Tessera is a generic multi-tenant product, must not bake one customer's infra into shared admin UI", "phase": null}, - {"decision": "LDAP bindDn/bindPassword made fully optional (anonymous bind support)", "rationale": "User explicitly requested removing the requirement to enter a bind user/password", "phase": null}, - {"decision": "Usernames normalized to lowercase everywhere (storage + lookup), not just at login", "rationale": "User: login was case-sensitive and shouldn't be; centralized in UserService rather than per-callsite", "phase": null}, - {"decision": "Per-user exclude list skips matches BEFORE recording the DN in syncedDns", "rationale": "So a user added to the denylist after already being imported gets deactivated on the next sync (rather than lingering active); exclude match is case-insensitive to align with lowercase username handling", "phase": null} + "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"} ], - "uncommitted_files": ["(unstaged) .planning/STATE.md, .planning/HANDOFF.json, .planning/.continue-here.md -- planning bookkeeping, not yet committed"], - "next_action": "No open LDAP work. All three previously-remaining items (per-user exclude filter, live full-sync verify, STATE.md catch-up) are done. Next session: ask the user what to pick up next -- likely new module work now that v1.0 + LDAP hardening are complete. No GSD phase active.", - "context_notes": "This whole session was reactive, ad-hoc fixing driven by live-testing on a real production-style deployment (alpha.tessera.ctl.de, test box 192.168.13.12) plus a freshly stood-up Zentyal/Samba AD test directory (192.168.13.13, domain intern.vicolab.de) that the user built specifically so LDAP could be tested against something real. No GSD phase is active -- the v1.0 milestone was already at 100% before this session; everything today was quick-task-style bugfixing/feature work, several done directly without spinning up the full /gsd-quick planner+executor pipeline (justified each time by being small, fully-diagnosed, and urgent to unblock live testing). CRITICAL boundary: user explicitly does NOT want me running docker compose pull/up/down/restart/rebuild on the test server myself -- only docker logs / psql for read-only debugging. They pull/rebuild themselves and tell me when done, then I test via Playwright in the browser." + "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} + ], + "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"} + ], + "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." } 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 new file mode 100644 index 0000000..c767b37 --- /dev/null +++ b/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/.continue-here.md @@ -0,0 +1,73 @@ +--- +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). +