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:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user