Files
nexarch/docs/CUT-01-PRUEFPROTOKOLL.md
patrickandClaude Sonnet 5 7f37126049
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Archive-Pflichttest-Gate / pflichttest-gate (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 / Web (dms-browser) (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 (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Mail-Pflichttest-Gate / pflichttest-gate (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) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CUT-01: Dokubegruendung DOK-03-Abhaengigkeit korrigieren (COR-02 statt COR-03)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 23:15:51 +02:00

343 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CUT-01 – Prüfprotokoll: Trockenlauf 1 – Entwicklung
Branch `feature/cut-01-trockenlauf-1-entwicklung`, Testhost `root@192.168.1.204`,
Wegwerf-Cluster `coredev` (PostgreSQL 17, Port 5433, Zugang über
`TEST_ADMIN_DSN` aus `/etc/nexarch-dev/core-dev.env`, nie ausgegeben).
Keine Verbindung zu archivmail/archivdms oder deren Daten, kein Zugriff auf
den Cluster `main` (5432) und die dort laufende archivdms-Instanz.
## Umfang
Zentrales Orchestrierungswerkzeug für den ersten Trockenlauf aller
Migrationsskripte. Es erzeugt eine vollständig synthetische Testkopie beider
Altsysteme, führt die **bestehenden** Migratoren in technisch korrekter
Reihenfolge aus und protokolliert jeden Schritt (JSON + Text). Migrationslogik
wird nicht dupliziert; das Werkzeug liefert Reihenfolge, Quell-Adapter gegen
die Testkopie, Testkopie und Protokoll.
| Pfad | Inhalt |
|---|---|
| `cmd/cutover-dryrun` | Einstiegspunkt (Flags: `-repo`, `-arbeitsverzeichnis`, `-seed`, `-bcrypt-kosten`, `-bezeichnung`, `-behalten`) |
| `internal/cutoverorchestrator` | Ablaufplan + Abhängigkeitsprüfung (`plan.go`), Runner mit Abbruch/Protokoll (`runner.go`, `protocol.go`), Redigierung (`redact.go`), Generator der Testkopie (`dataset.go`, `legacyschema.go`, `seed.go`), Anonymisierungsnachweis (`anonymization.go`), Export-Sichten (`export.go`), Schritte (`steps_*.go`), Modulstufen-Aufruf (`stages.go`), Cluster-Hilfen (`cluster.go`) |
| `dms/cmd/cutover-stage`, `dms/internal/cutoverstage` | DMS-Stufe: Schema, DOK-01, DOK-02, DOK-04, DOK-05, VAL-02-Entschlüsselung |
| `mail/cmd/cutover-stage`, `mail/internal/cutoverstage` | Mail-Stufe: MAI-02 |
| `archive/cmd/cutover-stage`, `archive/internal/cutoverstage` | Archive-Stufe: Schema, TAR-01, MAI-03, MAI-04, TAR-02 |
| `workflow/cmd/cutover-stage`, `workflow/internal/cutoverstage` | Workflow-Stufe: Schema, WFL-01 |
| `docs/cut-01/iteration-1`, `docs/cut-01/iteration-2` | Original-Protokolle aller Läufe (Text, JSON, VAL-01-/VAL-02-Berichte) |
**Warum Modulstufen als eigene Prozesse:** `dms/`, `mail/`, `archive/` und
`workflow/` sind eigene Go-Module; ihre `internal`-Pakete (die Migratoren) sind
aus dem Root-Modul nicht importierbar. Der Orchestrator baut je Modul ein
Stufen-Binary (`go build`), startet es mit minimaler Umgebung (Zugangsdaten nur
per Umgebungsvariable, nie als Argument) und liest das Ergebnis als JSON.
Gemeinsamer Laufkontext: `kontext.json` (DB-Namen, Tenant-Zuordnung, keine
Zugangsdaten). Übergaben zwischen Modulen (z. B. DOK-01 → TAR-01: Ziel-IDs der
Dokumente) laufen über Dateien unter `uebergabe/<slug>/`.
**Datenbank-Topologie im Lauf** (alle Namen mit Laufkennung `cut01xxxxxx`,
dadurch keine Kollision mit fremden Test-DBs, Aufräumen nur über diese Kennung):
| Datenbank | Inhalt |
|---|---|
| `<lauf>_quelle_archivmail`, `<lauf>_quelle_archivdms` | synthetische Testkopie der Altsysteme |
| `<lauf>_registry` | Core-Registry (alle `migrations/*.up.sql`) inkl. Maps, `object_acl`, `audit_events` |
| `tenant_<slug>` (von COR-01 provisioniert) | Core-Tenant-Schema + DMS-Schema (gleiche physische DB, siehe DMS FDN-02) |
| `<slug>_archive` | Archive-Schema je Mandant (Modell C) |
| `<slug>_workflow` | Workflow-Schema je Mandant (eigene DB: `module_registrations` existiert in Archive **und** Workflow) |
## Ablaufplan und Reihenfolge
Der Plan wird vor dem ersten Datenbankzugriff von `ValidatePlan` geprüft: jede
Abhängigkeit muss im Plan vorher stehen, sonst läuft kein einziger Schritt.
| # | Schritt | Paket / Funktion | hängt ab von | Begründung |
|---|---|---|---|---|
| 1 | PREP-01 | Arbeitsverzeichnis, Modulstufen bauen | – | |
| 2 | PREP-02 | Testkopie erzeugen | PREP-01 | |
| 3 | PREP-03 | Anonymisierungsnachweis | PREP-02 | vor jeder Verarbeitung |
| 4 | PREP-04 | Core-Registry (Core-Migrationen) | PREP-01 | Maps/Registry-Tabellen |
| 5 | EXP-01 | Export-Sichten (Iteration-1-Korrektur I-1) | PREP-03 | Exportform für MAP-01/MAP-03 |
| 6 | MAP-01 | `tenantmatch.ExportTenants/Match` | EXP-01 | Tenant-Identität vor allem anderen |
| 7 | MAP-02 | `tenantapproval.InsertCandidates/Approve/Correct` | MAP-01, PREP-04 | Freigabe Pflicht vor COR-01 |
| 8 | MAP-03 | `usermatch.ExportUsers/Match` | MAP-02 | Abgleich nur in freigegebenen Tenants |
| 9 | MAP-04* | Trockenlauf-Ersatz: `user_migration_map`/`role_migration_map` | MAP-03 | MAP-04 ist nicht gebaut (siehe Befunde) |
| 10 | COR-01 | `migrationtenant` + `tenant.Provisioner` | MAP-02 | legt `tenant_<slug>` an |
| 11 | SCHEMA-01 | `migrate.Orchestrator.RolloutAll` (TEN-07) | COR-01 | `users`-Tabelle vor DMS-Schema |
| 12 | SCHEMA-02 | DMS `migrate.Up`, Archive-SQL, Workflow `migrate.Up` | SCHEMA-01 | DMS braucht `users` |
| 13 | COR-02 | `migrationuser` (inkl. LDAP-Felder COR-04) | MAP-04*, SCHEMA-01 | |
| 14 | COR-04 | Prüfung `user.LDAPInfo` gegen Map | COR-02 | COR-04 hat keinen eigenen Migrator, steckt in COR-02 |
| 15 | COR-03 | `migrationrole` | COR-02 | `role_assignments` → FK `users` |
| 16 | COR-05 | Staging aus `audit_log` + `migrationaudit` | MAP-04*, COR-01 | braucht beide Maps |
| 17 | DOK-01 | `migrationdocument` | COR-02, SCHEMA-02 | `created_by` → migrierte Benutzer |
| 18 | DOK-02 | `migrationfolder` | DOK-01 | verknüpft migrierte Dokumente |
| 19 | DOK-04 | `migrationmetadata` | DOK-01 | Feldwerte an Dokumenten |
| 20 | DOK-03 | `migrationacl` + Core-Gruppen (`rbac.GroupStore`) | DOK-01, COR-02 | Objektrechte auf migrierte Dokumente/Gruppen (korrigiert durch Verifikation: tatsächliche Abhängigkeit ist COR-02/`user_migration_map`, nicht COR-03; Ausführungsreihenfolge war davon unberührt, da COR-02 ohnehin vor COR-03 läuft) |
| 21 | DOK-05 | `migrationsharelink` | DOK-01 | FK `share_links.document_id` |
| 22 | MAI-02 | `messagemigration` | COR-01 | Ziel-Slug |
| 23 | TAR-01 | `retentionmigration` | DOK-01, MAI-02, SCHEMA-02 | ObjectReference kommt aus DOK-01/MAI-02 |
| 24 | MAI-03 | `dsgvorequestmigration` | TAR-01 | sucht Objekte der betroffenen Person |
| 25 | MAI-04 | `reconcilereportmigration` | SCHEMA-02 | |
| 26 | TAR-02 | `deletionrequestmigration` | TAR-01, COR-02 | gleiche Retention-Objekte, Antragsteller → NEXARCH-ID |
| 27 | WFL-01 | `migrationworkflow` | COR-02, SCHEMA-02 | Akteursauflösung |
| 28 | VAL-01 | `migrationverify.Run` | COR-02, DOK-01, MAI-02 | |
| 29 | VAL-02 | `migrationverify.CompareSamples` | DOK-01, MAI-02 | |
| 30 | VAL-05 | `auth.LoginService.Login` | COR-02 | |
| 31 | CHK-01 | modulübergreifende Kettenprüfung | DOK-03, DOK-05, TAR-02, MAI-03, MAI-04, WFL-01 | |
Abbruchverhalten: beim ersten Fehler wird abgebrochen, alle Folgeschritte
werden mit „nicht ausgeführt: Abbruch nach Fehler in X“ protokolliert (die
Einzelpakete arbeiten selbst fail-closed, ein Weiterlaufen würde nur
Folgefehler protokollieren). Panics werden als Fehler des Schritts erfasst.
## Synthetische Testkopie
Erzeugt ausschließlich von `GenerateDataset` (deterministisch über `-seed`,
Standard 20261014). Strukturell nach `datenmodell-altsysteme.md`: archivmail
mit `tenants`/`tenant_domains`, `users.username`/`role`/`source`, `emails` +
`storage_objects` + `attachments` + `email_attachments` + `email_refs`,
`dsgvo_requests`, `reconciliation_reports`, append-only `audit_log`;
archivdms mit `users` inkl. `auth_source`/`ldap_uid`/`ldap_synced_at`,
`permission_groups`/`_members`, `document_visibility`, `document_grants`
(inkl. `deny`), `akten`, WORM-`documents` ohne Revisionstabelle,
`custom_field_defs`, `classification_templates`, `document_shares`,
`document_delete_requests`, `workflows`/`workflow_runs`, `audit_log`.
Abgebildete Konstellationen: Mandant in beiden Systemen per Slug-Treffer
(„Kanzlei Musterhausen“), Mandant nur über die manuelle Kundenzuordnungsliste
zuordenbar („Steuerbüro Beispielstadt“, abweichende Slugs, zwei Domains),
je ein Mandant nur in archivmail („Praxis Fantasia“) bzw. nur in archivdms
(„Ingenieurbüro Erfundenhausen“); Doppelkonten mit unterschiedlicher
Groß-/Kleinschreibung; LDAP-Konten beider Feldtiefen; alle dokumentierten
archivmail-Rollen außer `superadmin`; Fristen in Zukunft/Vergangenheit/keine;
aktive, abgelaufene, widerrufene Freigabelinks und Link auf gelöschtes
Dokument; offene/vollzogene/abgebrochene Löschanfragen; laufende,
abgeschlossene, abgebrochene Workflows; DSGVO-Anfragen `open`/`partial`/
`completed`; eine Mail mit Sichtbarkeit in zwei Mandanten.
Mengen je Lauf: 6 Altsystem-Mandanten, 30 Konten, 24 Dokumente, 18 Mails,
9 Anhänge, 48 Binärobjekte, 431 Tabellenzeilen.
## Akzeptanzkriterium 3: Anonymisierung nachweislich wirksam
**Argumentation:** Die Testkopie ist keine anonymisierte Kopie echter Daten,
sondern von Grund auf erzeugt. Das Werkzeug hat keinen Eingang für Fremddaten:
`GenerateDataset(seed, now, bcryptCost)` hat keinen Parameter für Dateien,
DSNs oder Exporte, die einzigen Datenquellen sind Konstanten und ein
Pseudozufallsgenerator in `internal/cutoverorchestrator/dataset.go`. Die
Quell-Datenbanken werden in jedem Lauf neu angelegt (`CREATE DATABASE`) und
ausschließlich daraus befüllt. Es existiert kein Codepfad, der eine Verbindung
zu archivmail/archivdms aufbaut. Echte Personendaten können daher im Testlauf
nicht vorkommen.
**Technischer Nachweis je Lauf (Schritt PREP-03, `VerifyAnonymization`)**,
unabhängig vom Generator: jede Zeile jeder Tabelle beider Quell-DBs wird als
JSON gelesen und durchsucht.
| Prüfung | Zielwert | Ergebnis (Läufe 2.1 und 2.2) |
|---|---|---|
| E-Mail-Adressen nur unter `.example` (RFC 2606 reserviert) | 0 Verstöße | 93 Adressen, 0 Verstöße |
| IP-Adressen nur aus Dokumentationsnetzen (RFC 5737) | 0 Verstöße | 24 Adressen, 0 Verstöße |
| Konten mit synthetischem Präfix „testperson“ | alle | 30/30 |
| Binärobjekte beginnen mit Marker `SYNTHETISCH` | alle | 48/48 |
| Tabellen/Zeilen geprüft | alle | 30 Tabellen, 431 Zeilen |
Unit-Tests: `TestGenerateDataset_ContainsOnlySyntheticPersonalData` (alle
erzeugten Personenangaben synthetisch) und
`TestAnonymizationCheck_DetectsRealLookingData` (die Prüfung erkennt eine
reale Domain und eine öffentliche IP – der Nachweis ist also wirksam, nicht
nur formal). Passwörter: zufällig erzeugt, nur als bcrypt-Hash (Kosten 12) in
der Testkopie, Klartext nur im Speicher des Laufs für VAL-05.
## Iterationsprotokoll
### Iteration 1 – Fehlerfindung (Läufe 1.1 bis 1.5)
| Lauf | Kennung | Abbruch bei | Fehler | Korrektur vor dem nächsten Lauf |
|---|---|---|---|---|
| 1.1 | cut01479c9d | MAP-01 | `tenants-export (archivmail): column "domain" does not exist` | **I-1** siehe unten |
| 1.2 | cut01a6173e | MAP-02 | Vollständigkeit: `archivmail/praxis-fantasia`, `archivdms/ingenieurbuero-erfundenhausen` ohne Eintrag in der freigegebenen `tenant_migration_map` | **I-2** |
| 1.3 | cut01c33bb7 | COR-03 | `keine core-rolle fuer archivmail-rolle "auditor" hinterlegt` | **I-3** |
| 1.4 | cut0171964d | MAI-03 | offene DSGVO-Anfrage: 2 migrierte Objekte der betroffenen Person, aber keine Einzelentscheidung | **I-4** |
| 1.5 | cut01748e71 | – | alle 31 Schritte erfolgreich | – |
**I-1 – Export-Schemaannahme MAP-01/MAP-03 (Integrationsfehler).**
`tenantmatch.ExportTenants` liest `tenants(id, slug, name, domain)`,
`usermatch.ExportUsers` liest `users(id, tenant_id, email, name,
password_hash)`. archivmail führt Domains in `tenant_domains` (1:n), beide
Altsysteme kennen nur `users.username`, archivmail-Superadmins haben
`tenant_id NULL`. Das MAP-01-Prüfprotokoll hatte genau diese Annahme als offen
markiert. Bei der Ursachenanalyse zu Lauf 1.1 wurde die gleiche Annahme in
MAP-03 per Spaltenabfrage der Testkopie bestätigt und mitbehoben.
Korrektur (ohne Änderung der MAP-Pakete): neuer Schritt **EXP-01** legt in
jeder Quelle das Schema `cutover_export` mit Sichten `tenants` (archivmail:
erste registrierte Domain je Mandant) und `users` (`username AS name`,
archivmail nur Konten mit Mandant) an; MAP-01/MAP-03 lesen über Pools mit
`search_path=cutover_export,public` (`export.go`, `Cluster.ConnectSchema`).
**I-2 – Einseitige Mandanten gingen verloren (Kettenfehler in MAP-02/MAP-03).**
`tenantapproval.InsertCandidates` verwarf Kandidaten, die nur in einem
Altsystem existieren; `usermatch.Match` übersprang solche Mandanten. Da die
`tenant_migration_map` die einzige Quelle für COR-01 ist, wären diese
Mandanten samt Benutzern, Dokumenten und Mails nie migriert worden – ohne
Fehlermeldung. MAP-01 gibt einseitige Kandidaten ausdrücklich aus, „damit sie
in der Freigabeprüfung sichtbar bleiben“.
Korrektur: `InsertCandidates` legt einseitige Kandidaten als `pending` an
(nur Kandidaten ganz ohne Quelle werden verworfen), Freigabepflicht
unverändert; `Match` liefert für einseitig freigegebene Mandanten die Benutzer
der vorhandenen Seite als einseitige Kandidaten. Test
`TestInsertCandidates_SkipsOneSidedCandidates` ersetzt durch
`TestInsertCandidates_KeepsOneSidedCandidatesPending` (prüft zusätzlich: ohne
Freigabe nicht nutzbar), neu `TestMatch_EinseitigFreigegebenerTenantLiefertEinseitigeKandidaten`.
**I-3 – Dokumentierte archivmail-Rollen ohne Core-Rolle (COR-03).**
`archivmailRoleMap` kannte `domain_admin`, `auditor`, `domain_auditor` nicht
(alle in `datenmodell-altsysteme.md` belegt) → fail-closed-Abbruch.
Korrektur: `domain_admin → tenant_admin`, `auditor`/`domain_auditor → user`
(Core hat keine Prüferrolle; geringste Rolle statt Rechteausweitung),
`superadmin` bleibt bewusst ungemappt. Test `TestCoreRole_DocumentedArchivmailRoles`.
**I-4 – DSGVO-Anfragen ohne Objektbezug (Kettenfehler TAR-01 → MAI-03).**
MAI-03 (`dsgvorequest.ProcessDeletionRequest` → `dpreport.SubjectReport`)
findet Objekte nur über `retention_objects.data_subject_ref`; TAR-01
registrierte über `retention.RegisterObject` ohne diese Referenz. Folge: eine
offene Art.-17-Anfrage wurde als „verarbeitet“ gezählt, aber ohne eine
einzige Einzelentscheidung. Erkannt durch die Nachprüfung der Archive-Stufe.
Korrektur: `retentionmigration.SourceObject.DataSubjectRef`; gesetzt →
`retention.RegisterObjectForSubject` (bestehende CMP-02-Funktion), sonst
unverändert `RegisterObject`. Die Mail-Stufe liefert den Absender als
betroffene Person. Test `TestMigrateAll_CarriesDataSubjectRef`.
Bereits vor Lauf 1.1 (Integrationsanalyse, keine Laufkorrektur):
`migrationdocument.DocumentID` exportiert die DOK-01-Ziel-ID, weil TAR-01/
TAR-02 im Archive-Modul die ObjectReference brauchen, `deriveUUID` aber nicht
importieren können (gleiches Muster wie `messagemigration.MessageID`).
### Iteration 2 – Bestätigung (Läufe 2.1 und 2.2)
Beide Läufe auf Commit `4fdbb9b` (Stand nach allen Iteration-1-Korrekturen),
ohne Codeänderung zwischen den Läufen, ohne `-behalten` (Aufräumen durch das
Werkzeug selbst).
| Lauf | Kennung | Ergebnis | Dauer | aufgeräumt |
|---|---|---|---|---|
| 2.1 | cut017f8b14 | alle 31 Schritte erfolgreich | 28,8 s | 15 Lauf-DBs entfernt |
| 2.2 | cut01d7d2a2 | alle 31 Schritte erfolgreich | 27,4 s | 15 Lauf-DBs entfernt |
Danach: 0 Datenbanken mit Laufkennung auf dem Cluster, alle fremden
Test-Datenbanken unverändert.
## Prüfungen
| # | Prüfung | Zielwert | Ergebnis |
|---|---|---|---|
| T1 | Vollständiger Lauf gegen anonymisierte Kopie protokolliert (Ticket-Prüfung 1) | Lauf ohne Abbruch, Protokoll vorhanden | **erfüllt** – Läufe 1.5, 2.1, 2.2; Protokolle `docs/cut-01/iteration-*/` |
| T2 | Mind. zwei Iterationen mit dokumentierten Korrekturen (Ticket-Prüfung 2) | ≥ 2 | **erfüllt** – Iteration 1 (5 Läufe, 4 Korrekturen), Iteration 2 (2 Läufe) |
| AK1 | Alle Migrationsskripte laufen mind. einmal vollständig durch | jeder Schritt `erfolgreich` | **erfüllt** – 31/31 in 1.5, 2.1, 2.2 (Ausnahme MAI-01, siehe B-5) |
| AK2 | Gefundene Fehler vor Trockenlauf 2 behoben | alle abbrechenden Fehler behoben | **erfüllt** – I-1 bis I-4 behoben und in Iteration 2 bestätigt; nicht abbrechende Befunde mit Fach-/Zielsystem-Entscheidung siehe unten |
| AK3 | Anonymisierung nachweislich wirksam | 0 Verstöße, Prüfung wirksam | **erfüllt** – siehe Abschnitt AK3 |
| V1 | VAL-01 Zeilenzahl je Tenant/Kerntabelle | 0 offene Befunde | **erfüllt** – 12 Vergleiche; Dokumente 24→21, Differenz 3 erklärt (vollzogene Löschungen, nur Audit) |
| V2 | VAL-02 Hash-Stichprobe Binärdateien | 0 Abweichungen | **erfüllt** – 15 DMS-Dateien (über `crypto.Service` entschlüsselt) + 9 Mail-Anhänge, 0 Abweichungen |
| V3 | VAL-05 Login mit migrierten Zugangsdaten | alle Stichproben-Logins ok, falsches Passwort abgewiesen | **erfüllt** – 12/12 Logins (3 je Tenant), 12/12 Ablehnungen |
| V4 | TAR-01 Fristen exakt übernommen | 0 Abweichungen | **erfüllt** – 40 Objekte geprüft, 27 Fristen identisch, 13 ohne Frist bleiben ohne |
| V5 | COR-05 Audit-Kontinuität | Zeitstempel/Aktion unverändert, nichts unmigriert | **erfüllt** – 24/24 Ereignisse |
| V6 | CHK-01 modulübergreifend | 0 Befunde | **erfüllt** – je Dokument/Nachricht ein Retention-Objekt (21/19), keine Objektrechte auf nicht migrierte Dokumente, Freigabelinks 6, Workflows 9, offene Löschanfragen 3, vollzogene 3, Akten 9, Reconciliation-Berichte 9 = Quelle |
| U1 | Orchestrator-Tests (Reihenfolge, Fehlerprotokoll, Abbruch) | grün | **erfüllt** – `go test ./internal/cutoverorchestrator`: Reihenfolge, Abbruch nach Fehler mit Protokoll/Konsole/Text, Panic, ungültiger Plan führt nichts aus, Kontext-Abbruch, Redigierung, Abhängigkeiten des echten Plans, Generator, Anonymisierungsprüfung, Slug/DSN |
| U2 | Regression aller orchestrierten Pakete, real gegen Postgres (`-p 1`) | grün | **erfüllt** – 21 Pakete in root/dms/archive/workflow/mail grün, plus `dms/internal/cutoverstage` |
| S1 | Keine Zugangsdaten in Code/Protokoll | keine Treffer | **erfüllt** – DSN nur aus Umgebung, Redigierung (Literal + URL-Userinfo + `password=`) in Runner und Stufen-Fehlern, Scan der Protokolle ohne Treffer |
## Offene Befunde ohne Abbruch (Übergabe an CUT-02 und Modul-Boards)
Diese Punkte brechen die Kette nicht ab und verfälschen keine Daten in
Richtung Datenverlust oder Rechteausweitung, verlangen aber eine Fach-,
Compliance- oder Zielsystem-Entscheidung („Keine Änderung der
Zielsystem-Fachlogik selbst“, Ticket CUT-01). Sie sind vor dem Stichtag zu
entscheiden.
- **B-1 Archive: abgelaufene Altsystem-Fristen bleiben `active`.** TAR-01
übernimmt `retain_until`, migrierte Objekte haben aber keine
Aufbewahrungsklasse; `deletionworkflow.ReleaseExpired` wertet nur
Klassenzuordnungen aus, nicht `retain_until`. Eine Mail mit Frist in der
Vergangenheit wird von MAI-03 mit „Aufbewahrungsfrist ist noch nicht
abgelaufen“ abgelehnt. GoBD-sicher (nichts wird zu früh gelöscht), aber
sachlich falsche Begründung gegenüber der betroffenen Person.
Empfehlung: RET-02/RET-03 berücksichtigen `retain_until` (Archive-Board).
- **B-2 MAI-03: abgeschlossene DSGVO-Anfragen hinterlassen keine Spur.**
`logHistorical` schreibt nur je noch vorhandenem Objekt; nach einer
vollzogenen Löschung gibt es keine Objekte mehr, `dsgvo_decision_log`
verlangt aber ein Objekt. Im Lauf: 3 abgeschlossene Anfragen, 0 Einträge.
Entscheidung nötig, wo der historische Nachweis liegt (z. B. Archive-Audit).
- **B-3 Dokumenttypen nicht migriert.** archivdms-`document_types` und
`documents.doc_type_id` haben kein Migrationspaket; DOK-04 belegt
`document_types` mit Klassifikationsvorlagen. Feld-Vorgaben der Vorlagen
(`classification_template_field_defaults.default_value`) gehen verloren,
da `SourceClassificationTemplate` kein Feld dafür hat.
- **B-4 Akten-Status.** `akten.status` (offen/geschlossen) hat im
Ordnermodell keine Entsprechung und wird nicht übernommen.
- **B-5 MAI-01 nicht im Lauf.** Die Postfach-Migration verlangt je Postfach
ein vom Betreiber neu vergebenes Passwort und einen echten IMAP-Test; im
Trockenlauf gibt es weder IMAP-Testserver noch sinnvoll erfindbare
Zugangsdaten. Gehört in CUT-02 mit Test-IMAP.
- **B-6 Suchindex-Ersatz.** MAI-02 schreibt Metadaten nur in Manticore; die
einzige Manticore-Instanz auf Host 204 teilt sich das Altsystem archivdms
und bleibt tabu. Im Trockenlauf vertritt ein dateibasierter Index
(`ziel-objekte/mail-index/<slug>.json`) den `SearchIndexer` – gleiches
Interface wie die Paket-Tests. CUT-02 braucht eine eigene Manticore-Instanz.
- **B-7 KEK-Ersatz.** DOK-01/VAL-02 nutzen statt der Core-KEK-API einen
pro Lauf zufälligen Seed (HMAC je Mandant, nur per Umgebungsvariable an die
DMS-Stufe). CUT-02 gegen die echte KEK-API.
- **B-8 MAP-04 fehlt.** Die menschliche Freigabe der Benutzer-Map ist nicht
gebaut; der Trockenlauf gibt jeden MAP-03-Kandidaten mit dem Prüfer
„trockenlauf-pruefer (synthetische freigabe, CUT-01)“ frei, übernimmt
LDAP-Felder aus der Quelle und füllt `role_migration_map` (archivdms:
erste Berechtigungsgruppe, sonst archivmail-Rolle). Ebenso simuliert MAP-02
die Freigabe und korrigiert per `Correct` den Ziel-Slug (Bindestrich →
Unterstrich, Laufpräfix): Altsystem-Slugs mit Bindestrich verletzen die
Core-Slug-Regel, ein Prüfer muss sie bei der Freigabe korrigieren.
- **B-9 archivmail mit mehreren Domains.** MAP-01 vergleicht genau eine
Domain; die Export-Sicht liefert die erste registrierte.
- **B-10 Plattformkonten.** archivmail-Superadmins (`tenant_id NULL`) und die
Rolle `superadmin` werden bewusst nicht mandantenbezogen migriert.
- **B-11 Mandanteneigene Gruppennamen.** COR-03 kennt nur die
archivdms-Standardgruppen; eigene Gruppennamen eines Mandanten brechen
fail-closed ab. Die Testkopie nutzt nur Standardgruppen; für reale Daten
braucht COR-03 eine pflegbare Zuordnung.
- **B-12 Soft-gelöschte Dokumente ohne vollzogene Löschung** sind in der
Testkopie nicht enthalten; DOK-01 kennt keinen Löschzustand und würde sie
als aktiv übernehmen. Der Adapter übernimmt nur `deleted_at IS NULL`.
Beobachtungen an bestehender Test-Infrastruktur (nicht Teil von CUT-01):
Archive-Pakettests teilen sich `TEST_TENANT_DSN` und kollidieren bei
parallelem Paketlauf (mit `-p 1` grün); `internal/migrationtenant`-Test räumt
seine Tenant-DBs nicht ab (`t.Cleanup` nutzt einen per `defer` bereits
geschlossenen Pool) – die durch den eigenen Regressionslauf entstandenen fünf
Datenbanken wurden entfernt.
## Entscheidungen (QA)
1. Modulstufen als Prozesse statt `go.work`/`replace`: keine Änderung an
`go.mod` der Module, `internal`-Sichtbarkeit bleibt unangetastet.
2. Export-Sichten statt Änderung von MAP-01/MAP-03: die Normalisierung
gehört zum Export-Dump des Stichtags, die Pakete bleiben schemagenerisch.
3. Adapter-Entscheidungen (in den Stufen dokumentiert): archivdms-WORM-Datei
= Revision 1; `boolean→bool`, `enum→select`, `monetary→number`;
widerrufene Links und Links auf gelöschte Dokumente werden nicht
angeboten; DSGVO `partial`/`failed` = offen; Löschanfrage
`pending`/`confirmed`/`blocked_retention` = offen, `cancelled` entfällt;
Antragsteller/Bestätiger werden auf NEXARCH-IDs abgebildet (Vier-Augen-
Vergleich); `document_visibility` liefert Erlaubnisse,
`document_grants.deny` die Verbote.
4. Datenbankname enthält stets die Laufkennung, Aufräumen nur über sie; das
Werkzeug löscht nie fremde Datenbanken.
5. Abbruch beim ersten Fehler, kein „weiter bei Fehler“-Modus.
## Bedienung
```bash
set -a && . /etc/nexarch-dev/core-dev.env && set +a # liefert TEST_ADMIN_DSN, nie ausgeben
go run ./cmd/cutover-dryrun -repo . -arbeitsverzeichnis /root/cut01-dryrun -bezeichnung "Trockenlauf"
```
Ergebnis: `protokoll.json`, `protokoll.txt`, VAL-01-/VAL-02-Berichte im
Laufverzeichnis; Exitcode 0 nur bei vollständigem Durchlauf. Mit `-behalten`
bleiben Datenbanken und Arbeitsdateien zur Analyse erhalten.