Nicht-destruktiver Richtlinien-Re-Import (Phase-1-Härtung Paket 3)

Der Re-Import löschte bisher alle Policy-Zeilen und legte sie neu an — das setzte
per-Dokument-Status, TISAX-Override, Freigabe/Version und nutzergepflegte
Variablenwerte zurück. Jetzt Diff/Upsert über stabile Schlüssel, verlustfrei.

- Schema: PolicyDocument.archivedAt + PolicyRequirement.archivedAt (Lifecycle,
  orthogonal zum Freigabe-status). Migration additiv/nullable.
- import-policies.ts komplett auf Reconcile umgebaut:
  - Dokumente (code): neu→anlegen; vorhanden→nur Inhaltsfelder aktualisieren,
    Status/Version/Owner/Freigabe/Override bleiben; fehlend→archivedAt setzen
    (deaktivieren, nicht löschen); Wiederauftauchen→reaktivieren.
  - Anforderungen (reqId): analog inkl. Deaktivierung entfernter Anforderungen.
  - Variablen (key): Metadaten aktualisieren, aber value NIE überschreiben
    (Kundenpflege); fehlende bleiben bestehen (als obsolet ausgewiesen).
  - Baseline (blId) und Nachweise (nr): Upsert; fehlende bleiben erhalten.
  - Deaktivierung greift nur im Paket-Namensraum (L00/R*/VA-*/BASELINE/NACHWEIS)
    — verwaltete Register aus import-managed (CRYPTO/RISKMATRIX/CLASSIFICATION/
    HANDBUCH) werden nicht angetastet.
- Änderungsreport (added/updated/archived/reactivated/unchanged/obsolete) wird
  zurückgegeben und ins Audit-Log geschrieben (Entity policy_package). Option
  { dryRun: true } liefert die Vorschau ohne Schreibzugriff. Idempotent.
- Aktive Ansichten (Bibliothek/Coverage, Dokument-Detail) filtern archivedAt: null;
  Historie bleibt per Direktlink erreichbar.

Akzeptanztest scripts/test-reimport.ts: Status/Override/Einreicher + Variablenwert
bleiben nach Re-Import erhalten, entfernte Anforderung wird deaktiviert (nicht
gelöscht), verwaltete Register unberührt, zweiter Lauf idempotent. Browser: /policies
rendert unverändert (30 Dokumente). tsc + lint + build + Guard-Check grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 13:46:42 +02:00
co-authored by Claude Opus 4.8
parent 8e4040d1da
commit e07dcd9f2a
7 changed files with 326 additions and 55 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ export default async function PolicyDetailPage({
} else {
const [variables, reqs] = await Promise.all([
db.policyVariable.findMany({ orderBy: { orderIdx: "asc" } }),
db.policyRequirement.findMany({ where: { policyCode: doc.code } }),
db.policyRequirement.findMany({ where: { policyCode: doc.code, archivedAt: null } }),
]);
const controls = [...new Set(reqs.map((r) => r.control))].sort();
const relatedVas = [...new Set(reqs.flatMap((r) => r.vaCodes))].sort();
+4 -2
View File
@@ -125,8 +125,10 @@ export default async function PoliciesPage({
const params = await searchParams;
const [docs, requirements, variables] = await Promise.all([
db.policyDocument.findMany({ orderBy: { orderIdx: "asc" } }),
db.policyRequirement.findMany({ orderBy: [{ control: "asc" }, { reqId: "asc" }] }),
// Aktive Bibliothek/Coverage: deaktivierte (nicht mehr im Paket enthaltene)
// Dokumente/Anforderungen ausblenden — Historie bleibt per Direktlink erreichbar.
db.policyDocument.findMany({ where: { archivedAt: null }, orderBy: { orderIdx: "asc" } }),
db.policyRequirement.findMany({ where: { archivedAt: null }, orderBy: [{ control: "asc" }, { reqId: "asc" }] }),
db.policyVariable.findMany(),
]);
const titleOf = (code: string) => docs.find((d) => d.code === code)?.title ?? code;