internal/rules implements Stufe 2 from the core principle: the LLM extracts facts, this deterministic engine judges them against versioned YAML rules. Facts/Condition/Rule/Finding types, a loader that refuses to load on a missing id/version or a duplicate rule id rather than silently skipping a bad file, and Evaluate() matching facts against rules. Two real rules grounded in verified research (see rules/OPEN.md for the open questions that surfaced along the way): - WK-001: no disclosure at all despite consideration (§ 5a Abs. 4 UWG, § 22 Abs. 1 MStV) - WK-004: disclosure present but hidden behind a "mehr anzeigen" cut (§ 5a Abs. 4 UWG, Leitfaden der Medienanstalten, LG Köln 12.05.2026) The two are deliberately disjoint (WK-004 requires disclosure_present= true) so a post with no disclosure at all doesn't double-fire both rules. Golden suite in testdata/golden/ covers both rules plus two clean cases; it's this suite, not the UI, that's the actual asset per CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.3 KiB
Go
50 lines
1.3 KiB
Go
package rules
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Load liest alle *.yaml-Dateien aus fsys (nicht rekursiv) und parst sie
|
|
// als Regeln. Ein Fehler in einer Datei (Parse-Fehler, fehlende ID/
|
|
// Version) bricht das Laden komplett ab, statt die Datei stillschweigend
|
|
// zu überspringen — ein halb geladenes Regelwerk ist gefährlicher als
|
|
// ein Start, der mit einem klaren Fehler abbricht.
|
|
func Load(fsys fs.FS) ([]Rule, error) {
|
|
entries, err := fs.ReadDir(fsys, ".")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rules: read dir: %w", err)
|
|
}
|
|
|
|
seen := make(map[string]bool)
|
|
var result []Rule
|
|
for _, entry := range entries {
|
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".yaml" {
|
|
continue
|
|
}
|
|
data, err := fs.ReadFile(fsys, entry.Name())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rules: read %s: %w", entry.Name(), err)
|
|
}
|
|
var r Rule
|
|
if err := yaml.Unmarshal(data, &r); err != nil {
|
|
return nil, fmt.Errorf("rules: parse %s: %w", entry.Name(), err)
|
|
}
|
|
if r.ID == "" {
|
|
return nil, fmt.Errorf("rules: %s: missing id", entry.Name())
|
|
}
|
|
if r.Version == 0 {
|
|
return nil, fmt.Errorf("rules: %s: missing version", entry.Name())
|
|
}
|
|
if seen[r.ID] {
|
|
return nil, fmt.Errorf("rules: %s: duplicate rule id %s", entry.Name(), r.ID)
|
|
}
|
|
seen[r.ID] = true
|
|
result = append(result, r)
|
|
}
|
|
return result, nil
|
|
}
|