11 KiB
context, phase, task, total_tasks, status, last_updated
| context | phase | task | total_tasks | status | last_updated |
|---|---|---|---|---|---|
| default | null | null | null | in_progress | 2026-07-09T14:36:45.919Z |
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/rebuildon the test server (root@192.168.13.12, "ViCoTest", app at https://alpha.tessera.ctl.de). Only read-only debugging (docker compose logs,psqlqueries,\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.
<current_state> 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 composepullinggit.vicolab.de/schalli/tessera-ctl/{web,api}:latestfrom Gitea CI. - Zentyal/Samba AD test directory: 192.168.13.13 (LDAP), domain
intern.vicolab.de, base DNdc=intern,dc=vicolab,dc=de, bind asAdministrator@intern.vicolab.de.
Last confirmed-working state (verified live via Playwright against alpha.tessera.ctl.de): LDAP connection test succeeds, group/OU discovery returns 36 real groups/OUs from Zentyal AD, and the new search box correctly filters that list (typed "Personal" → only "Personalabteilung" remained). </current_state>
<completed_work>
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 everyGET /favoritesbecause 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 adcdown -vreinstall 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 emailaaa2922— 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 defaulttessera/1.0UA 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). </completed_work>
<remaining_work>
- Per-user LDAP exclude/denylist filter — user explicitly asked for a way to exclude specific individual users (not just group/OU-based inclusion) from LDAP sync, giving
administrator,krbtgt,guest,dns-ldap,ldap$as examples of service accounts they don't want imported. The existinggroupFilterDnsinclude-filter (+ new search box) does NOT cover this — it only restricts which OUs/groups are searched, not individual usernames within an included scope. This request got sidetracked (user pivoted to praising the existing feature + asking for the search box) and was never revisited. This is the most likely next thing the user wants. - Live full-sync verification — only "Verbindung testen" and "Gruppen/OUs suchen" (discovery) were exercised live against the real Zentyal AD. An actual "Jetzt synchronisieren" run with a
groupFilterDnsselection saved and applied has not been observed/verified end-to-end yet. - STATE.md bookkeeping — the "Quick Tasks Completed" table only lists through
260708-cuc. Commits010aceb,39aa4bf,8e8305c,baff7ce,246dc89,aaa2922were done as direct fixes (fully diagnosed, small, urgent-to-unblock-live-testing) without spinning up the formal/gsd-quickplanner+executor pipeline each time, so they have no.planning/quick/entries or STATE.md rows. Purely cosmetic/audit-trail catch-up, not urgent. </remaining_work>
<decisions_made>
- 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/bindPasswordfully 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/findByUsernameplusAuthService.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). </decisions_made>
Required Reading (in order)
.planning/HANDOFF.json— structured version of this same state, for programmatic resume- 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
.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).
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 (serverldap://192.168.13.13:389, base DNdc=intern,dc=vicolab,dc=de, bindAdministrator@intern.vicolab.de, real password entered by the user directly in that session's browser). NogroupFilterDnsselection 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 theuser-files/zentyal/*.pngscreenshots the user provided. - Admin credentials: alpha.tessera.ctl.de admin password is
admin1234(changed from theadmin123default multiple times across DB resets this session — if login fails with that, tryadmin123first since a freshdcdown -vreinstall resets to the env-var default and forces a change again).
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.
<next_action> Start with: ask the user which of the three remaining_work items to pick up next (per-user LDAP exclude/denylist, live full-sync verification run, or STATE.md catch-up) — do not assume; they were mid-conversation about LDAP filtering when this pause was triggered by a context-budget warning, not by reaching a natural stopping point. </next_action>