efcf11c988
Verifikation unabhaengig nachgemessen: 651/651 API-Tests, 233/233 Web-Tests, beide Typpruefungen sauber. Der Sicherheitsfund T-Q3-01 wurde nicht nur gruen getestet, sondern falsifiziert: der Verifizierer hat die neue Besitzpruefung testweise zurueckgebaut, woraufhin der Test fehlschlug und die fremde Adresse tatsaechlich in prisma.user.update() landete. Danach sauber zurueckgesetzt. Ebenso gegengeprueft: beide Anlege-Wege (Sync und Einzelimport) nutzen denselben Kollisionsentscheider, im ausgelieferten Code stehen keine kundenspezifischen Namen oder Adressen, rohe ORM-Texte erreichen die Oberflaeche nicht mehr, und die Gruppensuche unter internem wie AD-Namen (#6c) ist per Regressionstest gesichert. Eine Abweichung des Ausfuehrenden ist dokumentiert und bestaetigt: die im Plan vorgesehene Testvorlage war wegen Kurzschlussauswertung schon gegen den unveraenderten Code gruen; mit einem zweiten, nicht passenden Konto reproduziert sie den Absturz nun wirklich. Status human_needed: die fuenf Browser-Pruefungen brauchen einen Neubau und sind Sache des Users. WINDOWS #14 und #15 bleiben bis dahin offen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
132 lines
14 KiB
Markdown
132 lines
14 KiB
Markdown
---
|
|
phase: quick-260909-ab3
|
|
verified: 2026-09-09T08:05:00Z
|
|
status: human_needed
|
|
score: 8/8 must-haves verified
|
|
covered_files:
|
|
- ".planning/quick/260909-ab3-matrix-suche-und-sync-meldungen-reparier/260909-ab3-PLAN.md"
|
|
- ".planning/quick/260909-ab3-matrix-suche-und-sync-meldungen-reparier/260909-ab3-SUMMARY.md"
|
|
- "apps/api/prisma/migrations/20260909120000_user_email_optional/migration.sql"
|
|
- "apps/api/prisma/schema.prisma"
|
|
- "apps/api/src/ldap/ldap.service.spec.ts"
|
|
- "apps/api/src/ldap/ldap.service.ts"
|
|
- "apps/api/src/tenders/tender-digest.scheduler.ts"
|
|
- "apps/api/src/tenders/tender-matching.service.ts"
|
|
- "apps/api/src/user/user.service.ts"
|
|
- "apps/web/src/app/(portal)/admin/groups/components/GroupMembersModal.tsx"
|
|
- "apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx"
|
|
- "apps/web/src/app/(portal)/admin/ldap/page.tsx"
|
|
- "apps/web/src/app/(portal)/admin/modules/grants/grants-matrix.test.tsx"
|
|
- "apps/web/src/app/(portal)/admin/modules/grants/page.tsx"
|
|
- "apps/web/src/app/(portal)/admin/users/page.tsx"
|
|
- "apps/web/src/messages/de.json"
|
|
- "apps/web/src/messages/en.json"
|
|
- "apps/web/src/messages/umlaut-dictionary.ts"
|
|
covered_digest: "v1:sha256:ae03cb70a8b2c8457ebc567846a2f092772671a446d0d9bb79b3b567eafa0288"
|
|
behavior_unverified: 0
|
|
overrides_applied: 0
|
|
human_verification:
|
|
- test: "Freigaben-Matrix im Browser nach `Claude_VT` bzw. `Vertrieb` durchsuchen"
|
|
expected: "Gruppenspalte bleibt stehen UND alle Modulzeilen bleiben stehen, es gibt anklickbare Kaestchen"
|
|
why_human: "Plan verlangt Browser-Nachpruefung nach Neubau von API/Web (Konvention: kein Docker-Deploy auf Testserver durch den Ausfuehrenden); durch Unit-Tests (grants-matrix.test.tsx, WINDOWS #6c Regressionstest) bereits codeseitig abgesichert"
|
|
- test: "Dieselbe Matrix, Suche nach `Cert`"
|
|
expected: "Modulzeile bleibt stehen UND alle Gruppenspalten bleiben stehen"
|
|
why_human: "Browser-Nachpruefung vom Plan explizit dem Nutzer zugewiesen; Unit-Test deckt die Achsen-Logik bereits ab"
|
|
- test: "Dieselbe Matrix, Suchbegriff ohne Treffer"
|
|
expected: "Verstaendliche Meldung statt leerer Tabelle"
|
|
why_human: "Browser-Nachpruefung vom Plan explizit dem Nutzer zugewiesen"
|
|
- test: "AD-Sync im Browser ausloesen: uvertrieb_ro, uvertrieb_rw, uvertrieb_ro_ss, usoftware_rw"
|
|
expected: "Alle vier Konten erscheinen in der Benutzerliste — ohne Adresse; das Konto mit mbuntz@ctl.de behaelt seine Adresse unveraendert"
|
|
why_human: "Verlangt einen echten Sync-Lauf gegen das reale AD nach Neubau von API/Web — nicht durch Unit-Tests mit Mock-AD abbildbar; Verhalten ist codeseitig durch resolveEmailForWrite() und fuenf Testfaelle in ldap.service.spec.ts abgesichert"
|
|
- test: "Derselbe Sync-Bericht im Browser pruefen"
|
|
expected: "Keine Zeile enthaelt englischen Techniktext oder Datenbankwortlaut; die sechs Kontakte ohne Anmeldenamen stehen als neutraler Hinweis, nicht in Rot"
|
|
why_human: "Visuelle/Text-Pruefung der gerenderten Oberflaeche nach echtem Sync-Lauf; Backend-Seite (kein roher ORM-Text im Bericht) ist durch Test 5 aus Task 2 bereits programmatisch bewiesen"
|
|
---
|
|
|
|
# Quick Task 260909-ab3: Matrix-Suche und Sync-Meldungen reparieren — Verification Report
|
|
|
|
**Task Goal:** Matrix-Suche und Sync-Meldungen reparieren (WINDOWS #14 und #15)
|
|
**Verified:** 2026-09-09T08:05:00Z
|
|
**Status:** human_needed
|
|
**Re-verification:** No — initial verification
|
|
|
|
## Goal Achievement
|
|
|
|
### Observable Truths
|
|
|
|
| # | Truth | Status | Evidence |
|
|
|---|-------|--------|----------|
|
|
| 1 | Freigaben-Matrix: ein Suchbegriff, der nur einen GRUPPENNAMEN trifft, laesst ALLE Modulzeilen stehen | ✓ VERIFIED | `page.tsx:157-158` returns `filteredModules: modules` (unfiltered) when `groupHits.length > 0 && moduleHits.length === 0`; test `'a group-name search leaves all module rows standing (WINDOWS #14)'` (line 231) passes |
|
|
| 2 | Freigaben-Matrix: ein Suchbegriff, der nur einen MODULNAMEN trifft, laesst ALLE Gruppenspalten stehen | ✓ VERIFIED | `page.tsx:154-155` returns `filteredGroups: groups` (unfiltered) when `moduleHits.length > 0 && groupHits.length === 0`; test `'a module-name search leaves all group columns standing (WINDOWS #14)'` (line 208) passes |
|
|
| 3 | Spaltensuche findet importierte Gruppe unter internem UND AD-Namen (WINDOWS #6c) | ✓ VERIFIED | `page.tsx:149` `matches(g.internalName ?? g.name) \|\| matches(g.name)`; dedicated regression test `'finds an imported group by both internal and AD name (WINDOWS #6c regression)'` (line 253) searches both `'Vertrieb'` and `'Claude_VT'` and passes |
|
|
| 4 | Kein Treffer weder Modul noch Gruppe -> sichtbare Meldung statt stumm leerer Tabelle | ✓ VERIFIED | `page.tsx:219-222` renders `t('noSearchResults', {search})` when `noMatch`; test `'shows a visible message when the search term matches neither modules nor groups'` (line 283) asserts message visible and 0 checkboxes |
|
|
| 5 | AD-Sync: Konto mit bereits belegter Adresse wird angelegt — ohne Adresse; erster Halter behaelt seine Adresse | ✓ VERIFIED | `resolveEmailForWrite()` (ldap.service.ts:416-427) queries `findUnique` and withholds the address unless `!holder \|\| holder.id === ownRecordId`; wired into BOTH `upsertMappedUser` (create AND update branch) and `importUsersByDn`. Falsification test performed: reverting the ownership check to unconditionally allow the address makes `'nimmt einem bestehenden Konto seine Adresse nicht weg'` fail with the real address leaking into the `update()` call — confirms the test genuinely exercises the takeover scenario, not just the happy path |
|
|
| 6 | Kein Bestandteil des Sync-Berichts traegt rohen ORM-/englischen Techniktext | ✓ VERIFIED | `ldap.service.ts:988-998`: catch block puts only `entry.dn` into `result.entryFailures`; the raw exception message goes exclusively to `this.logger.error(...)`. Test `'reicht keinen rohen Datenbanktext an den Bericht durch'` asserts `JSON.stringify(result)` does not contain the thrown ORM message — passes |
|
|
| 7 | Eintraege ohne Anmeldenamen erscheinen als neutraler Hinweis, nicht als Fehler | ✓ VERIFIED | `ldap.service.ts:965-969` pushes to `result.skippedNoLogin` (not `errors`); UI renders this list with `text-muted-foreground`, explicitly not `text-destructive` (`admin/ldap/page.tsx:1363-1374`); test `'meldet Eintraege ohne Anmeldenamen getrennt und nicht als Fehler'` passes |
|
|
| 8 | Benutzer ohne E-Mail-Adresse bricht weder Benutzerliste noch Mitgliedersuche | ✓ VERIFIED | `GroupMembersModal.tsx:104` guards with `(u.email ?? '')`; `users/page.tsx` declares `email: string \| null`, table cell falls back to en-dash. Falsification test performed: reverting the guard to `u.email.toLowerCase()` makes the two-account fixture genuinely crash with `TypeError: Cannot read properties of null` at the exact predicted line — confirms the fixture (added as a deviation) really exercises the crash path, addressing the executor's self-reported deviation |
|
|
|
|
**Score:** 8/8 truths verified (0 present, behavior-unverified)
|
|
|
|
### Required Artifacts
|
|
|
|
| Artifact | Expected | Status | Details |
|
|
|----------|----------|--------|---------|
|
|
| `apps/api/prisma/migrations/20260909120000_user_email_optional/migration.sql` | Migration making `User.email` optional, NOT applied | ✓ VERIFIED | 18 lines, `ALTER TABLE "User" ALTER COLUMN "email" DROP NOT NULL;` only, unique index untouched, German header comment documents the locked product decision. Coherent with `schema.prisma:32` (`email String? @unique`). Not applied to any database (only the file was written; no migrate/deploy command was run by this verifier or found evidence of prior application) |
|
|
| `apps/web/src/app/(portal)/admin/modules/grants/page.tsx` | Independent-axis search filtering | ✓ VERIFIED | 303 lines, `useMemo` truth table matches plan spec exactly (lines 140-162) |
|
|
| `apps/api/src/ldap/ldap.service.ts` | Shared collision decider wired into both create paths | ✓ VERIFIED | 1692 lines, `resolveEmailForWrite` (416-427) used in `upsertMappedUser` (464, 494) and `importUsersByDn` (697) |
|
|
| `apps/web/src/app/(portal)/admin/ldap/page.tsx` | Structured, German-only sync report display | ✓ VERIFIED | 1403 lines, `SyncResult` interface extended (79-81), three new report sections (1348-1386) in the plan-specified order and colors |
|
|
|
|
### Key Link Verification
|
|
|
|
| From | To | Via | Status | Details |
|
|
|------|----|----|--------|---------|
|
|
| `upsertMappedUser()` | `UserService.create({ email? })` | Optional email passthrough | ✓ WIRED | `user.service.ts:51` `email?: string`; `ldap.service.ts:506-513` calls `create` with `...(createEmail && { email: createEmail })` — spreads nothing when withheld |
|
|
| `LdapSyncResult` (backend) | `interface SyncResult` (frontend) | Field-name parity | ✓ WIRED | Both sides declare `emailConflicts: {account, email}[]`, `skippedNoLogin: string[]`, `entryFailures: string[]` with matching names/shapes |
|
|
| `de.json` | `en.json` | Key parity via `umlaut-guard.spec.ts` (3rd test) | ✓ WIRED | Ran `umlaut-guard.spec.ts` independently — 3/3 tests pass, confirming both files flatten to identical key sets |
|
|
|
|
### Requirements Coverage
|
|
|
|
| Requirement | Source Plan | Description | Status | Evidence |
|
|
|-------------|-------------|--------------|--------|----------|
|
|
| PERM-02 | 260909-ab3-PLAN.md | AD group import/sync (Phase 16, already Complete) | ✓ SATISFIED | This quick task is a bugfix to already-shipped Phase 16/15 functionality (WINDOWS #14/#15), not new scope; no regression to the underlying requirement introduced (verified: full API+web suites green) |
|
|
| PERM-03 | 260909-ab3-PLAN.md | Module grants matrix (Phase 15, already Complete) | ✓ SATISFIED | Same — matrix search bug fixed without altering the underlying grant-toggle mechanism (T-Q3-06 accepted: search only changes visibility, not server-checked grants) |
|
|
|
|
### Anti-Patterns Found
|
|
|
|
None. Scanned all 13 non-message-catalog files listed in `files_modified` for `TBD/FIXME/XXX/TODO/HACK/PLACEHOLDER/not yet implemented/coming soon` — zero hits.
|
|
|
|
### Behavioral Spot-Checks / Falsification Tests
|
|
|
|
| Behavior | Method | Result | Status |
|
|
|----------|--------|--------|--------|
|
|
| T-Q3-01 ownership check actually prevents address takeover | Reverted `resolveEmailForWrite` to unconditionally allow the address, ran `'nimmt einem bestehenden Konto seine Adresse nicht weg'` alone | Test failed exactly as expected (`update()` called with the foreign address in `data`); reverted, full suite re-confirmed 67/67 green | ✓ PASS |
|
|
| Task-3 deviation (two-account fixture) genuinely exercises the crash | Reverted `GroupMembersModal.tsx:104` guard to `u.email.toLowerCase()`, ran the new test alone | Test failed with `TypeError: Cannot read properties of null (reading 'toLowerCase')` at the predicted line; reverted, full suite re-confirmed 13/13 green | ✓ PASS |
|
|
| Full API suite | `npx vitest run` from `apps/api` | 651/651 passed (46 files) — matches SUMMARY claim exactly | ✓ PASS |
|
|
| Full API type-check | `npx tsc --noEmit -p tsconfig.json` from `apps/api` | Clean, no output | ✓ PASS |
|
|
| Full web suite | `npx vitest run` from `apps/web` | 233/233 passed (38 files) — matches SUMMARY claim exactly | ✓ PASS |
|
|
| Full web type-check | `npx tsc --noEmit` from `apps/web` | Clean, no output | ✓ PASS |
|
|
| Individual file counts | `ldap.service.spec.ts` (67), `grants-matrix.test.tsx` (12) | Matches SUMMARY's "62 existing + 5 new" and "12/12" claims | ✓ PASS |
|
|
| No customer-specific values in shipped code | `grep -rn -i "uvertrieb\|usoftware\|mbuntz\|CTL_PWS" apps/ --include="*.ts" --include="*.tsx" --include="*.json"` | Zero hits anywhere in `apps/` (implementation and tests) | ✓ PASS |
|
|
| No raw ORM text reaches the UI report | Traced `catch (entryError)` block (ldap.service.ts:988-998) | Only `entry.dn` pushed to `result.entryFailures`; raw message only reaches `this.logger.error` | ✓ PASS |
|
|
|
|
Working tree was restored to a clean state after each falsification (`git status --short` shows only the untracked SUMMARY.md, no residual diffs).
|
|
|
|
### Human Verification Required
|
|
|
|
Five items are explicitly deferred to the user per the plan's own `<verification>` section (require a rebuild of API and web images before they can be exercised in a real browser against a real AD/Postgres). These are recorded as human-check items, not gaps — the underlying code paths are independently proven via unit tests and the falsification tests above.
|
|
|
|
1. **Freigaben-Matrix: Suche nach `Claude_VT`/`Vertrieb`** — Gruppenspalte und alle Modulzeilen bleiben stehen, anklickbare Kaestchen vorhanden. *Why human:* Browser-Nachpruefung nach Neubau, laut Projektkonvention "Kein Docker-Deploy auf Testserver durch Claude".
|
|
2. **Dieselbe Matrix, Suche nach `Cert`** — Modulzeile und alle Gruppenspalten bleiben stehen. *Why human:* Gleicher Grund.
|
|
3. **Dieselbe Matrix, Begriff ohne Treffer** — verstaendliche Meldung statt leerer Tabelle. *Why human:* Gleicher Grund.
|
|
4. **AD-Sync ausloesen** — die vier Konten `uvertrieb_ro`, `uvertrieb_rw`, `uvertrieb_ro_ss`, `usoftware_rw` erscheinen ab jetzt in der Benutzerliste, ohne Adresse; das Konto mit `mbuntz@ctl.de` behaelt die Adresse. *Why human:* Braucht einen echten Sync-Lauf gegen das reale AD und eine angewandte Migration — nicht durch Unit-Tests mit Mock-AD abbildbar.
|
|
5. **Derselbe Bericht** — keine Zeile enthaelt englischen Techniktext oder Datenbankwortlaut, die sechs Kontakte ohne Anmeldenamen stehen als neutraler Hinweis, nicht in Rot. *Why human:* Visuelle/Text-Pruefung der gerenderten Oberflaeche nach echtem Lauf.
|
|
|
|
### Gaps Summary
|
|
|
|
No gaps found. All 8 must-have truths, all 4 required artifacts, and all 3 key links are verified against the actual codebase (not merely SUMMARY claims). Two of the plan's own risk points — the T-Q3-01 ownership-check regression risk and the executor's self-reported test-fixture deviation — were independently falsified by this verifier (reverting the fix and confirming the test goes red for the right reason, then restoring cleanly). All four required test/type-check commands were re-run independently from a clean working tree and matched the SUMMARY's reported counts exactly. Migration file exists, is coherent with the schema, and was not applied (confirmed no `migrate deploy`/`migrate dev` was executed by this verification). No customer-specific account names or domains leaked into shipped code. The only reason this report is `human_needed` rather than `passed` is the five browser-verification items the plan itself explicitly assigns to the user after their next rebuild — these are deferred, not failed.
|
|
|
|
---
|
|
|
|
_Verified: 2026-09-09T08:05:00Z_
|
|
_Verifier: Claude (gsd-verifier)_
|