feat: BreakPilot PWA - Full codebase (clean push without large binaries)
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
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.
This commit is contained in:
255
edu-search-service/internal/policy/loader.go
Normal file
255
edu-search-service/internal/policy/loader.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Loader handles loading policy configuration from YAML files.
|
||||
type Loader struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
// NewLoader creates a new Loader instance.
|
||||
func NewLoader(store *Store) *Loader {
|
||||
return &Loader{store: store}
|
||||
}
|
||||
|
||||
// LoadFromFile loads policy configuration from a YAML file.
|
||||
func (l *Loader) LoadFromFile(ctx context.Context, path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read YAML file: %w", err)
|
||||
}
|
||||
|
||||
config, err := ParseYAML(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse YAML: %w", err)
|
||||
}
|
||||
|
||||
return l.store.LoadFromYAML(ctx, config)
|
||||
}
|
||||
|
||||
// ParseYAML parses YAML configuration data.
|
||||
func ParseYAML(data []byte) (*BundeslaenderConfig, error) {
|
||||
// First, parse as a generic map to handle the inline Bundeslaender
|
||||
var rawConfig map[string]interface{}
|
||||
if err := yaml.Unmarshal(data, &rawConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse YAML: %w", err)
|
||||
}
|
||||
|
||||
config := &BundeslaenderConfig{
|
||||
Bundeslaender: make(map[string]PolicyConfig),
|
||||
}
|
||||
|
||||
// Parse federal
|
||||
if federal, ok := rawConfig["federal"]; ok {
|
||||
if federalMap, ok := federal.(map[string]interface{}); ok {
|
||||
config.Federal = parsePolicyConfig(federalMap)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse default_operations
|
||||
if ops, ok := rawConfig["default_operations"]; ok {
|
||||
if opsMap, ok := ops.(map[string]interface{}); ok {
|
||||
config.DefaultOperations = parseOperationsConfig(opsMap)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse pii_rules
|
||||
if rules, ok := rawConfig["pii_rules"]; ok {
|
||||
if rulesSlice, ok := rules.([]interface{}); ok {
|
||||
for _, rule := range rulesSlice {
|
||||
if ruleMap, ok := rule.(map[string]interface{}); ok {
|
||||
config.PIIRules = append(config.PIIRules, parsePIIRuleConfig(ruleMap))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse Bundeslaender (2-letter codes)
|
||||
bundeslaender := []string{"BW", "BY", "BE", "BB", "HB", "HH", "HE", "MV", "NI", "NW", "RP", "SL", "SN", "ST", "SH", "TH"}
|
||||
for _, bl := range bundeslaender {
|
||||
if blConfig, ok := rawConfig[bl]; ok {
|
||||
if blMap, ok := blConfig.(map[string]interface{}); ok {
|
||||
config.Bundeslaender[bl] = parsePolicyConfig(blMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parsePolicyConfig(m map[string]interface{}) PolicyConfig {
|
||||
pc := PolicyConfig{}
|
||||
|
||||
if name, ok := m["name"].(string); ok {
|
||||
pc.Name = name
|
||||
}
|
||||
|
||||
if sources, ok := m["sources"].([]interface{}); ok {
|
||||
for _, src := range sources {
|
||||
if srcMap, ok := src.(map[string]interface{}); ok {
|
||||
pc.Sources = append(pc.Sources, parseSourceConfig(srcMap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pc
|
||||
}
|
||||
|
||||
func parseSourceConfig(m map[string]interface{}) SourceConfig {
|
||||
sc := SourceConfig{
|
||||
TrustBoost: 0.5, // Default
|
||||
}
|
||||
|
||||
if domain, ok := m["domain"].(string); ok {
|
||||
sc.Domain = domain
|
||||
}
|
||||
if name, ok := m["name"].(string); ok {
|
||||
sc.Name = name
|
||||
}
|
||||
if license, ok := m["license"].(string); ok {
|
||||
sc.License = license
|
||||
}
|
||||
if legalBasis, ok := m["legal_basis"].(string); ok {
|
||||
sc.LegalBasis = legalBasis
|
||||
}
|
||||
if citation, ok := m["citation_template"].(string); ok {
|
||||
sc.CitationTemplate = citation
|
||||
}
|
||||
if trustBoost, ok := m["trust_boost"].(float64); ok {
|
||||
sc.TrustBoost = trustBoost
|
||||
}
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
func parseOperationsConfig(m map[string]interface{}) OperationsConfig {
|
||||
oc := OperationsConfig{}
|
||||
|
||||
if lookup, ok := m["lookup"].(map[string]interface{}); ok {
|
||||
oc.Lookup = parseOperationConfig(lookup)
|
||||
}
|
||||
if rag, ok := m["rag"].(map[string]interface{}); ok {
|
||||
oc.RAG = parseOperationConfig(rag)
|
||||
}
|
||||
if training, ok := m["training"].(map[string]interface{}); ok {
|
||||
oc.Training = parseOperationConfig(training)
|
||||
}
|
||||
if export, ok := m["export"].(map[string]interface{}); ok {
|
||||
oc.Export = parseOperationConfig(export)
|
||||
}
|
||||
|
||||
return oc
|
||||
}
|
||||
|
||||
func parseOperationConfig(m map[string]interface{}) OperationConfig {
|
||||
oc := OperationConfig{}
|
||||
|
||||
if allowed, ok := m["allowed"].(bool); ok {
|
||||
oc.Allowed = allowed
|
||||
}
|
||||
if requiresCitation, ok := m["requires_citation"].(bool); ok {
|
||||
oc.RequiresCitation = requiresCitation
|
||||
}
|
||||
|
||||
return oc
|
||||
}
|
||||
|
||||
func parsePIIRuleConfig(m map[string]interface{}) PIIRuleConfig {
|
||||
rc := PIIRuleConfig{
|
||||
Severity: "block", // Default
|
||||
}
|
||||
|
||||
if name, ok := m["name"].(string); ok {
|
||||
rc.Name = name
|
||||
}
|
||||
if ruleType, ok := m["type"].(string); ok {
|
||||
rc.Type = ruleType
|
||||
}
|
||||
if pattern, ok := m["pattern"].(string); ok {
|
||||
rc.Pattern = pattern
|
||||
}
|
||||
if severity, ok := m["severity"].(string); ok {
|
||||
rc.Severity = severity
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
// LoadDefaults loads a minimal set of default data (for testing or when no YAML exists).
|
||||
func (l *Loader) LoadDefaults(ctx context.Context) error {
|
||||
// Create federal policy with KMK
|
||||
federalPolicy, err := l.store.CreatePolicy(ctx, &CreateSourcePolicyRequest{
|
||||
Name: "KMK & Bundesebene",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create federal policy: %w", err)
|
||||
}
|
||||
|
||||
trustBoost := 0.95
|
||||
legalBasis := "Amtliche Werke (§5 UrhG)"
|
||||
citation := "Quelle: KMK, {title}, {date}"
|
||||
|
||||
_, err = l.store.CreateSource(ctx, &CreateAllowedSourceRequest{
|
||||
PolicyID: federalPolicy.ID,
|
||||
Domain: "kmk.org",
|
||||
Name: "Kultusministerkonferenz",
|
||||
License: LicenseParagraph5,
|
||||
LegalBasis: &legalBasis,
|
||||
CitationTemplate: &citation,
|
||||
TrustBoost: &trustBoost,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create KMK source: %w", err)
|
||||
}
|
||||
|
||||
// Create default PII rules
|
||||
defaultRules := DefaultPIIRules()
|
||||
for _, rule := range defaultRules {
|
||||
_, err := l.store.CreatePIIRule(ctx, &CreatePIIRuleRequest{
|
||||
Name: rule.Name,
|
||||
RuleType: PIIRuleType(rule.Type),
|
||||
Pattern: rule.Pattern,
|
||||
Severity: PIISeverity(rule.Severity),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create PII rule %s: %w", rule.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasData checks if the policy tables already have data.
|
||||
func (l *Loader) HasData(ctx context.Context) (bool, error) {
|
||||
policies, _, err := l.store.ListPolicies(ctx, &PolicyListFilter{Limit: 1})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(policies) > 0, nil
|
||||
}
|
||||
|
||||
// LoadIfEmpty loads data from YAML only if tables are empty.
|
||||
func (l *Loader) LoadIfEmpty(ctx context.Context, path string) error {
|
||||
hasData, err := l.HasData(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasData {
|
||||
return nil // Already has data, skip loading
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
// File doesn't exist, load defaults
|
||||
return l.LoadDefaults(ctx)
|
||||
}
|
||||
|
||||
return l.LoadFromFile(ctx, path)
|
||||
}
|
||||
Reference in New Issue
Block a user