Richtlinien-Fixes: zentrale Variablen, Schutzbedarf zentral, Coverage nach Level

- Zentrale Variablen (Organisation + Rollen + Schutzbedarf-Flags, lib/policy-variables.ts)
  sind im Richtlinien-Editor ausgeblendet und werden nur noch in den Einstellungen
  gepflegt; updatePolicyVariable/updateScopedVariables blocken zentrale Keys zusätzlich
  serverseitig.
- Schutzbedarf/TISAX wird ausschließlich zentral gesteuert (Superadmin): Override je
  Richtlinie entfernt (setProtectionOverride + UI weg), globaler TISAX-Schalter im
  Policy-Modul entfernt (setGlobalTisaxLevel weg) → nur noch Read-only-Anzeige im
  Editor und in der Bibliothek. Rendering nutzt stets den globalen Level.
- Coverage-/Referenzmatrix zeigt nur Anforderungen des aktiven Assessment-Levels
  (Bedingung erfüllt): bei AL2 keine „sehr hoch"-Controls (browser-verifiziert:
  297 statt 316 Anforderungen, 0 SEHR HOCH); AL3 zeigt alle.

tsc + lint + build grün; Edit-Seite: Schutzbedarf read-only, zentrale Variablen gesperrt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 11:17:09 +02:00
co-authored by Claude Opus 4.8
parent d38b265e09
commit 959d8162d6
5 changed files with 49 additions and 64 deletions
+7 -26
View File
@@ -5,6 +5,7 @@ import { redirect } from "next/navigation";
import { z } from "zod";
import { moduleGuard } from "@/server/action-guard";
import { type Permission } from "@/server/rbac";
import { isCentralVariable } from "@/lib/policy-variables";
import { writeAuditLog } from "@/server/audit";
const optDate = (v: FormDataEntryValue | null) => (v && String(v) ? new Date(String(v)) : null);
@@ -179,6 +180,8 @@ export async function updatePolicyTemplate(code: string, formData: FormData) {
/** Eine dokument-bezogene Variable ändern (eine Pflegestelle, §7/§8) — propagiert in alle Dokumente. */
export async function updatePolicyVariable(key: string, formData: FormData) {
const { session, db } = await guard();
const variable = await db.policyVariable.findFirst({ where: { key }, select: { key: true, groupName: true } });
if (variable && isCentralVariable(variable)) throw new Error("Zentrale Variablen werden ausschließlich in den Einstellungen gepflegt.");
const value = String(formData.get("value") ?? "");
await db.policyVariable.updateMany({ where: { key }, data: { value } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "policy_variable", after: { key, value } });
@@ -193,8 +196,11 @@ export async function updatePolicyVariable(key: string, formData: FormData) {
export async function updateScopedVariables(code: string, formData: FormData) {
const { session, db } = await guard();
const keys = String(formData.get("keys") ?? "").split(",").map((k) => k.trim()).filter(Boolean);
// Zentrale Variablen (Organisation/Rollen) niemals aus dem Richtlinien-Editor überschreiben.
const rows = keys.length ? await db.policyVariable.findMany({ where: { key: { in: keys } }, select: { key: true, groupName: true } }) : [];
const centralKeys = new Set(rows.filter(isCentralVariable).map((r) => r.key));
for (const key of keys) {
if (!formData.has(`var_${key}`)) continue;
if (centralKeys.has(key) || !formData.has(`var_${key}`)) continue;
const value = String(formData.get(`var_${key}`) ?? "");
await db.policyVariable.updateMany({ where: { key }, data: { value } });
}
@@ -203,31 +209,6 @@ export async function updateScopedVariables(code: string, formData: FormData) {
redirect(`/policies/${encodeURIComponent(code)}/edit`);
}
/* ── Schutzbedarf / TISAX-Level (Delta-Update) ── */
/** Globaler TISAX-Level: AL2 (very-high aus) / AL3 (very-high an); HIGH bleibt stets an. */
export async function setGlobalTisaxLevel(formData: FormData) {
const { session, db } = await guard();
const level = String(formData.get("level")) === "AL3" ? "AL3" : "AL2";
await db.policyVariable.updateMany({ where: { key: "FLAG_VERY_HIGH_PROTECTION" }, data: { value: level === "AL3" ? "true" : "false" } });
await db.policyVariable.updateMany({ where: { key: "FLAG_HIGH_PROTECTION" }, data: { value: "true" } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "tisax_level", after: { level } });
revalidatePath("/policies", "layout");
}
/** Schutzbedarf-Override je Richtlinie: "" = global, "AL2", "AL3". */
export async function setProtectionOverride(code: string, formData: FormData) {
const { session, db } = await guard();
const raw = String(formData.get("override") ?? "");
const override = raw === "AL2" || raw === "AL3" ? raw : null;
const doc = await db.policyDocument.findFirst({ where: { code } });
if (!doc) throw new Error("Dokument nicht gefunden");
await db.policyDocument.update({ where: { id: doc.id }, data: { protectionOverride: override } });
await writeAuditLog({ tenantId: session.user.tenantId, actorId: session.user.id, action: "update", entity: "policy_document", entityId: doc.id, after: { protectionOverride: override } });
revalidatePath("/policies", "layout");
redirect(`/policies/${encodeURIComponent(code)}/edit`);
}
/* ── Freigabe-Workflow (Vier-Augen, §8) ── */
export async function submitForApproval(code: string) {