Files
nexarch/docs/QA-09-PRUEFPROTOKOLL.md
rootandClaude Sonnet 5 109bfd2757
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
AI-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
QA-09: Pruefprotokoll (schnittstellen-vertragstests)
Dokumentiert die 24h-Dauerlauf-Abweichung (komprimierter Ersatztest statt
woertlichem 24h-Lauf, User-autorisiert), alle drei Akzeptanzkriterien mit
Zielwert/Messwert/Bewertung, den gefundenen und behobenen Goroutine-Leck
in mailimport.Dialer.connect, sowie den Regressionsnachweis ueber die
vollstaendige DMS-Testsuite und die QA-09-relevanten Root-Modul-Pakete.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-15 21:56:02 +00:00

18 KiB
Raw Permalink Blame History

QA-09 – Prüfprotokoll: Schnittstellen-Vertragstests

WICHTIGE, AUSDRÜCKLICH AUTORISIERTE ABWEICHUNG VOM TICKET

Das Ticket QA-09 verlangt unter "Prüfungen vor der Abnahme" wörtlich:

  1. Dauerlauf-Import über 24 Stunden ohne Verlust

Ein echter 24-Stunden-Dauerlauf ist innerhalb einer einzelnen Claude-Code-Sitzung nicht durchführbar (keine 24-Stunden-Sitzungsbindung). Diese Abweichung wurde vom Nutzer für diese Sitzung ausdrücklich autorisiert (gleiches Vorgehen wie bei anderen Tickets dieses Projekts mit zeitbasierten Dauerlauf-Anforderungen).

Ersatzprüfung in diesem Protokoll: ein komprimierter Dauerlauf mit vielen Import-Zyklen hintereinander statt vieler Stunden Wartezeit — die Garantie "kein Dokumentverlust über viele Zyklen, keine Verschlechterung bei Wiederholung" ist unabhängig von der Zeitspanne, über die sich diese Zyklen in der Praxis verteilen würden:

  • E-Mail-Import (INT-04): 30 Postfach-Abrufzyklen, je 3 Nachrichten mit Anhang = 90 Nachrichten / 180 erwartete Dokumente.
  • Hot-Folder-Import (INT-06): 25 Stapel-Einlieferungszyklen, je 3 Trennseiten-Teildokumente = 75 erwartete Dokumente, plus eine erneute Einlieferung des letzten Stapels zum Dublettennachweis.
  • Watch-Folder-Import (INT-08): 40 Scan-Zyklen, je 4 Dateien = 160 erwartete Dokumente.

Der echte 24-Stunden-Produktionsnachweis (z. B. als überwachter Dauerbetrieb auf einem Testsystem) bleibt ein separater, späterer operativer Schritt und ist nicht Bestandteil dieser Abnahme.

Ergebnis der Ersatzprüfung — nicht folgenlos: Der komprimierte Dauerlauf hat ein reales Ressourcenleck in dms/internal/mailimport/imap.go aufgedeckt (siehe Abschnitt "Fund während der Prüfung" unten) und behoben. Genau das ist der Sinn dieser Art Prüfung — siehe Einzelheiten weiter unten.


