fix(ai-sdk): national-law subsidiarity in authority rerank (DSGVO > BDSG for general questions)
CI / detect-changes (pull_request) Successful in 11s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Successful in 9s
CI / secret-scan (pull_request) Successful in 11s
CI / dep-audit (pull_request) Failing after 54s
CI / sbom-scan (pull_request) Failing after 59s
CI / build-sha-integrity (pull_request) Successful in 8s
CI / validate-canonical-controls (pull_request) Successful in 8s
CI / loc-budget (pull_request) Successful in 23s
CI / go-lint (pull_request) Successful in 57s
CI / python-lint (pull_request) Failing after 16s
CI / nodejs-lint (pull_request) Failing after 1m11s
CI / nodejs-build (pull_request) Successful in 3m4s
CI / test-go (pull_request) Successful in 1m1s
CI / iace-gt-coverage (pull_request) Successful in 18s
CI / test-python-backend (pull_request) Successful in 25s
CI / test-python-document-crawler (pull_request) Successful in 14s
CI / test-python-dsms-gateway (pull_request) Successful in 12s
CI / detect-changes (pull_request) Successful in 11s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Successful in 9s
CI / secret-scan (pull_request) Successful in 11s
CI / dep-audit (pull_request) Failing after 54s
CI / sbom-scan (pull_request) Failing after 59s
CI / build-sha-integrity (pull_request) Successful in 8s
CI / validate-canonical-controls (pull_request) Successful in 8s
CI / loc-budget (pull_request) Successful in 23s
CI / go-lint (pull_request) Successful in 57s
CI / python-lint (pull_request) Failing after 16s
CI / nodejs-lint (pull_request) Failing after 1m11s
CI / nodejs-build (pull_request) Successful in 3m4s
CI / test-go (pull_request) Successful in 1m1s
CI / iace-gt-coverage (pull_request) Successful in 18s
CI / test-python-backend (pull_request) Successful in 25s
CI / test-python-document-crawler (pull_request) Successful in 14s
CI / test-python-dsms-gateway (pull_request) Successful in 12s
The authority reranker (wired in legal_rag_client.go:168) had no national-subsidiarity dimension, so a general BDSG paragraph could outrank the primary DSGVO article. Surfaced by the KB-2026.1 BDSG pilot (dp_05/08/11 + cr_07). - authorityScore: DE binding_law in an EU-primary domain WITHOUT a co-primary topic match -> soft demote (subsidiarityPen 0.18), not exclusion. National special rules stay co-primary via the topic ontology (DSB Art.37+§38, special categories Art.9+§22, ...). - queryDomain: fall back to a regulation-name mention (DSGVO/BDSG/CRA) so a question phrased around the act is domain-scoped even without a topical keyword (fixes cr_07: BDSG Teil-3 §64). - data_protection keyword stem 'auftragsverarbeit' (catches Auftragsverarbeitungsvertrag). Pure ranking logic, no data manipulation; soft demotes keep national rules visible. Build result (DSGVO+BDSG): degraded=0, must_not=0. go build/vet/test ./... green; 6 new table tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,73 @@ func TestRerankByAuthority_Acceptance(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Subsidiarity (KB-2026.1 BDSG-pilot regression): a national implementing § that is NOT a
|
||||
// co-primary topic norm must not outrank the primary DSGVO article on a general question.
|
||||
t.Run("subsidiarity dp_05: BDSG §23 below DSGVO Art.6 (Rechtsgrundlage)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 23 BDSG", "BDSG", "DE", 0.70),
|
||||
bindingRes("Art. 6 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Welche Rechtsgrundlagen erlauben eine Verarbeitung personenbezogener Daten?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.6 must beat general BDSG §, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
if len(out) != 2 {
|
||||
t.Fatalf("BDSG must stay visible (soft demote), got len=%d", len(out))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("subsidiarity dp_08: BDSG §70 below DSGVO Art.28 (Auftragsverarbeitung)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 70 BDSG", "BDSG", "DE", 0.70), // Teil 3 → scope + subsidiarity
|
||||
bindingRes("Art. 28 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Was muss ein Auftragsverarbeitungsvertrag enthalten?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.28 must beat BDSG §70, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("subsidiarity dp_11: BDSG §22 below DSGVO Art.32 on a TOM question", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 22 BDSG", "BDSG", "DE", 0.70),
|
||||
bindingRes("Art. 32 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Welche technischen und organisatorischen Massnahmen verlangt das Datenschutzrecht?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.32 must beat BDSG §22 on a non-topic TOM question, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cr_07: a 'DSGVO' mention scopes the domain so BDSG Teil-3 §64 is demoted", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 64 BDSG", "BDSG", "DE", 0.70), // Teil 3 (law enforcement)
|
||||
bindingRes("Art. 32 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
// Query has no DP keyword but names the DSGVO → domain fallback scopes it data_protection,
|
||||
// so scope+subsidiarity demote the law-enforcement § below the primary norm.
|
||||
out := rerankByAuthority("Welche rechtliche Grundlage gilt fuer technische und organisatorische Massnahmen - DSGVO oder ein Standard?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO must win on a DSGVO-mention question, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("co-primary dp_01: BDSG §38 stays top on a DSB question (national special rule)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 38 BDSG", "BDSG", "DE", 0.66),
|
||||
bindingRes("Art. 37 DSGVO", "DSGVO", "EU", 0.64),
|
||||
}
|
||||
out := rerankByAuthority("Ab wann muss ein Datenschutzbeauftragter benannt werden?", in)
|
||||
// DSB topic → §38 is co-primary (topic-matched, NOT subsidiarity-demoted) and keeps its
|
||||
// semantic lead; Art. 37 stays a close second. Both remain top-2.
|
||||
if out[0].RegulationShort != "BDSG" {
|
||||
t.Fatalf("BDSG §38 (DSB co-primary) must stay top, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
if out[1].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("Art. 37 DSGVO must stay co-primary second, got %q", out[1].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nothing is dropped and topic amplifies", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
guidanceRes("ENISA", "ENISA", 0.72),
|
||||
|
||||
Reference in New Issue
Block a user