This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/admin-v2/lib/sdk/drafting-engine/constraint-enforcer.ts
BreakPilot Dev 206183670d feat(sdk): Add Drafting Engine with 4-mode agent system (Explain/Ask/Draft/Validate)
Extends the Compliance Advisor from a Q&A chatbot into a full drafting engine
that can generate, validate, and refine compliance documents within Scope Engine
constraints. Includes intent classifier, state projector, constraint enforcer,
SOUL templates, Go backend endpoints, and React UI components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 12:37:18 +01:00

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()