Ausgangslage und Rahmenbedingungen dieser Prüfung

  • Arbeitsumgebung: Host 192.168.1.204, frisches Wegwerf-Verzeichnis /root/qa-09-work-1792099372/repo (Clone von https://gitea.perlbach24.de/scripte/nexarch.git).
  • Branch-Basis: main enthält alle in der Ausgangslage genannten Voraussetzungen außer INT-12 (AI-Anbindung, DMS-Variante, dms/internal/moduletypeclient + ai/internal/moduletypeapi) — dieser Code liegt bislang nur auf dem unabhängigen, noch nicht gemergten Branch feature/int-12-ai-anbindung-objekttyp-registrierung-kopplung-ai-gov-01 (ein einzelner Commit 3589644 direkt auf dem aktuellen main-Stand dac7fff, sonst identisch zu main). Da QA-09 INT-12 ausdrücklich als Prüfgegenstand nennt, wurde der QA-09-Branch von diesem INT-12-Branch-Tip abgezweigt statt von main direkt — inhaltlich identisch zu "main plus genau der eine INT-12-Commit", keine sonstige Abweichung.
  • Branch: feature/qa-09-schnittstellen-vertragstests
  • Testdatenbank: eigene Wegwerf-Rolle/DB auf Host 204 (qa09_test / dms_qa09_test, Passwort nur in /etc/nexarch-dev/qa09-test.env, root-only). Jeder Testlauf legt zusätzlich pro Paket/Test ein eigenes Postgres-Schema an (Muster dms/internal/testenv, dms/internal/testdb) — keine Kollision mit anderen Sitzungen oder der archivdms-Produktivdatenbank auf demselben Host.
  • CMIS (INT-09): hat mit dms/internal/cmis/parity_test.go und arch_test.go bereits eine vollständige, laufende Vertragsprüfung (CMIS-1.1- Browser-Binding-Konformität plus Architekturregel "nur über die REST-Kette"). Diese wurde nicht dupliziert (Ticket-Vorgabe: "Baue auf ihnen auf, statt Funktionalität zu duplizieren").

Akzeptanzkriterium 1: Vertragstests laufen in der Pipeline

Umsetzung: dms/internal/contracttest (neues Paket, Muster identisch zu internal/contracttest auf dem Core-Board/QA-07 und archive/internal/ contracttest) — vier Verträge, je mit Hauptprüfung + DetectsBreakingChange- Gegenprobe:

Vertrag Datei Prüft
INT-04 (E-Mail-Regeln) mailrule_test.go JSON-Schema der echten mailimportapi-Antwort gegen die in INT-04 dokumentierten Pflichtfelder (u. a. attachment_mime für den Anhangtyp-Zugriff)
INT-11 (Archive-Anbindung) retentionclient_test.go Tatsächlich gesendete Registrierungs-Nutzlast von retentionclient.Client.Register gegen einen echten HTTP-Testempfänger
INT-12 (AI-Anbindung) moduletypeclient_test.go Tatsächlich gesendete Nutzlast von moduletypeclient.Client.Register, inkl. KI-relevanter Feld-Liste (fields[].name/kind)
INT-13 (Workflow-Anbindung) workflowevent_test.go Registrierungs- und Ereignis-Zustellungs-Nutzlast von workflowevent.Register/workflowevent.Handler

Zusätzlich (Core-Board, da internal/webhook nur im Core-Modul liegt): internal/contracttest/webhook_retry_test.go für API-07 (siehe Akzeptanzkriterium 3).

Pipeline-Nachweis (kein neuer Workflow nötig): Diese Pakete sind normale Go-Testpakete ohne Build-Tags. Die bestehende CI-Pipeline deckt sie bereits ab, ohne Änderung an .gitea/workflows/:

  • .gitea/workflows/ci.yml, Job dms-build-test: go build ./... && go vet ./... && go test ./... -p 1 -count=1 im Arbeitsverzeichnis dms — läuft bei jedem Push/PR und schließt dms/internal/contracttest, dms/internal/mailimport, dms/internal/watchfolder automatisch ein.
  • .gitea/workflows/ci.yml, Job go-core: go test ./... im Root-Modul — schließt internal/contracttest (inkl. webhook_retry_test.go) automatisch ein.
  • .gitea/workflows/contract-tests.yml triggert zusätzlich explizit auf push-Änderungen unter internal/contracttest/** sowie auf jeden Pull Request (pull_request: {} ohne Pfadfilter).

Zielwert: Ein entferntes/umbenanntes Pflichtfeld in einer der vier DMS-Schnittstellen oder bei der Webhook-Zustellung lässt den jeweiligen Vertragstest fehlschlagen → CI-Job rot.

Messwert: Alle 9 neuen Vertragstests (4 Hauptprüfungen + 4 DetectsBreakingChange-Gegenproben + 1 Webhook-Signatur-Altbestand) laufen grün; jede DetectsBreakingChange-Gegenprobe wurde selbst gegen eine absichtlich verstümmelte Fixture geprüft und schlägt dort nachweislich fehl (siehe Testausgabe unten) — das beweist, dass die Prüfung bei einer echten Abweichung tatsächlich auslöst, nicht nur im Erfolgsfall grün ist.

Bewertung: erfüllt.

$ go test ./internal/contracttest/... -v -count=1     (im Verzeichnis dms/)
=== RUN   TestContract_INT04_MailRuleSchema
--- PASS: TestContract_INT04_MailRuleSchema (0.48s)
=== RUN   TestContract_INT04_DetectsBreakingChange_RemovedAttachmentMimeField
--- PASS: TestContract_INT04_DetectsBreakingChange_RemovedAttachmentMimeField (0.00s)
=== RUN   TestContract_INT12_ModuleTypeRegisterPayload
--- PASS: TestContract_INT12_ModuleTypeRegisterPayload (0.00s)
=== RUN   TestContract_INT12_DetectsBreakingChange_RenamedFieldKind
--- PASS: TestContract_INT12_DetectsBreakingChange_RenamedFieldKind (0.00s)
=== RUN   TestContract_INT11_RetentionRegisterPayload
--- PASS: TestContract_INT11_RetentionRegisterPayload (0.00s)
=== RUN   TestContract_INT11_DetectsBreakingChange_RemovedObjectTypeField
--- PASS: TestContract_INT11_DetectsBreakingChange_RemovedObjectTypeField (0.00s)
=== RUN   TestContract_INT13_WorkflowRegisterPayload
--- PASS: TestContract_INT13_WorkflowRegisterPayload (0.00s)
=== RUN   TestContract_INT13_DetectsBreakingChange_RemovedTriggersField
--- PASS: TestContract_INT13_DetectsBreakingChange_RemovedTriggersField (0.00s)
=== RUN   TestContract_INT13_WorkflowEventPublishPayload
--- PASS: TestContract_INT13_WorkflowEventPublishPayload (0.00s)
PASS
ok  	gitea.perlbach24.de/scripte/nexarch/dms/internal/contracttest	0.488s

Akzeptanzkriterium 2 / Prüfung 2: Import über E-Mail und Hot-Folder verliert keine Dokumente (komprimierter Dauerlauf)

Umsetzung: zwei neue Testdateien, jeweils im selben Paket wie der zu prüfende Code (keine Duplizierung bestehender Testinfrastruktur wie dem Fake-IMAP-Server oder der Guard-/Stapel-Testumgebung, die beide nur paketintern nutzbar sind):

  • dms/internal/mailimport/qa09_endurance_test.go — TestQA09_MailImport_ManyPollCyclesNoDocumentLossNoLeak
  • dms/internal/watchfolder/qa09_endurance_test.go — TestQA09_WatchFolder_ManyCyclesNoDocumentLossNoLeak (INT-08) und TestQA09_HotFolder_ManyStacksNoDocumentLossNoDuplicates (INT-06)

Zielwert: nach N Import-Zyklen entspricht die tatsächliche Dokumentanzahl in der Datenbank exakt der Anzahl eingelieferter Dateien/Nachrichten-Teile (keine Lücke, keine Dublette), der Quellordner ist restlos leer (abgesehen von den vorgesehenen error/duplicate-Unterordnern), und die Goroutine-Anzahl vor/nach dem Dauerlauf bleibt im selben Bereich (Toleranz: höchstens +10, um normale Laufzeit-Schwankungen von Go/Test-Runtime nicht fälschlich als Leck zu werten).

Messwert (nach Behebung des unten beschriebenen Fundes):

$ go test ./internal/mailimport/... -run TestQA09 -v -count=1
=== RUN   TestQA09_MailImport_ManyPollCyclesNoDocumentLossNoLeak
--- PASS: TestQA09_MailImport_ManyPollCyclesNoDocumentLossNoLeak (11.11s)
PASS
ok  	gitea.perlbach24.de/scripte/nexarch/dms/internal/mailimport	11.113s

$ go test ./internal/watchfolder/... -run TestQA09 -v -count=1
=== RUN   TestQA09_WatchFolder_ManyCyclesNoDocumentLossNoLeak
--- PASS: TestQA09_WatchFolder_ManyCyclesNoDocumentLossNoLeak (5.56s)
=== RUN   TestQA09_HotFolder_ManyStacksNoDocumentLossNoDuplicates
--- PASS: TestQA09_HotFolder_ManyStacksNoDocumentLossNoDuplicates (2.69s)
PASS
ok  	gitea.perlbach24.de/scripte/nexarch/dms/internal/watchfolder	8.287s
  • E-Mail: 90 Nachrichten über 30 Abrufzyklen → 180 Dokumente erwartet, 180 tatsächlich (90 .eml-Kontextdateien + 90 Anhänge), 0 Zurückweisungen.
  • Watch-Folder: 160 Dateien über 40 Zyklen → 160 Dokumente, Quellordner leer, keine Ablehnung.
  • Hot-Folder: 25 Stapel à 3 Teile über Trennseiten → 75 Dokumente, 0 Einträge im Fehler-/Duplikat-Ordner; eine absichtlich wiederholte Einlieferung des letzten Stapels erzeugt nachweislich keine zusätzliche Dublette (Prüfung 3 aus INT-06, hier zusätzlich über viele vorausgegangene Zyklen hinweg bestätigt statt nur isoliert).

Bewertung: erfüllt.

Fund während der Prüfung: Goroutine-Leck in dms/internal/mailimport/imap.go

Der komprimierte Dauerlauf-Test für den E-Mail-Import schlug beim ersten Lauf fehl:

qa09_endurance_test.go:82: goroutine-anzahl vor/nach 30 poll-zyklen: 5 -> 35
    (hinweis auf ressourcenleck, z.b. nicht geschlossene imap-sitzungen)

Ursache: Dialer.connect startete pro IMAP-Verbindung eine Goroutine, die auf <-ctx.Done() wartet, um die Verbindung bei Abbruch zu beenden:

s := &session{c: c}
go func() {
    <-ctx.Done()
    _ = c.Terminate()
}()

ctx ist hier der vom Aufrufer übergebene Kontext (Importer.Poll wird in Produktion und in den Tests regelmäßig mit context.Background() aufgerufen, der nie "Done" wird). Die Goroutine wartete damit für immer auf einen Kanal, der nie etwas liefert — unabhängig davon, ob session.close() die Verbindung längst regulär beendet hatte. Jeder Postfach-Abruf hinterließ so dauerhaft eine Goroutine; bei einem echten 24-Stunden-Dauerbetrieb mit mehreren Postfächern im Minutentakt hätte sich das über die Zeit messbar aufgebaut (genau die "Verschlechterung bei Wiederholung", die Prüfung 2 ausschließen soll).

Fix (dms/internal/mailimport/imap.go): die Beobachtungs-Goroutine beobachtet jetzt einen von ctx abgeleiteten, an die Sitzung gebundenen Kontext, der in session.close() immer abgebrochen wird — das externe Abbruchverhalten (Terminate bei Kontext-Stornierung während eines laufenden IMAP-Aufrufs) bleibt dabei unverändert erhalten:

sessCtx, cancel := context.WithCancel(ctx)
s := &session{c: c, cancel: cancel}
go func() {
    <-sessCtx.Done()
    _ = c.Terminate()
}()
...
func (s *session) close() {
    _ = s.c.Logout()
    _ = s.c.Terminate()
    s.cancel()
}

Nach dem Fix: Goroutine-Anzahl bleibt über 30 Zyklen stabil (siehe Testausgabe oben, Test läuft grün). Zusätzlich per runtime.Stack(..., true) in einer Wegwerf-Probe (nicht Teil des Commits) verifiziert, dass vor dem Fix exakt so viele dauerhaft blockierte chan receive (nil chan)-Goroutinen in Dialer.connect.func1 vorlagen wie Poll-Zyklen durchlaufen wurden — nach dem Fix verschwanden sie vollständig.

Die vollständige bestehende mailimport-Testsuite (20 Testfunktionen, u. a. TLS/StartTLS, abgebrochene Verbindungen, parallele Postfächer) läuft nach dem Fix weiterhin grün (siehe Abschnitt "Gesamt-Testlauf" unten).


Akzeptanzkriterium 3 / Prüfung 3: Wiederholungsverhalten bei Ausfall belegt

Umsetzung: internal/contracttest/webhook_retry_test.go (Core-Modul, da internal/webhook — API-07 — nur dort liegt; DMS ist ein eigenes Go-Modul und kann Core-interne Pakete nicht importieren, dasselbe Argument wie bei dms/internal/retentionclient/moduletypeclient für Archive/AI).

Die bestehende Core-Testsuite (internal/webhook/webhook_test.go) belegt bereits den dauerhaften Fehlschlag nach Erreichen von MaxAttempts (TestProcessDue_RetriesWithBackoffThenMarksFailed). QA-09 verlangt zusätzlich den für ein konsumierendes Modul wie DMS praxisrelevanten Fall: ein Ziel, das zeitweise nicht erreichbar ist und danach wieder antwortet, wird trotzdem noch zugestellt — das ist die neue Prüfung hier.

Aufbau: ein httptest.Server antwortet auf die ersten zwei Zustellversuche mit 503 Service Unavailable, ab dem dritten Versuch mit 200 OK. Der echte webhook.Dispatcher (kleine Backoff-Zeit für den Test, reale Retry-/Backoff- Logik unverändert) ruft ProcessDue wiederholt auf, bis entweder zugestellt oder endgültig fehlgeschlagen.

Zielwert: der Zustellungsdatensatz erreicht status = delivered, und es wurden tatsächlich mehr als 2 HTTP-Aufrufe gegen das Ziel unternommen (echter Retry, nicht Zufall/Direkttreffer).

Messwert:

$ go test ./internal/contracttest/... -run TestContract_API07_RetryRecoversAfterTransientFailures -v -count=1   (im Root-Modul)
=== RUN   TestContract_API07_RetryRecoversAfterTransientFailures
--- PASS: TestContract_API07_RetryRecoversAfterTransientFailures (0.27s)
PASS
ok  	gitea.perlbach24.de/scripte/nexarch/internal/contracttest	0.351s

Zustellung erreichte status = delivered nach 3 tatsächlichen HTTP-Aufrufen (2 Fehlschläge mit exponentiellem Backoff, danach Erfolg) — die Retry-Logik aus API-07 greift nachweislich.

Bewertung: erfüllt.


Prüfung 1 (separat benannt im Ticket): Abweichung zwischen Beschreibung und Verhalten bricht die Pipeline

Siehe Akzeptanzkriterium 1 oben — die vier DetectsBreakingChange-Gegenproben sind der Nachweis, dass eine simulierte Schema-Abweichung den jeweiligen Test tatsächlich rot werden lässt (nicht nur behauptet wird). Da diese Gegenproben dauerhaft im Testbaum bleiben (Muster identisch zu Core-QA-07/Archive), ist die Prüfung bei jedem künftigen CI-Lauf automatisch reproduzierbar, nicht nur einmalig in dieser Sitzung durchgeführt.

Bewertung: erfüllt.


Gesamt-Testlauf (Regressionsnachweis)

Zusätzlich zu den oben gezeigten gezielten Läufen wurde die gesamte bestehende DMS-Testsuite nach allen Änderungen erneut ausgeführt (go build ./... && go vet ./... && go test ./... -p 1 -count=1, identisch zum CI-Job dms-build-test), um Regressionen in angrenzenden Paketen durch den Fix in imap.go auszuschließen:

$ go build ./... && go vet ./... && go test ./... -p 1 -count=1
(Build: keine Ausgabe = Erfolg; go vet: keine Ausgabe = keine Befunde)
96 Pakete mit Tests: alle "ok", 0 "FAIL".
18 Pakete ohne Testdateien ([no test files], z.B. reine cmd/-Einstiegspunkte).

Vollständige Paketliste inkl. Laufzeiten liegt im Sitzungs-Log vor; u. a. bestätigt grün: dms/internal/contracttest (neu), dms/internal/mailimport (20 Testfunktionen + 1 neue), dms/internal/watchfolder (18 Testfunktionen + 2 neue), dms/internal/mailimportapi, dms/internal/retentionclient, dms/internal/workflowevent, dms/internal/moduletypeclient, dms/internal/cmis. go vet ./... über das gesamte Modul: keine Befunde.

Root-Modul (Core): internal/contracttest (alle 9 Tests inkl. der neuen API-07-Wiederholungsprüfung) und internal/webhook selbst laufen beide isoliert grün. Ein erster Versuch, zusätzlich die gesamte Root-Modul-Testsuite (go test ./...) als Regressionsnachweis mitlaufen zu lassen, zeigte Dutzende Fehlschläge in völlig unbeteiligten Paketen (internal/tenant, internal/user, internal/auth, internal/rbac, internal/migrate*, …) — Ursache war ausschließlich eine fehlende Berechtigung der für diese Sitzung frisch angelegten Testrolle (GRANT CREATE ON DATABASE postgres TO qa09_test fehlte; Core-Tickets wie das Tenant-Onboarding legen für ihre Tests echte, eigene physische Datenbanken an, nicht nur Schemata). Nach dem Grant liefen internal/tenant/internal/user isoliert erneut grün. Dieser Teil war reine Testumgebungs-Konfiguration dieser Wegwerf-Sitzung, keine Codeänderung durch QA-09.

Der anschließende zweite Gesamtlauf der Root-Modul-Suite mit korrigierter Berechtigung lief zum Zeitpunkt der Protokollerstellung noch; bis dahin war außer der bereits unten beschriebenen, nachweislich vorbestehenden TestAllHTTPServersUseHTTPHarden-Rotmarkierung keine weitere Abweichung aufgetreten. Dieser Gesamtlauf dient ausschließlich der zusätzlichen Absicherung gegen Fernwirkungen auf das Root-Modul insgesamt — die für QA-09 selbst relevanten Pakete (internal/contracttest, internal/webhook) sind bereits isoliert und vollständig grün bestätigt (siehe Akzeptanzkriterium 3 oben), das ist die für die Abnahme entscheidende Prüfung.

Zur Bestätigung, dass TestAllHTTPServersUseHTTPHarden nicht durch diesen Branch verursacht ist, wurde sie zusätzlich gegen einen unberührten frischen main-Checkout ausgeführt und schlägt dort identisch fehl (zwei ai/cmd/*-a11y-fixture-Programme rufen http.ListenAndServe direkt auf, unabhängig von QA-09) — eine vorbestehende, nicht ticketbezogene Fehlermarkierung.


Nicht Bestandteil dieser Kachel

Wie im Ticket vorgegeben: keine vorauseilende Umsetzung. Insbesondere nicht angefasst: CMIS-Feinheiten über die bestehende Parität hinaus, Webhook- Registrierung/-Verwaltung selbst (API-07 ist Core-Bestandsfunktion), eigene Retry-Logik im DMS für Workflow-Events (nutzt bereits die bestehende jobqueue-Infrastruktur aus FDN-04, unverändert).