Files
tessera-ctl/.planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-VERIFICATION.md
T
2026-10-08 11:34:16 +02:00

132 lines
18 KiB
Markdown

---
phase: quick-261008-dts
verified: 2026-10-08T11:25:00Z
status: human_needed
score: 7/7 must-haves verified
covered_files: [".planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-PLAN.md",".planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-SUMMARY.md","CHANGELOG.md","apps/api/prisma/migrations/20261008120000_domains_autodns/migration.sql","apps/api/prisma/schema.prisma","apps/api/src/app.module.ts","apps/api/src/domains/autodns-client.spec.ts","apps/api/src/domains/autodns-client.ts","apps/api/src/domains/autodns-parse.spec.ts","apps/api/src/domains/autodns-parse.ts","apps/api/src/domains/domain-name.spec.ts","apps/api/src/domains/domain-name.ts","apps/api/src/domains/domains-cache.ts","apps/api/src/domains/domains-directory.service.spec.ts","apps/api/src/domains/domains-directory.service.ts","apps/api/src/domains/domains-orders.service.spec.ts","apps/api/src/domains/domains-orders.service.ts","apps/api/src/domains/domains-settings.service.spec.ts","apps/api/src/domains/domains-settings.service.ts","apps/api/src/domains/domains.controller.spec.ts","apps/api/src/domains/domains.controller.ts","apps/api/src/domains/domains.module.ts","apps/api/src/domains/domains.seed.ts","apps/api/src/domains/domains.types.ts","apps/api/src/domains/dto/domains-contact.dto.ts","apps/api/src/domains/dto/domains-customer.dto.ts","apps/api/src/domains/dto/domains-order.dto.ts","apps/api/src/domains/dto/domains-settings.dto.ts","apps/api/src/module-registry/module-manage-handlers.spec.ts","apps/web/src/app/(portal)/modules/domains/components/ContactForm.test.tsx","apps/web/src/app/(portal)/modules/domains/components/ContactForm.tsx","apps/web/src/app/(portal)/modules/domains/components/ContactsTab.tsx","apps/web/src/app/(portal)/modules/domains/components/CustomersTab.tsx","apps/web/src/app/(portal)/modules/domains/components/DomainsTab.tsx","apps/web/src/app/(portal)/modules/domains/components/EnvironmentBadge.tsx","apps/web/src/app/(portal)/modules/domains/components/OrdersTab.test.tsx","apps/web/src/app/(portal)/modules/domains/components/OrdersTab.tsx","apps/web/src/app/(portal)/modules/domains/components/RegisterTab.test.tsx","apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx","apps/web/src/app/(portal)/modules/domains/components/SettingsTab.tsx","apps/web/src/app/(portal)/modules/domains/domains-page.test.tsx","apps/web/src/app/(portal)/modules/domains/layout.tsx","apps/web/src/app/(portal)/modules/domains/page.tsx","apps/web/src/app/(portal)/modules/module-layouts.test.tsx","apps/web/src/components/domains/group-by-customer.test.ts","apps/web/src/components/domains/group-by-customer.ts","apps/web/src/components/domains/order-status.test.ts","apps/web/src/components/domains/order-status.ts","apps/web/src/components/domains/ui-classes.ts","apps/web/src/components/modules/module-tile.tsx","apps/web/src/lib/domains-api.ts","apps/web/src/lib/module-identity.ts","apps/web/src/lib/module-loader.ts","apps/web/src/lib/stores/nav-store.ts","apps/web/src/messages/de.json","apps/web/src/messages/en.json","apps/web/src/messages/umlaut-dictionary.ts","docs/anleitung-administration.md","docs/anleitung-anwender.md","docs/mandantentrennung-zugriffsklassifikation.md"]
covered_digest: "v3:sha256:cbd9180b81679f6e773368c9acac6050b8ea89f07a2ceb0a6717989336115fdf"
behavior_unverified: 0
overrides_applied: 0
human_verification:
- test: "Demo-Kontext und Verbindungstest (A1, A2/A6/A9): Demo-Zugangsdaten eintragen, Kontext 4 (sonst 1), je Versuch genau einmal auf 'Verbindung testen' klicken"
expected: "'Verbindung erfolgreich. AutoDNS hat die Anmeldung bestätigt.' Welcher Demo-Kontext funktioniert, wird in die Handbücher übernommen"
why_human: "Braucht das echte AutoDNS-Demo-System und echte Zugangsdaten; Tests laufen nur gegen einen nachgebauten Aufruf"
- test: "Kontakte anlegen (A2): Person und Organisation mit Telefon +49 30 123456 anlegen, danach 'Aus AutoDNS neu einlesen'"
expected: "Meldung 'Der Kontakt wurde bei AutoDNS angelegt', Kontakt erscheint in der Liste. Feldsatz und Telefonformat werden von AutoDNS angenommen"
why_human: "Die Form des POST /contact-Bodys ist nur gegen die echte API belegbar"
- test: "Verfügbarkeit (A3, Umlaut): freie, belegte (z. B. denic.de) und Umlautdomain prüfen"
expected: "Frei mit Preis, belegt mit WHOIS-Status, Umlautdomain als Punycode gesucht; ein fehlender Preis zeigt 'Preis nicht ermittelbar'"
why_human: "Antwortform von POST /domainstudio (WHOIS-Status, Preisfundstelle) ist nur gegen das echte System prüfbar"
- test: "Registrierung im Demo-System (A4, A5, A8): Zusammenfassung, Häkchen, 'Jetzt verbindlich registrieren' für eine freie .de-Domain mit Standard-Nameservern"
expected: "Auftrag 'In Bearbeitung' mit Auftragsnummer, Kontaktverweis { id } wird akzeptiert, Nameserver-Prüfung besteht. Danach 'Aktualisieren' bis 'Registriert' (Job SUCCESS); Doppelklick erzeugt genau einen Auftrag"
why_human: "POST /domain ist asynchron; Job-Fundstelle, Kontaktverweis und Nameserver-Prüfung sind nur am Demo-System belegbar"
- test: "Domainliste nach Registrierung (D-J) und Abgleich unklarer Aufträge"
expected: "Domain erscheint mit Inhaber, Ablaufdatum und Status (registryStatus vorhanden); GET /domain/{name} liefert für Unbekanntes einen business-Fehler, damit 'Verwerfen' nach einem Abgleich freigeschaltet wird"
why_human: "registryStatus-Werte, cancelationStatus-Werte und die Antwort auf unbekannte Domains sind Annahmen über die echte API"
- test: "Rechte im Browser (optional, dunkler Modus): mit einem Benutzer nur mit 'Benutzen' anmelden"
expected: "Reiter Domains, Kontakte, Kunden, Aufträge sichtbar; kein Registrieren, kein Einstellungen, keine Schreibknöpfe; die API antwortet bei Schreibrouten 403"
why_human: "Die API-Metadaten und Oberflächen-Tests sind grün; ein echter Benutzen-Benutzer wurde in dieser Prüfung nicht angelegt (kein Eingriff in lokale Benutzer)"
---
# Quick-Task 261008-dts: Modul Domains (AutoDNS) Verification Report
**Task Goal:** Neues Modul "Domains" mit Anbindung an AutoDNS/InterNetX JSON-API: Einstellungen (verschlüsselter Zugang, Demo/Live, Kontext, Standard-Nameserver, Verbindungstest), Kontakte (lesen, anlegen, Kunden zuordnen, filtern/gruppieren), Domain registrieren (Verfügbarkeit, Kontakte, Nameserver, Zusammenfassung + ausdrückliche Bestätigung, keine Doppelbestellung, Job-Status), Domainliste. Ansehen mit "Benutzen", Ändern mit "Verwalten" oder Admin. Tests mit gemockter API. UI Deutsch, siezen, keine Mandant-Begriffe. CHANGELOG + Anleitungen.
**Verified:** 2026-10-08
**Status:** human_needed
**Re-verification:** No, initial verification
Alle sieben Wahrheiten sind im Code belegt. Es gibt keine Lücken. Offen sind nur Prüfungen, die das echte AutoDNS-Demo-System brauchen (gemäß Auftrag human_needed, keine Lücke).
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | Reiter je Recht; API antwortet auf Schreib-/Verwalten-Routen mit 403 für Benutzen | VERIFIED | `domains.controller.ts`: Klasse `@UseModule('domains')`; 12 Handler mit `@ModuleManage('domains')` (GET/PUT settings, connection-test, POST customers, PUT/DELETE customers/:id, POST contacts, contacts/assign, availability, orders, orders/:id/submit, orders/:id/cancel); GET status/customers/contacts/domains/orders und POST orders/refresh nur Klasse. Kein `@Roles(` (grep leer). `domains.controller.spec.ts` und `module-manage-handlers.spec.ts` grün (Metadaten, Deklarationsreihenfolge). `page.tsx`: `useCanManageModule('domains') === true` steuert Registrieren/Einstellungen; Reihenfolge Domains, Kontakte, Kunden, Registrieren, Aufträge, Einstellungen. Live-Stack: Admin erhält 200 auf alle Benutzen-Routen. |
| 2 | Zugang je System verschlüsselt, Demo/Live mit Bestätigung, ein GET /hello | VERIFIED | Migration/Schema: getrennte Spalten `demo*`/`live*`. `domains-settings.service.ts`: `crypto.encrypt` beim Speichern, leeres Passwort behält Wert, Antwort nur `hasPassword`, Entschlüsselungsfehler wirft `InternalServerErrorException`. Live-Stack: `GET /settings` ohne Passwortfeld; `PUT {environment:LIVE}` ohne `confirmLive` gibt 400 `confirmLiveRequired`. `testConnection` genau ein `autodnsRequest(GET /hello)`; Spec prüft literalen Basic-Header `YXBpLXVzZXI6Z2VoZWlt`, URL, Kontext-Header. Neuer Stand startet auf DEMO. Verbindungstest ohne Zugang: `{ok:false,kind:'not-configured'}` (live bestätigt). |
| 3 | Kontakte: live aus AutoDNS, "Nicht zugeordnet", neu einlesen, anlegen, zuordnen, eigene Firma, Filter/Gruppierung | VERIFIED | `domains-directory.service.ts`: `fetchAll` seitenweise (100, max 2000, sequentiell), 60-s-`TtlCache`, Zuordnung je aktivem System, `createContact`, `assignContacts`, `isOwnCompany` löst Markierung bei anderen. Web: `ContactsTab`, `ContactForm`, `CustomersTab`, `group-by-customer.ts`. 27 Directory-Tests, ContactForm-/Gruppierungs-/Seitentests grün. Live: `GET contacts` ohne Zugang gibt 409 `notConfigured`. Echte AutoDNS-Antwort siehe Human-Punkte. |
| 4 | Domainliste mit Kunde (über Inhaber-Kontakt), Inhaber, Ablauf, Status | VERIFIED | `listDomains` nutzt `POST /domain/_search?keys[]=expire&keys[]=ownerc`, Inhabername und Kunde über Zuordnung des aktiven Systems, `cancelationPending`. `DomainsTab.tsx` mit Filter/Gruppierung; Seitentest prüft Gruppen, "Nicht zugeordnet" zuletzt, dd.mm.yyyy. |
| 5 | Registrieren: DomainStudio FREE, vier Kontakte, 2-6 Nameserver, Zusammenfassung, ausdrückliche Bestätigung, höchstens ein POST /domain, 409 bei Zweitbestätigung/Systemwechsel, UNKNOWN statt Raten | VERIFIED | `domains-orders.service.ts` `submitOrder` (Zeilen 456-534): Zugang vorab, Lesen, dann `updateMany where {id, tenantId, status:'DRAFT', environment}`; bei `count !== 1` kein Netzaufruf, 404/409 `environmentChanged`/`alreadySubmitted`; genau ein `callRaw('POST','/domain')` ohne Query und ohne Schleife; Ausgang: Job → SUBMITTED, auth/forbidden/rate-limit/business → FAILED (openKey null), alles andere (Zeitablauf, Netz, TLS, umschlaglos) → UNKNOWN, nie zurück auf DRAFT. `createOrder` prüft Verfügbarkeit serverseitig neu (`notAvailable` 409), Nameserver 2-6, Kontakte gegen Liste. Migration: `@@unique(tenantId, environment, openKey)`. Verhaltensbelegt: Test "zwei gleichzeitige Bestätigungen" (Spec 519-545, `Promise.allSettled`, Lese-Barriere, In-Memory-`updateMany` wertet where aus) endet mit einem Erfolg, einem `alreadySubmitted`, genau einem POST; Systemwechsel-Tests ohne Fetch. `RegisterTab.tsx`: `useRef`-Sperre, Häkchen Pflicht. Alle 66 Tests grün. |
| 6 | Auftragsstatus folgt dem Job; unklarer Auftrag erst nach Abgleich verwerfbar | VERIFIED | `refreshOpenOrders` (max 20, älteste Prüfung zuerst, SUBMITTING älter 120 s → UNKNOWN), `refreshFromJob` (SUCCESS/FAILED/CANCELED/Rest bleibt SUBMITTED mit jobStatus), `reconcile` (GET /domain/{name}, dann POST /job/_search; ein Fehler beim Nachsehen setzt `lastCheckedAt` nicht), `cancelOrder` (UNKNOWN ohne `lastCheckedAt` → 409 `checkFirst`). `OrdersTab`: Abgleich beim Öffnen, 30-s-Intervall nur bei offenen Aufträgen. Live: `POST orders/refresh` 201. Reale Job-Antwortform: Human-Punkt. |
| 7 | Ein Client, feste Hosts, 350 ms, 20 s, kein Retry, status.type ERROR = Fehler, AutoDNS-Login als 502 | VERIFIED | `autodns-client.ts`: `AUTODNS_BASE_URLS` Konstante, `redirect:'error'`, `AutodnsRateLimiter(350)`, `AUTODNS_TIMEOUT_MS=20_000`, 5-MiB-Deckel, kein Retry, Pfadprüfung, `status.type==='ERROR'` → business. `domains.types.ts` `autodnsFailureToHttp`: auth/forbidden → `BadGatewayException` `autodnsAuth` (nie 401/403), Zeit/Netz/TLS → 504. Alle Aufrufer (Settings, Directory, Orders) gehen durch `autodnsRequest`. 313-Zeilen-Spec mit literalen Werten grün. |
**Score:** 7/7 Wahrheiten verifiziert (0 present, behavior-unverified; die Verhaltens-Wahrheit 5 ist durch einen benannten, bestehenden Test belegt)
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `apps/api/prisma/migrations/20261008120000_domains_autodns/migration.sql` | 4 Tabellen, 2 Enums, RLS | VERIFIED | Vier Tabellen mit ENABLE + FORCE + `tenant_isolation_policy`; `migrate status` "up to date", `migrate diff --exit-code` "No difference" |
| `apps/api/src/domains/autodns-client.ts` | Client, Parser, Limiter | VERIFIED | Exporte `AUTODNS_BASE_URLS`, `autodnsRequest`, `parseAutodnsEnvelope`, `AutodnsRateLimiter` vorhanden |
| `domains-settings.service.ts` | verschlüsselt, maskiert, Test | VERIFIED | 323 Zeilen, `crypto.encrypt(`, `decryptPassword` laut |
| `domains-directory.service.ts` | Kunden, Listen, Zuordnung | VERIFIED | 585 Zeilen, Rest siehe Wahrheit 3/4 |
| `domains-orders.service.ts` | Anspruch, Abgleich | VERIFIED | 782 Zeilen, Anspruch mit `status: 'DRAFT'` |
| `.../modules/domains/page.tsx` | Badge, 6 Reiter | VERIFIED | `useCanManageModule('domains')` steuert Reiter |
| `.../components/RegisterTab.tsx` | Verfügbarkeit bis Bestätigung | VERIFIED | `submittingRef`, Checkbox, gesperrter Knopf |
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `submitOrder` | `domainsOrder.updateMany` DRAFT, dann `POST /domain` | `claim.count !== 1` vor dem Netz | WIRED | Zeilen 480-511 |
| Settings-Dienst | `CryptoService` | `crypto.encrypt(` je System | WIRED | Zeilen 200-201 |
| `listDomains` | Zuordnung des aktiven Systems | Inhaber-Kontakt-Id → Kunde | WIRED | `loadAssignments(tenantId, environment)` |
| Controller | `ModuleGuard` | `@UseModule('domains')` + `@ModuleManage('domains')` | WIRED | 12 Verwalten-Handler, kein Rollen-Decorator |
| `page.tsx` | `useCanManageModule('domains')` | Reiter/Schreibknöpfe | WIRED | Zeile 36 |
| Modul-Registrierung | Seed, Loader, Icon, Navtitel, Layout-Test, `app.module.ts` | | WIRED | Log "Domains module seeded in registry"; Routen `orders/refresh` vor `orders/:id/submit` |
### Data-Flow Trace (Level 4)
| Artifact | Variable | Source | Real Data | Status |
|----------|----------|--------|-----------|--------|
| `DomainsTab` | Domainliste | `GET /modules/domains/domains` → `POST /domain/_search` (AutoDNS live) | Ja (gegen Mock getestet) | FLOWING |
| `ContactsTab` | Kontakte | `POST /contact/_search` + lokale Zuordnung | Ja | FLOWING |
| `OrdersTab` | Aufträge | `DomainsOrder` aus der Datenbank, Abgleich mit AutoDNS-Job | Ja | FLOWING |
| `EnvironmentBadge` | System/Status | `GET status` (live: DEMO, nicht eingerichtet) | Ja | FLOWING |
### Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| Domains-, RLS- und Rechte-Specs | `vitest run src/domains rls-coverage rls-access-inventory module-manage-handlers` | 10 Dateien, 267 Tests grün | PASS |
| Vollständige API-Suite | `pnpm --filter @tessera/api test` | 133 Dateien, 2423 Tests grün | PASS |
| Vollständige Web-Suite | `pnpm --filter @tessera/web test` | 132 Dateien, 1455 Tests grün | PASS |
| Web Domains/Meldungen/Layouts | `vitest run modules/domains components/domains module-layouts src/messages` | 10 Dateien, 98 Tests grün | PASS |
| Typprüfung | `tsc --noEmit` api und web | beide ohne Ausgabe | PASS |
| Lint | `biome lint` auf Domains-Dateien | 42 Dateien, keine Funde | PASS |
| Migration ohne Abweichung | `prisma migrate status` / `migrate diff --exit-code` | "up to date" / "No difference" | PASS |
| Live-Stack (Admin) | curl status/settings/customers/orders; connection-test; PUT LIVE ohne confirmLive | 200 / maskiert / 200 `[]` / `not-configured` / 400 `confirmLiveRequired` | PASS |
| Wortlaut | de/en `domains.*`-Schlüssel, Suche Mandant/Tenant/Lizenz in Texten, CHANGELOG, Handbücher | 207 = 207, gleiche Schlüssel, keine Treffer | PASS |
### Probe Execution
Step 7c: übersprungen, der Plan deklariert keine Probe-Skripte.
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| QUICK-261008-dts (L-01 bis L-12) | 261008-dts-PLAN.md | Modul Domains | SATISFIED im Code | Wahrheiten 1-7; CHANGELOG-Eintrag unter "Unveröffentlicht / Neu"; `### Domains` in `anleitung-anwender.md` und Inhaltsverzeichnis; `### Domains: AutoDNS anbinden` in `anleitung-administration.md`; keine Transfer-/Kündigungs-/Zonenfunktion (L-07), generischer `autodnsRequest` vorhanden. Gegenprobe gegen echtes System: Human-Punkte |
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| (alle geänderten Dateien) | | TBD/FIXME/XXX/TODO/HACK | keiner | Suche ohne Treffer |
| `domains.controller.ts` `POST connection-test` | | antwortet mit HTTP 201 statt 200 (Nest-Standard für POST) | Info | Plan sagt "immer 200 mit { ok, kind, message }". Der Inhalt stimmt, der Browser wertet jedes 2xx gleich. Kein Eingriff nötig |
| `autodns-client.ts` | 150-155 | HTTP-Fehler mit lesbarer Hülle zählt als `business`, also als ausdrückliche Ablehnung (FAILED) | Info | Ein Gateway, das JSON mit `messages` liefert, könnte fälschlich als FAILED statt UNKNOWN gelten. Gateway-Seiten ohne Hülle gehen korrekt auf UNKNOWN. Geringes, hinnehmbares Restrisiko |
### Human Verification Required
Die Punkte stehen oben im Frontmatter (`human_verification`). Sie decken die elf Annahmen aus der SUMMARY-Checkliste ab, die nur das echte AutoDNS-Demo-System schließen kann: Demo-Kontext (A1), Anmeldung ohne 2FA, Kontaktfelder und Telefonformat (A2), DomainStudio-Antwort und Preis (A3), Job-Rückgabe und Kontaktverweis (A4/A5), Job bis SUCCESS, `registryStatus`/`cancelationStatus`, Nameserver-Prüfung für .de (A8), Auftragssuche und `GET /domain/{name}` für Unbekanntes sowie Umlautdomains. Dazu kommt eine optionale Browser-Prüfung mit einem reinen "Benutzen"-Benutzer.
### Gaps Summary
Keine Lücken. Der Code erreicht das Ziel: Migration ohne Abweichung, Client, drei Dienste, Controller mit durchgängigem `@ModuleManage` und ohne Rollen-Decorator, sechs Reiter mit Rechtesteuerung, Doppelbestell-Schutz durch atomaren Anspruch (durch einen benannten Nebenläufigkeits-Test belegt), Tests mit gemockter API, deutsche Texte ohne verbotene Begriffe, CHANGELOG und beide Handbücher. Der lokale Stack läuft mit dem Modul. Offen sind nur die Annahmen über die echte AutoDNS-API, die laut Auftrag als human_needed zählen.
---
_Verified: 2026-10-08_
_Verifier: Claude (gsd-verifier)_