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
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
343 lines
23 KiB
Markdown
343 lines
23 KiB
Markdown
# 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.
|