92 lines
11 KiB
Markdown
92 lines
11 KiB
Markdown
---
|
|
context: default
|
|
phase: null
|
|
task: null
|
|
total_tasks: null
|
|
status: in_progress
|
|
last_updated: 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/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._
|
|
|
|
<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 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`.
|
|
|
|
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 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).
|
|
</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 existing `groupFilterDns` include-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 `groupFilterDns` selection 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`. Commits `010aceb`, `39aa4bf`, `8e8305c`, `baff7ce`, `246dc89`, `aaa2922` were done as direct fixes (fully diagnosed, small, urgent-to-unblock-live-testing) without spinning up the formal `/gsd-quick` planner+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`/`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).
|
|
</decisions_made>
|
|
|
|
<blockers>
|
|
None currently blocking. Two open items are queued as "remaining_work" above but nothing is stuck.
|
|
</blockers>
|
|
|
|
## 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).
|
|
|
|
## 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).
|
|
|
|
<context>
|
|
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.
|
|
|
|
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.
|
|
</context>
|
|
|
|
<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>
|