Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
335
ai-compliance-sdk/internal/api/handlers/drafting_handlers.go
Normal file
335
ai-compliance-sdk/internal/api/handlers/drafting_handlers.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/audit"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/llm"
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/rbac"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// DraftingHandlers handles Drafting Engine API endpoints
|
||||
type DraftingHandlers struct {
|
||||
accessGate *llm.AccessGate
|
||||
registry *llm.ProviderRegistry
|
||||
piiDetector *llm.PIIDetector
|
||||
auditStore *audit.Store
|
||||
trailBuilder *audit.TrailBuilder
|
||||
}
|
||||
|
||||
// NewDraftingHandlers creates new Drafting Engine handlers
|
||||
func NewDraftingHandlers(
|
||||
accessGate *llm.AccessGate,
|
||||
registry *llm.ProviderRegistry,
|
||||
piiDetector *llm.PIIDetector,
|
||||
auditStore *audit.Store,
|
||||
trailBuilder *audit.TrailBuilder,
|
||||
) *DraftingHandlers {
|
||||
return &DraftingHandlers{
|
||||
accessGate: accessGate,
|
||||
registry: registry,
|
||||
piiDetector: piiDetector,
|
||||
auditStore: auditStore,
|
||||
trailBuilder: trailBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Request/Response Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// DraftDocumentRequest represents a request to generate a compliance document draft
|
||||
type DraftDocumentRequest struct {
|
||||
DocumentType string `json:"document_type" binding:"required"`
|
||||
ScopeLevel string `json:"scope_level" binding:"required"`
|
||||
Context map[string]interface{} `json:"context"`
|
||||
Instructions string `json:"instructions"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
// ValidateDocumentRequest represents a request to validate document consistency
|
||||
type ValidateDocumentRequest struct {
|
||||
DocumentType string `json:"document_type" binding:"required"`
|
||||
DraftContent string `json:"draft_content"`
|
||||
ValidationContext map[string]interface{} `json:"validation_context"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
// DraftHistoryEntry represents a single audit trail entry for drafts
|
||||
type DraftHistoryEntry struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
DocumentType string `json:"document_type"`
|
||||
ScopeLevel string `json:"scope_level"`
|
||||
Operation string `json:"operation"`
|
||||
ConstraintsRespected bool `json:"constraints_respected"`
|
||||
TokensUsed int `json:"tokens_used"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// DraftDocument handles document draft generation via LLM with constraint validation
|
||||
func (h *DraftingHandlers) DraftDocument(c *gin.Context) {
|
||||
var req DraftDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userID := rbac.GetUserID(c)
|
||||
tenantID := rbac.GetTenantID(c)
|
||||
namespaceID := rbac.GetNamespaceID(c)
|
||||
|
||||
if userID == uuid.Nil || tenantID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate scope level
|
||||
validLevels := map[string]bool{"L1": true, "L2": true, "L3": true, "L4": true}
|
||||
if !validLevels[req.ScopeLevel] {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid scope_level, must be L1-L4"})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate document type
|
||||
validTypes := map[string]bool{
|
||||
"vvt": true, "tom": true, "dsfa": true, "dsi": true, "lf": true,
|
||||
"av_vertrag": true, "betroffenenrechte": true, "einwilligung": true,
|
||||
"daten_transfer": true, "datenpannen": true, "vertragsmanagement": true,
|
||||
"schulung": true, "audit_log": true, "risikoanalyse": true,
|
||||
"notfallplan": true, "zertifizierung": true, "datenschutzmanagement": true,
|
||||
}
|
||||
if !validTypes[req.DocumentType] {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid document_type"})
|
||||
return
|
||||
}
|
||||
|
||||
// Build system prompt for drafting
|
||||
systemPrompt := fmt.Sprintf(
|
||||
`Du bist ein DSGVO-Compliance-Experte. Erstelle einen strukturierten Entwurf fuer Dokument "%s" auf Level %s.
|
||||
Antworte NUR im JSON-Format mit einem "sections" Array.
|
||||
Jede Section hat: id, title, content, schemaField.
|
||||
Halte die Tiefe strikt am vorgegebenen Level.
|
||||
Markiere fehlende Informationen mit [PLATZHALTER: Beschreibung].
|
||||
Sprache: Deutsch.`,
|
||||
req.DocumentType, req.ScopeLevel,
|
||||
)
|
||||
|
||||
userPrompt := "Erstelle den Dokumententwurf."
|
||||
if req.Instructions != "" {
|
||||
userPrompt = req.Instructions
|
||||
}
|
||||
|
||||
// Detect PII in context
|
||||
contextStr := fmt.Sprintf("%v", req.Context)
|
||||
dataCategories := h.piiDetector.DetectDataCategories(contextStr)
|
||||
|
||||
// Process through access gate
|
||||
chatReq := &llm.ChatRequest{
|
||||
Model: req.Model,
|
||||
Messages: []llm.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: userPrompt},
|
||||
},
|
||||
MaxTokens: 16384,
|
||||
Temperature: 0.15,
|
||||
}
|
||||
|
||||
gatedReq, err := h.accessGate.ProcessChatRequest(
|
||||
c.Request.Context(),
|
||||
userID, tenantID, namespaceID,
|
||||
chatReq, dataCategories,
|
||||
)
|
||||
if err != nil {
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, req.ScopeLevel, "draft", false, 0, err.Error())
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "access_denied",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Execute the request
|
||||
resp, err := h.accessGate.ExecuteChat(c.Request.Context(), gatedReq)
|
||||
if err != nil {
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, req.ScopeLevel, "draft", false, 0, err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "llm_error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tokensUsed := 0
|
||||
if resp.Usage.TotalTokens > 0 {
|
||||
tokensUsed = resp.Usage.TotalTokens
|
||||
}
|
||||
|
||||
// Log successful draft
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, req.ScopeLevel, "draft", true, tokensUsed, "")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"document_type": req.DocumentType,
|
||||
"scope_level": req.ScopeLevel,
|
||||
"content": resp.Message.Content,
|
||||
"model": resp.Model,
|
||||
"provider": resp.Provider,
|
||||
"tokens_used": tokensUsed,
|
||||
"pii_detected": gatedReq.PIIDetected,
|
||||
})
|
||||
}
|
||||
|
||||
// ValidateDocument handles document cross-consistency validation
|
||||
func (h *DraftingHandlers) ValidateDocument(c *gin.Context) {
|
||||
var req ValidateDocumentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
userID := rbac.GetUserID(c)
|
||||
tenantID := rbac.GetTenantID(c)
|
||||
namespaceID := rbac.GetNamespaceID(c)
|
||||
|
||||
if userID == uuid.Nil || tenantID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Build validation prompt
|
||||
systemPrompt := `Du bist ein DSGVO-Compliance-Validator. Pruefe die Konsistenz und Vollstaendigkeit.
|
||||
Antworte NUR im JSON-Format:
|
||||
{
|
||||
"passed": boolean,
|
||||
"errors": [{"id": string, "severity": "error", "title": string, "description": string, "documentType": string, "legalReference": string}],
|
||||
"warnings": [{"id": string, "severity": "warning", "title": string, "description": string}],
|
||||
"suggestions": [{"id": string, "severity": "suggestion", "title": string, "description": string, "suggestion": string}]
|
||||
}`
|
||||
|
||||
validationPrompt := fmt.Sprintf("Validiere Dokument '%s'.\nInhalt:\n%s\nKontext:\n%v",
|
||||
req.DocumentType, req.DraftContent, req.ValidationContext)
|
||||
|
||||
// Detect PII
|
||||
dataCategories := h.piiDetector.DetectDataCategories(req.DraftContent)
|
||||
|
||||
chatReq := &llm.ChatRequest{
|
||||
Model: req.Model,
|
||||
Messages: []llm.Message{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: validationPrompt},
|
||||
},
|
||||
MaxTokens: 8192,
|
||||
Temperature: 0.1,
|
||||
}
|
||||
|
||||
gatedReq, err := h.accessGate.ProcessChatRequest(
|
||||
c.Request.Context(),
|
||||
userID, tenantID, namespaceID,
|
||||
chatReq, dataCategories,
|
||||
)
|
||||
if err != nil {
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, "", "validate", false, 0, err.Error())
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "access_denied",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.accessGate.ExecuteChat(c.Request.Context(), gatedReq)
|
||||
if err != nil {
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, "", "validate", false, 0, err.Error())
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "llm_error",
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tokensUsed := 0
|
||||
if resp.Usage.TotalTokens > 0 {
|
||||
tokensUsed = resp.Usage.TotalTokens
|
||||
}
|
||||
|
||||
h.logDraftAudit(c, userID, tenantID, req.DocumentType, "", "validate", true, tokensUsed, "")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"document_type": req.DocumentType,
|
||||
"validation": resp.Message.Content,
|
||||
"model": resp.Model,
|
||||
"provider": resp.Provider,
|
||||
"tokens_used": tokensUsed,
|
||||
"pii_detected": gatedReq.PIIDetected,
|
||||
})
|
||||
}
|
||||
|
||||
// GetDraftHistory returns the audit trail of all drafting operations for a tenant
|
||||
func (h *DraftingHandlers) GetDraftHistory(c *gin.Context) {
|
||||
userID := rbac.GetUserID(c)
|
||||
tenantID := rbac.GetTenantID(c)
|
||||
|
||||
if userID == uuid.Nil || tenantID == uuid.Nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Query audit store for drafting operations
|
||||
entries, _, err := h.auditStore.QueryGeneralAuditEntries(c.Request.Context(), &audit.GeneralAuditFilter{
|
||||
TenantID: tenantID,
|
||||
ResourceType: "compliance_document",
|
||||
Limit: 50,
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to query draft history"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"history": entries,
|
||||
"total": len(entries),
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Audit Logging
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (h *DraftingHandlers) logDraftAudit(
|
||||
c *gin.Context,
|
||||
userID, tenantID uuid.UUID,
|
||||
documentType, scopeLevel, operation string,
|
||||
constraintsRespected bool,
|
||||
tokensUsed int,
|
||||
errorMsg string,
|
||||
) {
|
||||
newValues := map[string]any{
|
||||
"document_type": documentType,
|
||||
"scope_level": scopeLevel,
|
||||
"constraints_respected": constraintsRespected,
|
||||
"tokens_used": tokensUsed,
|
||||
}
|
||||
if errorMsg != "" {
|
||||
newValues["error"] = errorMsg
|
||||
}
|
||||
|
||||
entry := h.trailBuilder.NewGeneralEntry().
|
||||
WithTenant(tenantID).
|
||||
WithUser(userID).
|
||||
WithAction("drafting_engine." + operation).
|
||||
WithResource("compliance_document", nil).
|
||||
WithNewValues(newValues).
|
||||
WithClient(c.ClientIP(), c.GetHeader("User-Agent"))
|
||||
|
||||
go func() {
|
||||
entry.Save(c.Request.Context())
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user