docs(quick-261008-h3t): Domains-Nameserver aus AutoDNS
Tessera CI/CD / Lint & Type Check (push) Successful in 55s
Tessera CI/CD / Tests (push) Successful in 1m34s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m24s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 12:40:35 +02:00
parent 2176f8ecce
commit 4ff43c2252
3 changed files with 427 additions and 1 deletions
+2 -1
View File
@@ -31,7 +31,7 @@ See: .planning/PROJECT.md (updated 2026-07-17)
Phase: 18 (desktop-client-fertigstellen) — COMPLETE (2026-09-17, Verifikation passed, Windows-Bedienprobe bestanden)
Plan: 6 of 6
Status: Alle 18 Phasen abgeschlossen; Version 1.2.0 freigegeben. Kein laufender Meilenstein. Nach 1.2.0 auf main (Beta): Bildmarke in Akzentfarbe, CI-Desktop-Skip, Favoriten-Symbol/-Sortierung, Desktop-Server-Adresse, Update in der App (signiert), Versionszeile auf der Setup-Seite — alles verifiziert und auf VM/CI nachgewiesen
Last activity: 2026-10-08 - Quick 261008-dts Modul Domains (AutoDNS)
Last activity: 2026-10-08 - Quick 261008-h3t Domains-Nameserver aus AutoDNS
Progress: [██████████] 99%
@@ -498,6 +498,7 @@ Gerettet aus `.continue-here.md`. Relevant fuer die noch offenen Live-Tests.
| 261005-jqd | Einstellungen und Verwaltung aufgeraeumt (gemeinsame Navigation, Seitenkopf, Karten; „Verwaltung“) | 2026-10-05 | 0a35a32 + (dieser Commit) | [261005-jqd-verwaltung-aufgeraeumt](.planning/quick/261005-jqd-verwaltung-aufgeraeumt/) |
| 261006-dcs | Linux-App: weißes Fenster auf Arch/EndeavourOS (libwayland aus AppImage entfernt) | 2026-10-06 | (dieser Commit) | [261006-dcs-linux-app-weisses-fenster-arch](.planning/quick/261006-dcs-linux-app-weisses-fenster-arch/) |
| 261008-dts | Modul Domains: AutoDNS-Anbindung (Zugang Demo/Live, Kunden, Kontakte, Domainliste, Registrieren ohne Doppelbestellung, Aufträge) — Needs Review (Demo-Prüfung offen) | 2026-10-08 | 53b73dd | [261008-dts-modul-domains-autodns-anbindung-kontakte](.planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/) |
| 261008-h3t | Domains: Standard-Nameserver aus AutoDNS-Profil statt Tessera-Einstellung (nur Anzeige, Sperre wenn keine) — Demo-Prüfung offen | 2026-10-08 | 2176f8e | [261008-h3t-domains-nameserver-aus-autodns-statt-tes](.planning/quick/261008-h3t-domains-nameserver-aus-autodns-statt-tes/) |
## Deferred Items
@@ -0,0 +1,329 @@
---
phase: quick-261008-h3t
plan: 01
type: execute
wave: 1
depends_on: []
quick_id: 261008-h3t
description: "Domains: Standard-Nameserver aus dem AutoDNS-Benutzerprofil statt aus einer Tessera-Einstellung"
date: 2026-10-08
files_modified:
# Task 1 — tracer (API): AutoDNS profile -> parser -> draft payload -> POST /domain body, plus GET name-servers route
- apps/api/src/domains/domain-name.ts
- apps/api/src/domains/autodns-parse.ts
- apps/api/src/domains/autodns-parse.spec.ts
- apps/api/src/domains/domains-orders.service.ts
- apps/api/src/domains/domains-orders.service.spec.ts
- apps/api/src/domains/dto/domains-order.dto.ts
- apps/api/src/domains/domains.controller.ts
- apps/api/src/domains/domains.controller.spec.ts
- apps/api/src/module-registry/module-manage-handlers.spec.ts
- apps/api/src/domains/domains-settings.service.ts
# Task 2 — web: Registrieren shows AutoDNS nameservers read-only, hint and lock when missing
- apps/web/src/lib/domains-api.ts
- apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx
- apps/web/src/app/(portal)/modules/domains/components/RegisterTab.test.tsx
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
# Task 3 — remove the setting everywhere (DB column, API, web), docs, changelog, rebuild
- apps/api/prisma/schema.prisma
- apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql
- apps/api/src/domains/domains-settings.service.spec.ts
- apps/api/src/domains/domains.types.ts
- apps/api/src/domains/dto/domains-settings.dto.ts
- apps/api/src/domains/dto/domains-settings.dto.spec.ts
- apps/web/src/app/(portal)/modules/domains/components/SettingsTab.tsx
- apps/web/src/app/(portal)/modules/domains/domains-page.test.tsx
- CHANGELOG.md
- docs/anleitung-anwender.md
- docs/anleitung-administration.md
- docs/mandantentrennung-zugriffsklassifikation.md
autonomous: true
requirements: [QUICK-261008-h3t]
estimate:
tokens: 100000
raw_tokens: 100000
tasks: 3
confidence: low
must_haves:
truths:
- "The Einstellungen tab of the module Domains shows no 'Standard-Nameserver' card any more; GET/PUT settings and GET status carry no nameserver field; after the migration the table DomainsConfig has no nameserver column (D-01)"
- "A manager opening the Registrieren tab sees the standard nameservers that AutoDNS holds in the profile of the configured AutoDNS user (GET /user/{user}/{context}/profile of the active system), read-only, in AutoDNS order (by the number in the profile key), with no input, add or remove controls (D-02, D-03)"
- "Creating a draft ignores any nameservers sent by the browser, reads the AutoDNS profile on the server, stores exactly that ordered list in the draft payload and returns it in the summary; confirming sends exactly that stored list in that order in the one POST /domain; the WR-02 version binding, the atomic DRAFT-to-SUBMITTING claim, the single POST without retry and the UNKNOWN handling are unchanged (D-03)"
- "If AutoDNS cannot be read or the profile yields fewer than two recognisable nameservers (or two different values for the same number), the Registrieren tab shows a German Sie-form hint ('In AutoDNS sind keine Standard-Nameserver hinterlegt …' respectively 'Tessera konnte die Standard-Nameserver nicht aus AutoDNS lesen …'), 'Zusammenfassung anzeigen' stays disabled, the server refuses the draft with 409 noDefaultNameServers or the AutoDNS error without writing a row, and submit refuses a draft with fewer than two nameservers before the claim — nothing is guessed (D-04)"
- "docs/anleitung-anwender.md, docs/anleitung-administration.md and the existing Domains lines under 'Unveröffentlicht' in CHANGELOG.md describe nameservers coming from AutoDNS; the 261008-dts SUMMARY is unchanged (D-05)"
- "The full api and web test suites, both tsc runs and the de/en key parity stay green; the rebuilt stack answers GET modules/domains/name-servers with 200, 409, 502 or 504 (never 404 or 500)"
artifacts:
- path: "apps/api/src/domains/autodns-parse.ts"
provides: "parseProfileNameServers: tolerant recognition of the standard nameservers in an AutoDNS user profile ([ASSUMED] key names)"
exports: ["parseProfileNameServers"]
- path: "apps/api/src/domains/domains-orders.service.ts"
provides: "getProfileNameServers, profile read inside createOrder, pre-claim guard for drafts without nameservers"
contains: "noDefaultNameServers"
- path: "apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql"
provides: "drops the obsolete settings column"
contains: "DROP COLUMN"
- path: "apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx"
provides: "read-only AutoDNS nameserver list, hint with retry, summary locked without nameservers"
key_links:
- from: "apps/api/src/domains/domains-orders.service.ts createOrder"
to: "AutoDNS GET /user/{user}/{context}/profile"
via: "readProfileNameServers(credentials) -> payload.nameServers"
pattern: "readProfileNameServers\\(credentials\\)"
- from: "apps/api/src/domains/domains-orders.service.ts submitOrder"
to: "AutoDNS POST /domain"
via: "stored payload list in stored order"
pattern: "payload\\.nameServers\\.map"
- from: "apps/api/src/domains/domains.controller.ts"
to: "DomainsOrdersService.getProfileNameServers"
via: "GET name-servers with ModuleManage('domains')"
pattern: "@Get\\('name-servers'\\)"
- from: "apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx"
to: "GET /modules/domains/name-servers"
via: "getNameServers() in apps/web/src/lib/domains-api.ts"
pattern: "getNameServers\\("
---
<objective>
The Tessera setting "Standard-Nameserver" in the module Domains (built in quick 261008-dts, commits 1f1c984..53b73dd) is wrong and goes away. AutoDNS already holds the standard nameservers in the profile of the AutoDNS user (for this user: ns2.ctl.de, b.ns14.net, c.ns14.net, d.ns14.net in exactly that order). Tessera reads them from AutoDNS when a registration is prepared, shows them for control only, stores them with the draft the user confirms and sends them explicitly, in AutoDNS order, in the single POST /domain. Company-specific values are never hard-coded in Tessera — not in code, tests, defaults or docs.
Locked requirements from the request (cited below as D-xx; numbering follows the request):
- D-01 Remove the settings card "Standard-Nameserver" (web SettingsTab, API DTO/service, validation). DB column: removed by migration (chosen in Task 3; values are worthless).
- D-02 On registration, read the standard nameservers from the AutoDNS user profile (GET /user/{name}/{context}/profile, response UserProfileViews with profiles[] of key/value). The key names are UNKNOWN: recognise tolerantly (keys containing "ns" plus a number, sorted by that number), encapsulated in one function with tests, assumption documented as [ASSUMED] and put on the demo checklist.
- D-03 Registrieren tab and summary show the AutoDNS nameservers read-only, in AutoDNS order. They are stored with the draft (part of what the user confirms; WR-02 version binding stays intact) and sent explicitly in that order in POST /domain.
- D-04 If Tessera cannot read nameservers from AutoDNS (error or no matching keys): clear German Sie-form hint starting "In AutoDNS sind keine Standard-Nameserver hinterlegt …" and registration locked — nothing guessed.
- D-05 Update docs/anleitung-anwender.md, docs/anleitung-administration.md and the existing Domains lines under "Unveröffentlicht" in CHANGELOG.md (adapt, no new line). Do NOT change the 261008-dts SUMMARY.
- Money safety (atomic claim, exactly one POST, UNKNOWN) is not touched except where needed; all existing tests stay green.
Discretion choices (documented here, cited in tasks):
- On a read ERROR (auth, timeout, gateway) the hint says "Tessera konnte die Standard-Nameserver nicht aus AutoDNS lesen …" plus the AutoDNS detail, because "no nameservers stored" would be false in that case; both cases lock registration identically. The "no matching keys" case uses the requested opening sentence verbatim.
- The server reads the profile itself inside createOrder (authoritative); the browser list is informational. The client never sends nameservers.
- Fewer than two recognised nameservers counts as "not stored" (.de needs at least two). No upper cap and no truncation (truncating would be guessing); AutoDNS validates the rest.
Purpose: AutoDNS stays the single source of the nameserver defaults; Tessera holds no company-specific values and never registers with nameservers the user did not see.
Output: profile parser with tests, API route GET name-servers, draft/summary/submit using the AutoDNS list, read-only Registrieren display with lock, setting removed including DB column, docs and changelog updated.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/STATE.md
@./CLAUDE.md
@.planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-SUMMARY.md
@.planning/quick/261008-dts-modul-domains-autodns-anbindung-kontakte/261008-dts-REVIEW.md
Project rules that apply here:
- NestJS: static routes before any `:id` route (domains.controller.spec.ts checks declaration order).
- API TS lib has no Object.hasOwn; use `Object.prototype.hasOwnProperty.call` or `in`.
- Never read .env files. Rebuild locally with `docker compose up -d --build api web` (the api container runs `prisma migrate deploy` on start). No deploy to the test server, no push (commits stay local; the user pushes bundled).
- UI texts: Sie-form, real umlauts (apps/web/src/messages/umlaut-guard.spec.ts fails on ae/oe/ue substitutes), de/en keys identical, no "Mandant"/"Lizenz".
- Global ValidationPipe: `whitelist: true`, no forbidNonWhitelisted — unknown body fields are stripped, not rejected.
AutoDNS spec facts (Swagger 2.0, already checked by the planner; the local copy is a session scratch file the executor need not open):
- GET /user/{name}/{context}/profile (operationId userProfileInfo, task 1301017) -> JsonResponseDataUserProfileViews: `data` is an array of UserProfileViews `{ profiles: UserProfileView[] }`; UserProfileView has `key` (string, example "techc"), `value` (string), `flag`, `inherited` (bool), `readonly` (bool), created/updated/owner/updater.
- Domain has `nameServers[]` (objects with `name`), `nameServerGroup`, `zone`. The existing POST /domain body already sends `nameServers: [{ name }]`.
- Base URLs (autodns-client.ts AUTODNS_BASE_URLS): Demo `https://api.demo.autodns.com/v1`, Live `https://api.autodns.com/v1`. `autodnsRequest` rejects paths containing `..`, `?`, `#`, `//` or whitespace by throwing.
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1 (tracer): AutoDNS profile -> parser -> draft payload -> POST /domain body, plus GET name-servers</name>
<files>apps/api/src/domains/domain-name.ts, apps/api/src/domains/autodns-parse.ts, apps/api/src/domains/autodns-parse.spec.ts, apps/api/src/domains/domains-orders.service.ts, apps/api/src/domains/domains-orders.service.spec.ts, apps/api/src/domains/dto/domains-order.dto.ts, apps/api/src/domains/domains.controller.ts, apps/api/src/domains/domains.controller.spec.ts, apps/api/src/module-registry/module-manage-handlers.spec.ts, apps/api/src/domains/domains-settings.service.ts</files>
<read_first>apps/api/src/domains/autodns-parse.ts, apps/api/src/domains/domains-orders.service.ts (lines 1-600: ERR, readPayload, callRaw, createOrder, submitOrder), apps/api/src/domains/domains-orders.service.spec.ts (harness lines 1-160, createOrder block around line 333, submit block around line 480-560), apps/api/src/domains/domains.controller.ts, apps/api/src/domains/domains.controller.spec.ts (lines 1-60 and 95-140), apps/api/src/module-registry/module-manage-handlers.spec.ts (lines 85-105), apps/api/src/domains/domain-name.ts</read_first>
<behavior>
- parseProfileNameServers, UserProfileViews shape: entries ns3/ns1/ns4/ns2 listed out of order (plus a techc entry) -> hostnames ordered ns1, ns2, ns3, ns4; techc ignored (D-02)
- numeric sort: ns10 comes after ns9, not after ns1
- value normalisation: " Z.Example.NET. " -> "z.example.net"; values that are no hostname (IP 192.0.2.1, empty, "kein rechner", "a.example.org 192.0.2.1") are skipped
- key variants recognised: nameserver1, NS_2, default_ns3, nserver4 (case-insensitive)
- flat items `{ key, value }` directly in data are accepted as well as `{ profiles: [...] }` and a single object with profiles
- same number with two different hostnames -> empty list (ambiguous, nothing guessed); same number with the same hostname -> once; the same hostname under two numbers -> first kept
- list fallback, only when no numbered key yields a hostname: key "nameservers" with "a.example.org, b.example.org" -> that order; two list keys with different lists -> empty
- garbage input (null, "x", {}, [1, 2]) -> `{ nameServers: [], keys: [] }`; `keys` lists the profile keys seen (for the log line)
- getProfileNameServers: exactly one GET to `https://api.demo.autodns.com/v1/user/api-user/4/profile`; returns `{ environment: 'DEMO', nameServers }` in key order; a user name with a space is percent-encoded in the path
- profile without nameserver keys -> 409 code noDefaultNameServers; AutoDNS 401 -> 502 autodnsAuth; timeout -> 504 (D-04)
- createOrder: payload.nameServers and summary.nameServers equal the profile order (neither alphabetical nor from the request); a request body with nameServers ['evil.example.com', 'x.example.com'] is ignored (D-03)
- createOrder with a profile without nameservers -> 409 noDefaultNameServers, no row written, no /domainstudio call; profile answered 500 -> rejected, no row (D-04)
- createOrder on an existing DRAFT re-reads the profile and returns a new version (WR-02 binding intact)
- tracer chain (describe 'Nameserver aus AutoDNS (h3t)'): createOrder -> submitOrder(id, version) -> exactly one POST /domain whose body.nameServers is [{ name }] in profile order
- submit guard: a DRAFT whose payload.nameServers is [] or has one entry -> 400 orderInvalid, the claim updateMany is not called, zero AutoDNS calls
- every existing submit, claim, UNKNOWN, reconcile and cancel test passes unchanged
- controller: getNameServers is GET 'name-servers', carries ModuleManage('domains'), no role decorator, is declared before every :id handler and forwards req.tenantId
</behavior>
<action>
Write the tests from the behavior list first (RED), then implement (GREEN). This task proves the money path end-to-end inside the API; the web follows in Task 2, the removal of the old setting in Task 3.
1. domain-name.ts: move the hostname regex constant HOSTNAME_PATTERN here unchanged and export it. In domains-settings.service.ts delete its local definition and import it from './domain-name' (its own nameserver normaliser stays until Task 3). In domains-orders.service.ts import it from './domain-name' instead of the settings service. autodns-parse.ts imports it from './domain-name' too (no dependency from the parser on a Nest service).
2. autodns-parse.ts (D-02): add exported parseProfileNameServers(data: unknown) returning `{ nameServers: string[]; keys: string[] }`. Doc comment in German stating: the key names of the standard nameservers in the AutoDNS user profile are not documented — [ASSUMED], recognised tolerantly, checked by H-1 on the demo checklist of quick 261008-h3t. Rules:
a. Collect entries: data may be an array of `{ profiles: [...] }` (spec), an array of flat `{ key, value }` items, or one object with `profiles`. Keep items whose key and value are strings. `keys` = distinct trimmed keys in first-seen order, at most 50, each cut to 60 characters.
b. Normalise a value: trim, lowercase, remove one trailing dot; accept it only if the whole result matches HOSTNAME_PATTERN (no token splitting for numbered keys).
c. Numbered keys: lowercase the key and search anywhere in it for the regex `(?:nameserver|name[_-]server|nserver|ns)[_.-]?(\d{1,2})(?!\d)`; the captured number is the position. Skip entries whose value is not a hostname (for example glue addresses).
d. If one position carries two different hostnames, return an empty list (never pick one). The same hostname twice at one position counts once.
e. Sort by position numerically, then drop repeated hostnames keeping the first occurrence.
f. Only if step c produced no hostname: keys matching exactly `^(?:default[_.-]?)?(?:nameservers?|name[_-]servers?|nservers?|ns)$` whose value split on `[\s,;]+` gives tokens that are ALL hostnames provide that list in written order; two such keys with different lists give an empty list.
g. No minimum here; the caller enforces it.
3. domains-orders.service.ts:
- Add ERR.noDefaultNameServers with code 'noDefaultNameServers' and message "In AutoDNS sind keine Standard-Nameserver hinterlegt. Bitte hinterlegen Sie mindestens zwei Nameserver als Standard in AutoDNS; bis dahin ist keine Registrierung möglich." (thrown as ConflictException, D-04).
- Private readProfileNameServers(credentials): exactly one callRaw GET to the path `/user/` + encodeURIComponent(credentials.user) + `/` + credentials.context + `/profile` — no query, no retry. A thrown error (for example the client's path check) becomes BadGatewayException with code 'autodnsError' and message "Tessera konnte die Standard-Nameserver nicht aus AutoDNS lesen."; a result with ok false is thrown via autodnsFailureToHttp. Parse with parseProfileNameServers; with fewer than MIN_NAMESERVERS (2) entries log one warning via this.logger.warn that starts with "Keine Standard-Nameserver im AutoDNS-Profil erkannt" and lists only the profile key names (never values, never credentials), then throw ERR.noDefaultNameServers. Otherwise return the list.
- Public getProfileNameServers(tenantId): getActiveCredentials (409 notConfigured unchanged), then readProfileNameServers; returns `{ environment, nameServers }`.
- createOrder (D-03): delete the use of the request's nameservers and the method normalizeNameServers plus MAX_NAMESERVERS (no other user). Directly after the existing-order check and before availabilityFor, call readProfileNameServers(credentials) and put the returned ordered list into payload.nameServers. Everything else (availability, contact check, write, summary with version) stays as is; the summary keeps returning payload.nameServers.
- submitOrder: the only change is the existing pre-claim guard — "payload missing" becomes "payload missing OR payload.nameServers.length below MIN_NAMESERVERS", same 400 orderInvalid. Do not touch the claim, the single POST, the body mapping (order preserved, never sorted), outcomeOfSubmit, recoverFailedOutcomeWrite, reconcile or cancelOrder.
- Adjust doc comments that mention nameservers coming from the settings or the browser.
4. dto/domains-order.dto.ts: remove the nameServers field from CreateDomainsOrderDto and the class-validator imports that become unused; doc comment: the nameservers come from AutoDNS, never from the browser. A browser still sending the field is stripped by the whitelist ValidationPipe.
5. domains.controller.ts: add handler getNameServers with `@Get('name-servers')` and `@ModuleManage('domains')`, calling this.orders.getProfileNameServers(this.requireTenantId(req)). Place it directly after checkAvailability in the static section (before every `:id` route). No role decorator. Add "Standard-Nameserver aus AutoDNS lesen" to the Verwalten list in the class doc comment.
6. Tests:
- autodns-parse.spec.ts: the parser cases from the behavior list, example hostnames only (example.org, example.net, example.com).
- domains-orders.service.spec.ts: extend makeHarness so GET calls whose URL ends with '/profile' are answered by a separate, overridable profile responder (default: envelope with one `{ profiles: [...] }` item listing ns3, ns1, ns4, ns2 out of order with example hostnames whose alphabetical order differs from the key order, plus a techc entry); these calls are still recorded in h.calls. Remove nameServers from the createOrder dto fixture and delete the "Nameserver %j -> BadRequest %s" table test (its rules are gone with D-02). Add the service, createOrder, tracer-chain and submit-guard tests from the behavior list. If an existing test counts all calls of a createOrder run, account for the one profile call explicitly rather than weakening the assertion.
- domains.controller.spec.ts: add 'getNameServers' to MANAGE_HANDLERS, `getProfileNameServers` to makeOrders, the route expectation `[0, 'name-servers']`, and a forwarding test.
- module-manage-handlers.spec.ts: add 'getNameServers' to the DomainsController list only; do not reformat the file (its organizeImports finding is pre-existing).
Commit: `feat(domains): Standard-Nameserver aus dem AutoDNS-Profil lesen (h3t)`.
</action>
<verify>
<automated>pnpm --filter @tessera/api exec vitest run src/domains module-manage-handlers rls-coverage rls-access-inventory && pnpm --filter @tessera/api exec tsc --noEmit && test -z "$(grep -nE '^\s*@Roles\(' apps/api/src/domains/domains.controller.ts)" && grep -q "@Get('name-servers')" apps/api/src/domains/domains.controller.ts && grep -q "Nameserver aus AutoDNS (h3t)" apps/api/src/domains/domains-orders.service.spec.ts && echo "tracer api ok"</automated>
</verify>
<done>Profile parser covered by tests; createOrder stores the AutoDNS list in AutoDNS order and the chained test proves the one POST /domain carries it unchanged; missing or unreadable profile nameservers block the draft with 409/502/504 and no row; drafts without two nameservers never reach the claim; GET name-servers is a Verwalten route before all :id routes; every existing domains test is green.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Registrieren shows the AutoDNS nameservers read-only, with hint and lock when missing</name>
<files>apps/web/src/lib/domains-api.ts, apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx, apps/web/src/app/(portal)/modules/domains/components/RegisterTab.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json</files>
<read_first>apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx, apps/web/src/app/(portal)/modules/domains/components/RegisterTab.test.tsx, apps/web/src/lib/domains-api.ts (lines 1-80 and 280-360), apps/web/src/messages/de.json and en.json (block domains.register)</read_first>
<behavior>
- With getNameServers resolving ['z.example.net', 'b.example.org', 'a.example.org'], the nameserver section lists exactly these in this DOM order, read-only: no textbox labelled 'Nameserver 1', no button 'Nameserver hinzufügen' (D-03)
- getNameServers rejecting with DomainsRequestError(409, 'noDefaultNameServers', …) shows an alert containing "In AutoDNS sind keine Standard-Nameserver hinterlegt"; after a free domain and chosen contacts, "Zusammenfassung anzeigen" is disabled and createOrder is never called (D-04)
- getNameServers rejecting with DomainsRequestError(502, 'autodnsAuth', 'Anmeldung bei AutoDNS fehlgeschlagen …') shows "Tessera konnte die Standard-Nameserver nicht aus AutoDNS lesen" plus the server text; "Erneut aus AutoDNS lesen" calls getNameServers again, and after success the list appears and the summary button becomes enabled
- createOrder is called with exactly { domain, ownerContactId, adminContactId, techContactId, zoneContactId } — no nameServers key (D-03)
- the summary shows the server's list in its order: "z.example.net, b.example.org, a.example.org"
- existing tests for double-click lock, submit error lock, orderChanged and cancel stay green
</behavior>
<action>
1. domains-api.ts: add interface DomainsNameServers `{ environment: DomainsEnvironment; nameServers: string[] }` and function getNameServers() that requests '/name-servers' (GET, same request helper as listOrders). Remove nameServers from CreateOrderInput (D-03: the server reads them itself). Leave the DomainsStatus and DomainsSettings types alone — Task 3 removes their old field.
2. RegisterTab.tsx (D-03, D-04):
- Remove the editable nameserver list completely: the MIN/MAX constants used only for it, initialNameServers, the nameServers state seeded from the status prop, the inputs and the add/remove buttons. resetAll no longer touches nameservers.
- New state for the AutoDNS list, a small union: loading, ok with list, missing, unreadable with detail. Load via getNameServers on mount and whenever status.environment changes, guarded by an alive flag like the contacts effect. DomainsRequestError with code 'noDefaultNameServers' -> missing; any other error -> unreadable with detail = the DomainsRequestError message, else tc('requestFailed').
- When missing or unreadable, render one hint with role="alert" at the top of the input view (above the domain section, so it is visible before anything is filled in): t('nameServersMissing') or t('nameServersUnreadable') followed by the detail line, plus a SECONDARY_BUTTON t('nameServersRetry') that reloads. The availability check stays usable.
- The nameserver section keeps its place (shown once the domain is free) and keeps the "Zusammenfassung anzeigen" footer; description t('nameServersDescription'); content: loading -> t('nameServersLoading'); ok -> an ordered list (ol) whose items show t('nameServer', { number }) and the hostname as plain text, nothing editable; missing or unreadable -> t('nameServersBlocked').
- canSummarize additionally requires the ok state with at least two entries.
- onSummary calls createOrder without nameservers.
- The summary row keeps summary.nameServers joined with ", " (server order = AutoDNS order). Update the component doc comment (nameservers come from AutoDNS, read-only).
3. de.json / en.json, block domains.register (identical keys in both; Sie-form; real umlauts):
- intro: "Prüfen Sie, ob eine Domain frei ist, wählen Sie die Kontakte und registrieren Sie die Domain verbindlich bei AutoDNS. Die Nameserver übernimmt Tessera aus AutoDNS."
- nameServersDescription: "Diese Standard-Nameserver sind in AutoDNS hinterlegt. Tessera verwendet sie unverändert und in dieser Reihenfolge; ändern lassen sie sich nur in AutoDNS."
- keep nameServer ("Nameserver {number}"); delete addNameServer and removeNameServer
- add nameServersLoading: "Nameserver werden aus AutoDNS gelesen …"
- add nameServersMissing: "In AutoDNS sind keine Standard-Nameserver hinterlegt. Bitte hinterlegen Sie mindestens zwei Nameserver als Standard in AutoDNS. Bis dahin ist keine Registrierung möglich."
- add nameServersUnreadable: "Tessera konnte die Standard-Nameserver nicht aus AutoDNS lesen. Bis das gelingt, ist keine Registrierung möglich." (discretion choice, see objective)
- add nameServersRetry: "Erneut aus AutoDNS lesen"
- add nameServersBlocked: "Ohne Standard-Nameserver aus AutoDNS ist keine Registrierung möglich – siehe Hinweis oben."
- English counterparts with the same meaning. Do not touch domains.settings.nameServers yet (Task 3).
4. RegisterTab.test.tsx: add a mockNameServers for getNameServers in the existing vi.mock (default resolves the example list from the behavior block); replace the prefill assertion and the "zwischen 2 und 6" test with the behavior cases; keep the status fixture as it is (Task 3 drops its old field).
Commit: `feat(domains): Registrieren zeigt Nameserver aus AutoDNS nur zur Kontrolle (h3t)`.
</action>
<verify>
<automated>pnpm --filter @tessera/web exec vitest run modules/domains src/messages && pnpm --filter @tessera/web exec tsc --noEmit && node -e 'const de=require("./apps/web/src/messages/de.json"),en=require("./apps/web/src/messages/en.json");const w=(o,p,r)=>{for(const[k,v]of Object.entries(o||{})){const q=p+"."+k;if(v&&typeof v==="object")w(v,q,r);else r[q]=v}return r};const a=w(de.domains,"domains",{}),b=w(en.domains,"domains",{});if(Object.keys(a).length<40||Object.keys(a).sort().join()!==Object.keys(b).sort().join()){console.error("key mismatch");process.exit(1)}if(!String(a["domains.register.nameServersMissing"]).startsWith("In AutoDNS sind keine Standard-Nameserver hinterlegt")){console.error("hint text");process.exit(1)}for(const v of [...Object.values(a),...Object.values(b)])if(/mandant|tenant|lizenz|licens/i.test(String(v))){console.error("bad text",v);process.exit(1)}console.log("web register ok")'</automated>
</verify>
<done>The Registrieren tab lists the AutoDNS nameservers read-only in AutoDNS order, shows the German hint with a retry button when they are missing or unreadable, keeps "Zusammenfassung anzeigen" disabled in that case, and no longer sends nameservers when creating a draft; de/en keys match; web domains tests and the umlaut guard are green.</done>
</task>
<task type="auto">
<name>Task 3: Remove the setting everywhere (DB column, API, web), update docs and changelog, rebuild</name>
<files>apps/api/prisma/schema.prisma, apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql, apps/api/src/domains/domains-settings.service.ts, apps/api/src/domains/domains-settings.service.spec.ts, apps/api/src/domains/domains.types.ts, apps/api/src/domains/dto/domains-settings.dto.ts, apps/api/src/domains/dto/domains-settings.dto.spec.ts, apps/web/src/lib/domains-api.ts, 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/components/RegisterTab.test.tsx, apps/web/src/messages/de.json, apps/web/src/messages/en.json, CHANGELOG.md, docs/anleitung-anwender.md, docs/anleitung-administration.md, docs/mandantentrennung-zugriffsklassifikation.md</files>
<read_first>apps/api/src/domains/domains-settings.service.ts, apps/api/src/domains/domains-settings.service.spec.ts, apps/api/src/domains/dto/domains-settings.dto.ts, apps/api/src/domains/dto/domains-settings.dto.spec.ts, apps/web/src/app/(portal)/modules/domains/components/SettingsTab.tsx (lines 1-40 and 340-509), apps/web/src/app/(portal)/modules/domains/domains-page.test.tsx (fixtures near lines 70-90, nameserver test near line 357), CHANGELOG.md lines 1-15, docs/anleitung-administration.md lines 362-372, docs/anleitung-anwender.md lines 174-180, docs/mandantentrennung-zugriffsklassifikation.md line 901</read_first>
<precondition>The local stack is up: `docker compose ps --status running --services` lists db (the rebuild below needs it).</precondition>
<reversibility rating="costly">Dropping the column discards the stored values (the user confirmed they are worthless); bringing it back would need a new migration.</reversibility>
<!-- planner-discipline-allow: defaultNameServers -->
<action>
1. Database (D-01): remove the field defaultNameServers from model DomainsConfig in schema.prisma. Create apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql with a short German header comment (quick-261008-h3t: die Standard-Nameserver kommen aus dem AutoDNS-Benutzerprofil; die Spalte wird nicht mehr gelesen, ihre Werte sind wertlos) and the single statement ALTER TABLE "DomainsConfig" DROP COLUMN "defaultNameServers"; — removal chosen over leaving the column unused because the module is unreleased, nothing reads the column after this task, and a dead column would mislead later work. No RLS change (no new table). Run `pnpm --filter @tessera/api exec prisma generate`.
2. API (D-01):
- domains-settings.service.ts: remove the nameserver constants, the HOSTNAME_PATTERN import, the field from ConfigRow, normalizeNameServers, the saveSettings branch, getDefaultNameServers (no caller since Task 1) and the field in toSettingsView and getStatus; update the class doc comment (no Standard-Nameserver any more). Keep the remaining two forTenant raw hits (loadRow, saveSettings) unchanged so the access inventory stays correct.
- domains.types.ts: remove the field from DomainsSettingsView and DomainsStatusView.
- dto/domains-settings.dto.ts: remove the field and the class-validator imports that become unused.
- Specs: domains-settings.service.spec.ts drops the fixture fields and the nameserver test and adjusts view expectations; add one assertion that getSettings and getStatus return objects without any nameserver property. dto spec: remove the field from the valid body and from the null list.
3. Web (D-01):
- domains-api.ts: remove the field from DomainsStatus, DomainsSettings and SaveDomainsSettingsInput.
- SettingsTab.tsx: delete NameServersCard, padNameServers, the row constants, its render line and imports that become unused.
- de.json and en.json: delete the block domains.settings.nameServers in both.
- domains-page.test.tsx: drop the fixture fields and the test "Nameserver: speichert die bereinigte Liste"; add a test that the Einstellungen tab renders no "Standard-Nameserver" heading and no element with id domains-nameservers.
- RegisterTab.test.tsx: drop the old field from the status fixture.
4. Docs and changelog (D-05; Sie-form in user docs as before; never name the user's concrete nameserver hostnames anywhere):
- docs/anleitung-administration.md, section "Domains: AutoDNS anbinden": replace the bullet "Standard-Nameserver" with a bullet "Nameserver kommen aus AutoDNS": Tessera has no own nameserver setting; for every registration it reads the standard nameservers from the AutoDNS user profile of the AutoDNS user entered in the settings (also values inherited from a parent user) and uses them unchanged, in the order stored there; store at least two there; they must be set up, because for .de domains the DENIC checks them at registration; if Tessera finds none or cannot read them, the Registrieren tab shows a hint and registration is not possible. In the bullet "Eigener AutoDNS-Benutzer" add that the user must be able to read its own user profile.
- docs/anleitung-anwender.md, "Eine Domain registrieren" step 2: replace the sentence about prefilled nameservers ("zwei bis sechs") with: below the contacts you see, for control only, the nameservers stored as standard in AutoDNS, in the order stored there; they can only be changed in AutoDNS; if none are stored, a hint appears and registration is locked. Step 3 (summary lists Nameserver) stays.
- CHANGELOG.md under "Unveröffentlicht", adapt the existing Domains lines, no new line: in the line "Domains, Anbindung an AutoDNS" delete the closing sentence about Standard-Nameserver being prefilled; in the line "Domains, Registrieren" replace "Kontakte und Nameserver wählen" with "Kontakte wählen; die Nameserver übernimmt Tessera unverändert und in derselben Reihenfolge aus den Standardwerten in AutoDNS (sind dort keine hinterlegt, ist die Registrierung gesperrt)".
- docs/mandantentrennung-zugriffsklassifikation.md line 901 (row domains-settings.service.ts): remove ", Standard-Nameserver" from the description and append "Spalte für Standard-Nameserver mit quick-261008-h3t entfernt (Migration 20261008160000)." Keep the table columns unchanged.
- Do not edit .planning/quick/261008-dts-*/261008-dts-SUMMARY.md.
5. Run `pnpm exec biome check` on the touched source directories apps/api/src/domains and "apps/web/src/app/(portal)/modules/domains" plus apps/web/src/lib/domains-api.ts (not on module-manage-handlers.spec.ts, whose finding is pre-existing) and fix what it reports.
6. Rebuild locally: `docker compose up -d --build api web`; wait until the api answers on http://localhost:3001 and its log shows the migrations applied without error. Nothing is pushed and nothing is deployed to the test server.
Commit: `refactor(domains): Einstellung Standard-Nameserver entfernt, Doku angepasst (h3t)`.
</action>
<verify>
<automated>pnpm --filter @tessera/api test && pnpm --filter @tessera/web test && pnpm --filter @tessera/api exec tsc --noEmit && pnpm --filter @tessera/web exec tsc --noEmit && ! grep -rn defaultNameServers apps/api/src apps/web/src && ! grep -rnE "ns14|ns2\.ctl" apps/api/src apps/web/src docs CHANGELOG.md && grep -q 'DROP COLUMN "defaultNameServers"' apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql && grep -q "Nameserver kommen aus AutoDNS" docs/anleitung-administration.md && grep -q "Standardwerten in AutoDNS" CHANGELOG.md && node -e 'const de=require("./apps/web/src/messages/de.json"),en=require("./apps/web/src/messages/en.json");const w=(o,p,r)=>{for(const[k,v]of Object.entries(o||{})){const q=p+"."+k;if(v&&typeof v==="object")w(v,q,r);else r[q]=v}return r};const a=w(de.domains,"domains",{}),b=w(en.domains,"domains",{});if(Object.keys(a).sort().join()!==Object.keys(b).sort().join()||Object.keys(a).some(k=>k.startsWith("domains.settings.nameServers"))){console.error("keys");process.exit(1)}' && docker compose ps --status running --services | grep -qx api && docker compose ps --status running --services | grep -qx web && test "$(docker compose exec -T db psql -U tessera -d tessera -tAc "SELECT count(*) FROM information_schema.columns WHERE table_name='DomainsConfig' AND column_name='defaultNameServers'")" = "0" && A=$(mktemp) && curl -sf -c "$A" -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' http://localhost:3001/auth/login >/dev/null && MID=$(curl -sf -b "$A" http://localhost:3001/modules/catalog | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const m=JSON.parse(s).find(x=>x.slug==="domains");if(!m)process.exit(1);process.stdout.write(m.isActiveForTenant?"":m.id)})') && { [ -z "$MID" ] || curl -sf -b "$A" -X POST "http://localhost:3001/modules/$MID/activate" >/dev/null; } && S=$(curl -sf -b "$A" http://localhost:3001/modules/domains/settings) && echo "$S" | grep -q '"hasPassword"' && ! echo "$S" | grep -q defaultNameServers && C=$(curl -s -o /dev/null -w '%{http_code}' -b "$A" http://localhost:3001/modules/domains/name-servers) && case "$C" in 200|409|502|504) echo "final gates ok (name-servers $C)";; *) echo "name-servers answered $C"; exit 1;; esac</automated>
<human-check>End of task, with Demo credentials entered by the user (record as checklist H-1..H-4 in the SUMMARY; not run by the executor): H-1 [ASSUMED] key names — Registrieren tab lists the Demo user's standard nameservers in the AutoDNS order; if instead the hint "In AutoDNS sind keine Standard-Nameserver hinterlegt" appears although AutoDNS has values, read the warning line "Keine Standard-Nameserver im AutoDNS-Profil erkannt" in `docker compose logs api` (it lists the key names) and adapt parseProfileNameServers. H-2 the AutoDNS user may read its own profile (no 403; a 403 shows the unreadable hint). H-3 a demo registration sends the shown nameservers and AutoDNS accepts them explicitly. H-4 Live: the list matches the four values the user named, in that order. H-x replaces item 8 (A8) of the 261008-dts demo checklist, whose step "Standard-Nameserver in den Einstellungen eintragen" no longer exists.</human-check>
</verify>
<done>No "Standard-Nameserver" card in Einstellungen, no nameserver field in settings/status API and web types, column dropped by migration 20261008160000 and absent in the local DB; docs and the existing CHANGELOG Domains lines describe nameservers from AutoDNS; full api and web suites, both tsc runs and biome on touched files are green; the rebuilt stack serves GET name-servers without 404/500.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| browser -> API | Manager-controlled request bodies for draft creation and submit; the nameserver list must not be taken from here |
| API -> AutoDNS | Profile read (new GET) and the existing single POST /domain; AutoDNS answers are untrusted input to the parser |
| API -> logs | New warning line when no nameservers are recognised |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-h3t-01 | Tampering | createOrder request body | high | mitigate | Field removed from CreateDomainsOrderDto (whitelist strips it); server reads the profile itself; test proves a sent list is ignored (Task 1) |
| T-h3t-02 | Tampering | draft vs. confirmed order | high | mitigate | List stored in the draft payload; summary returns it; WR-02 `updatedAt` binding in the claim unchanged; re-creating a draft re-reads the profile and changes the version (Task 1 test) |
| T-h3t-03 | Tampering | profile path built from the stored AutoDNS user name | medium | mitigate | `encodeURIComponent` on the user name plus the client's existing path check; a thrown path error becomes 502 autodnsError, never a request to another path (Task 1) |
| T-h3t-04 | Repudiation / integrity | guessing nameservers | high | mitigate | Ambiguous or fewer than two recognised values -> 409 noDefaultNameServers, no draft; submit guard rejects drafts with fewer than two nameservers before the claim; web keeps the summary button disabled (Tasks 1, 2) |
| T-h3t-05 | Information Disclosure | warning log line | low | mitigate | Logs only profile key names (max 50, 60 chars each), never values or credentials (Task 1) |
| T-h3t-06 | Denial of Service | extra AutoDNS calls | low | accept | One profile GET per Registrieren load and per draft, Verwalten only, through the shared 350 ms limiter, no retry |
| T-h3t-07 | Elevation of Privilege | GET name-servers | medium | mitigate | `@ModuleManage('domains')`, no role decorator, class-level `@UseModule`; controller spec and module-manage-handlers spec assert it (Task 1) |
| T-h3t-08 | Tampering | money-safety path | critical | mitigate | Claim, single POST, outcome mapping, UNKNOWN recovery, reconcile and cancel untouched; all existing submit/claim/reconcile tests must pass unchanged (Task 1 verify, Task 3 full suite) |
| T-h3t-SC | Tampering | npm/pip/cargo installs | high | accept | No package installs in this plan; nothing to audit |
</threat_model>
<verification>
- Task 1: api domains specs, module-manage-handlers, rls-coverage, rls-access-inventory and api tsc green; tracer chain test present; no role decorator in the controller.
- Task 2: web domains tests and message guards green, web tsc green, de/en key parity, hint text starts with the requested sentence.
- Task 3: full api and web suites, both tsc runs, biome on touched files, no old field in apps/*/src, no company-specific nameserver hostnames in code, docs or changelog, migration present and applied (column absent in the local DB), rebuilt api/web running, GET name-servers answers 200/409/502/504.
</verification>
<success_criteria>
- The setting "Standard-Nameserver" is gone from UI, API, DTOs, types and database (D-01).
- Registration uses only the nameservers AutoDNS holds in the user profile, recognised by one tested function with the [ASSUMED] key rule (D-02).
- Registrieren and the summary show them read-only in AutoDNS order; the draft stores them, the one POST /domain sends them in that order, WR-02 still binds the confirmation (D-03).
- Without readable nameservers, a clear German hint appears and registration is locked in browser and server (D-04).
- Docs and the existing CHANGELOG Domains lines updated; 261008-dts SUMMARY untouched (D-05).
- Money safety unchanged; every pre-existing test still green.
</success_criteria>
<output>
Create `.planning/quick/261008-h3t-domains-nameserver-aus-autodns-statt-tes/261008-h3t-SUMMARY.md` when done. Include: commits per task, test/gate measurements, the demo checklist H-1..H-4 from the Task 3 human-check (H-1 marked [ASSUMED], with the log-line instructions), the note that it supersedes item 8 of the 261008-dts checklist, and browser steps for the orchestrator (dark mode): Einstellungen without a nameserver card; Registrieren with the read-only list or the hint plus "Erneut aus AutoDNS lesen" and a disabled "Zusammenfassung anzeigen".
</output>
@@ -0,0 +1,96 @@
---
phase: quick-261008-h3t
plan: 01
subsystem: domains
tags: [autodns, nameserver, domains, migration]
requires:
- quick-261008-dts (Modul Domains)
provides:
- Standard-Nameserver kommen aus dem AutoDNS-Benutzerprofil (GET /user/{user}/{context}/profile)
- GET modules/domains/name-servers (Verwalten)
affects:
- apps/api/src/domains
- apps/web Registrieren und Einstellungen
tech-stack:
added: []
patterns:
- tolerante Profilauswertung in einer Funktion (parseProfileNameServers)
key-files:
created:
- apps/api/prisma/migrations/20261008160000_domains_drop_default_nameservers/migration.sql
modified:
- apps/api/src/domains/autodns-parse.ts
- apps/api/src/domains/domain-name.ts
- apps/api/src/domains/domains-orders.service.ts
- apps/api/src/domains/domains.controller.ts
- apps/api/src/domains/domains-settings.service.ts
- apps/api/src/domains/dto/domains-order.dto.ts
- apps/api/src/domains/dto/domains-settings.dto.ts
- apps/api/prisma/schema.prisma
- apps/web/src/app/(portal)/modules/domains/components/RegisterTab.tsx
- apps/web/src/app/(portal)/modules/domains/components/SettingsTab.tsx
- apps/web/src/lib/domains-api.ts
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- CHANGELOG.md
- docs/anleitung-anwender.md
- docs/anleitung-administration.md
- docs/mandantentrennung-zugriffsklassifikation.md
decisions:
- Spalte DomainsConfig.defaultNameServers per Migration entfernt (Modul unveroeffentlicht, Werte wertlos)
- Bei Lesefehler eigener Hinweis "nicht aus AutoDNS lesen" statt "keine hinterlegt"; beide sperren die Registrierung
- Weniger als zwei erkannte Nameserver gelten als nicht hinterlegt; keine Kuerzung, kein Raten
status: complete
actuals:
tokens: 60000
tasks: 3
commits: 3
plan_head_before: 95d625bd25e6713fb98a29b27bb08b8efe2ab8b0
plan_head_after: 2176f8ecce233a5e3c6416e866f73d2a8823a37e
completed: 2026-10-08
---
# Phase quick-261008-h3t Plan 01: Domains, Nameserver aus AutoDNS Summary
Tessera liest die Standard-Nameserver der Registrierung aus dem AutoDNS-Benutzerprofil (tolerante Schluessel-Erkennung), zeigt sie nur lesend in AutoDNS-Reihenfolge, speichert sie im Entwurf und sendet sie unveraendert im einen POST /domain; die Tessera-Einstellung samt DB-Spalte ist entfernt.
## Commits
| Task | Commit | Beschreibung |
| ---- | ------ | ------------ |
| 1 (tracer) | 473738d | feat(domains): Standard-Nameserver aus dem AutoDNS-Profil lesen (h3t) |
| 2 | 1b20b84 | feat(domains): Registrieren zeigt Nameserver aus AutoDNS nur zur Kontrolle (h3t) |
| 3 | 2176f8e | refactor(domains): Einstellung Standard-Nameserver entfernt, Doku angepasst (h3t) |
## Messungen
- API-Suite: 135 Dateien, 2498 Tests gruen; Web-Suite: 132 Dateien, 1458 Tests gruen.
- tsc (api, web) ohne Fehler; biome check auf den beruehrten Verzeichnissen sauber.
- de/en-Schluesselparitaet gruen; kein `defaultNameServers` mehr in apps/*/src; keine firmenspezifischen Nameserver-Namen in Code, Docs, Changelog.
- Tracer-Kette (Entwurf -> Bestaetigung -> genau ein POST /domain mit Profil-Nameservern in Profil-Reihenfolge) als Test "Nameserver aus AutoDNS (h3t)" gruen.
- Neu gebauter Stack: Migration 20261008160000 angewendet, Spalte in der lokalen DB weg, GET settings ohne Nameserver-Feld, GET name-servers antwortet 409 notConfigured (lokal ist kein AutoDNS-Zugang hinterlegt), GET status 200.
## Deviations from Plan
None - plan executed exactly as written. Hinweis: ein einzelner Web-Test (domains-page, "Speichern") fiel in einem Lauf unter Last durch (Zeitueberschreitung) und war im Einzel- und Wiederholungslauf gruen; unveraendert, nicht von dieser Aenderung verursacht.
## Known Stubs
None.
## Demo-Checkliste (vom Benutzer mit Demo-Zugang auszufuehren, nicht vom Executor)
- H-1 [ASSUMED] Schluesselnamen: Der Reiter Registrieren listet die Standard-Nameserver des Demo-Benutzers in AutoDNS-Reihenfolge. Erscheint stattdessen "In AutoDNS sind keine Standard-Nameserver hinterlegt", obwohl AutoDNS Werte hat, die Warnzeile "Keine Standard-Nameserver im AutoDNS-Profil erkannt" in `docker compose logs api` lesen (sie nennt die Schluesselnamen) und `parseProfileNameServers` anpassen.
- H-2 Der AutoDNS-Benutzer darf sein eigenes Profil lesen (kein 403; ein 403 zeigt den Hinweis "nicht aus AutoDNS lesen").
- H-3 Eine Demo-Registrierung sendet die angezeigten Nameserver und AutoDNS akzeptiert sie ausdruecklich.
- H-4 Live: die Liste entspricht den vier vom Benutzer genannten Werten in dieser Reihenfolge.
- H-x ersetzt Punkt 8 (A8) der Checkliste von 261008-dts, dessen Schritt "Standard-Nameserver in den Einstellungen eintragen" entfaellt.
## Browser-Schritte fuer den Orchestrator (Dunkelmodus)
1. Domains -> Einstellungen: keine Karte "Standard-Nameserver" mehr.
2. Domains -> Registrieren, Domain pruefen: entweder schreibgeschuetzte Nameserver-Liste in AutoDNS-Reihenfolge, oder (lokal ohne Zugang) Hinweis oben mit "Erneut aus AutoDNS lesen" und gesperrtem "Zusammenfassung anzeigen".
## Self-Check: PASSED
Commits 473738d, 1b20b84, 2176f8e liegen auf main; Migration und Parser vorhanden.