Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
222 lines
7.5 KiB
TypeScript
222 lines
7.5 KiB
TypeScript
/**
|
|
* Constraint Enforcer - Hard Gate vor jedem Draft
|
|
*
|
|
* Stellt sicher, dass die Drafting Engine NIEMALS die deterministische
|
|
* Scope-Engine ueberschreibt. Prueft vor jedem Draft-Vorgang:
|
|
*
|
|
* 1. Ist der Dokumenttyp in requiredDocuments?
|
|
* 2. Passt die Draft-Tiefe zum Level?
|
|
* 3. Ist eine DSFA erforderlich (Hard Trigger)?
|
|
* 4. Werden Risiko-Flags beruecksichtigt?
|
|
*/
|
|
|
|
import type { ScopeDecision, ScopeDocumentType, ComplianceDepthLevel } from '../compliance-scope-types'
|
|
import { DOCUMENT_SCOPE_MATRIX, getDepthLevelNumeric } from '../compliance-scope-types'
|
|
import type { ConstraintCheckResult, DraftContext } from './types'
|
|
|
|
export class ConstraintEnforcer {
|
|
|
|
/**
|
|
* Prueft ob ein Draft fuer den gegebenen Dokumenttyp erlaubt ist.
|
|
* Dies ist ein HARD GATE - bei Violation wird der Draft blockiert.
|
|
*/
|
|
check(
|
|
documentType: ScopeDocumentType,
|
|
decision: ScopeDecision | null,
|
|
requestedDepthLevel?: ComplianceDepthLevel
|
|
): ConstraintCheckResult {
|
|
const violations: string[] = []
|
|
const adjustments: string[] = []
|
|
const checkedRules: string[] = []
|
|
|
|
// Wenn keine Decision vorhanden: Nur Basis-Drafts erlauben
|
|
if (!decision) {
|
|
checkedRules.push('RULE-NO-DECISION')
|
|
if (documentType !== 'vvt' && documentType !== 'tom' && documentType !== 'dsi') {
|
|
violations.push(
|
|
'Scope-Evaluierung fehlt. Bitte zuerst das Compliance-Profiling durchfuehren.'
|
|
)
|
|
} else {
|
|
adjustments.push(
|
|
'Ohne Scope-Evaluierung wird Level L1 (Basis) angenommen.'
|
|
)
|
|
}
|
|
return {
|
|
allowed: violations.length === 0,
|
|
violations,
|
|
adjustments,
|
|
checkedRules,
|
|
}
|
|
}
|
|
|
|
const level = decision.determinedLevel
|
|
const levelNumeric = getDepthLevelNumeric(level)
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Rule 1: Dokumenttyp in requiredDocuments?
|
|
// -----------------------------------------------------------------------
|
|
checkedRules.push('RULE-DOC-REQUIRED')
|
|
const isRequired = decision.requiredDocuments.some(
|
|
d => d.documentType === documentType && d.required
|
|
)
|
|
const scopeReq = DOCUMENT_SCOPE_MATRIX[documentType]?.[level]
|
|
|
|
if (!isRequired && scopeReq && !scopeReq.required) {
|
|
// Nicht blockieren, aber warnen
|
|
adjustments.push(
|
|
`Dokument "${documentType}" ist auf Level ${level} nicht als Pflicht eingestuft. ` +
|
|
`Entwurf ist moeglich, aber optional.`
|
|
)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Rule 2: Draft-Tiefe passt zum Level?
|
|
// -----------------------------------------------------------------------
|
|
checkedRules.push('RULE-DEPTH-MATCH')
|
|
if (requestedDepthLevel) {
|
|
const requestedNumeric = getDepthLevelNumeric(requestedDepthLevel)
|
|
|
|
if (requestedNumeric > levelNumeric) {
|
|
violations.push(
|
|
`Angefragte Tiefe ${requestedDepthLevel} ueberschreitet das bestimmte Level ${level}. ` +
|
|
`Die Scope-Engine hat Level ${level} festgelegt. ` +
|
|
`Ein Draft mit Tiefe ${requestedDepthLevel} ist nicht erlaubt.`
|
|
)
|
|
} else if (requestedNumeric < levelNumeric) {
|
|
adjustments.push(
|
|
`Angefragte Tiefe ${requestedDepthLevel} liegt unter dem bestimmten Level ${level}. ` +
|
|
`Draft wird auf Level ${level} angehoben.`
|
|
)
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Rule 3: DSFA-Enforcement
|
|
// -----------------------------------------------------------------------
|
|
checkedRules.push('RULE-DSFA-ENFORCEMENT')
|
|
if (documentType === 'dsfa') {
|
|
const dsfaRequired = decision.triggeredHardTriggers.some(
|
|
t => t.rule.dsfaRequired
|
|
)
|
|
|
|
if (!dsfaRequired && level !== 'L4') {
|
|
adjustments.push(
|
|
'DSFA ist laut Scope-Engine nicht verpflichtend. ' +
|
|
'Entwurf wird als freiwillige Massnahme gekennzeichnet.'
|
|
)
|
|
}
|
|
}
|
|
|
|
// Umgekehrt: Wenn DSFA verpflichtend und Typ != dsfa, ggf. hinweisen
|
|
if (documentType !== 'dsfa') {
|
|
const dsfaRequired = decision.triggeredHardTriggers.some(
|
|
t => t.rule.dsfaRequired
|
|
)
|
|
const dsfaInRequired = decision.requiredDocuments.some(
|
|
d => d.documentType === 'dsfa' && d.required
|
|
)
|
|
|
|
if (dsfaRequired && dsfaInRequired) {
|
|
// Nur ein Hinweis, kein Block
|
|
adjustments.push(
|
|
'Hinweis: Eine DSFA ist laut Scope-Engine verpflichtend. ' +
|
|
'Bitte sicherstellen, dass auch eine DSFA erstellt wird.'
|
|
)
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Rule 4: Risiko-Flags beruecksichtigt?
|
|
// -----------------------------------------------------------------------
|
|
checkedRules.push('RULE-RISK-FLAGS')
|
|
const criticalRisks = decision.riskFlags.filter(
|
|
f => f.severity === 'CRITICAL' || f.severity === 'HIGH'
|
|
)
|
|
|
|
if (criticalRisks.length > 0) {
|
|
adjustments.push(
|
|
`${criticalRisks.length} kritische/hohe Risiko-Flags erkannt. ` +
|
|
`Draft muss diese adressieren: ${criticalRisks.map(r => r.title).join(', ')}`
|
|
)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Rule 5: Hard-Trigger Consistency
|
|
// -----------------------------------------------------------------------
|
|
checkedRules.push('RULE-HARD-TRIGGER-CONSISTENCY')
|
|
for (const trigger of decision.triggeredHardTriggers) {
|
|
const mandatoryDocs = trigger.rule.mandatoryDocuments
|
|
if (mandatoryDocs.includes(documentType)) {
|
|
// Gut - wir erstellen ein mandatory document
|
|
} else {
|
|
// Pruefen ob die mandatory documents des Triggers vorhanden sind
|
|
// (nur Hinweis, kein Block)
|
|
}
|
|
}
|
|
|
|
return {
|
|
allowed: violations.length === 0,
|
|
violations,
|
|
adjustments,
|
|
checkedRules,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience: Prueft aus einem DraftContext heraus.
|
|
*/
|
|
checkFromContext(
|
|
documentType: ScopeDocumentType,
|
|
context: DraftContext
|
|
): ConstraintCheckResult {
|
|
// Reconstruct a minimal ScopeDecision from context
|
|
const pseudoDecision: ScopeDecision = {
|
|
id: 'projected',
|
|
determinedLevel: context.decisions.level,
|
|
scores: context.decisions.scores,
|
|
triggeredHardTriggers: context.decisions.hardTriggers.map(t => ({
|
|
rule: {
|
|
id: t.id,
|
|
label: t.label,
|
|
description: '',
|
|
conditionField: '',
|
|
conditionOperator: 'EQUALS' as const,
|
|
conditionValue: null,
|
|
minimumLevel: context.decisions.level,
|
|
mandatoryDocuments: [],
|
|
dsfaRequired: false,
|
|
legalReference: t.legalReference,
|
|
},
|
|
matchedValue: null,
|
|
explanation: '',
|
|
})),
|
|
requiredDocuments: context.decisions.requiredDocuments.map(d => ({
|
|
documentType: d.documentType,
|
|
label: d.documentType,
|
|
required: true,
|
|
depth: d.depth,
|
|
detailItems: d.detailItems,
|
|
estimatedEffort: '',
|
|
triggeredBy: [],
|
|
})),
|
|
riskFlags: context.constraints.riskFlags.map(f => ({
|
|
id: `rf-${f.title}`,
|
|
severity: f.severity as 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL',
|
|
title: f.title,
|
|
description: '',
|
|
recommendation: f.recommendation,
|
|
})),
|
|
gaps: [],
|
|
nextActions: [],
|
|
reasoning: [],
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
|
|
return this.check(documentType, pseudoDecision)
|
|
}
|
|
}
|
|
|
|
/** Singleton-Instanz */
|
|
export const constraintEnforcer = new ConstraintEnforcer()
|