1e5aaf7103
CI / detect-changes (pull_request) Successful in 13s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Successful in 5s
CI / secret-scan (pull_request) Successful in 11s
CI / dep-audit (pull_request) Failing after 54s
CI / sbom-scan (pull_request) Failing after 1m1s
CI / build-sha-integrity (pull_request) Successful in 11s
CI / validate-canonical-controls (pull_request) Successful in 7s
CI / loc-budget (pull_request) Successful in 23s
CI / go-lint (pull_request) Successful in 53s
CI / python-lint (pull_request) Failing after 17s
CI / nodejs-lint (pull_request) Failing after 1m6s
CI / nodejs-build (pull_request) Successful in 2m59s
CI / test-go (pull_request) Successful in 1m0s
CI / iace-gt-coverage (pull_request) Successful in 17s
CI / test-python-backend (pull_request) Successful in 26s
CI / test-python-document-crawler (pull_request) Successful in 12s
CI / test-python-dsms-gateway (pull_request) Successful in 8s
Der Advisor fan-outete bisher selbst ueber eine feste Liste expliziter Collections
(advisor-rag.ts) und umging damit das #61-Scope-Routing (das nur den Default-Pfad
routet) → der gemessene +28-Retrieval-Gewinn (CB-100: 53→81, 0 Regr) kam nie beim
Antwort-LLM an. Dieser Router zieht den Fan-out in die Retriever-Schicht:
- SDK: LegalRAGClient.Retrieve() + POST /sdk/v1/rag/retrieve {query, top_k} —
fan-outet server-seitig ueber die Broad-Authority-Base + die KB-2026.1-Slice bei
inKBScope, merge+dedup, sortiert nach Authority-Score (rerankByAuthority je
Collection), top-K. Index-Warmup vor dem nebenlaeufigen Fan-out (Map-Race-frei).
Per-Env via RAG_ROUTER_COLLECTIONS.
- admin: advisor-rag.ts ruft EINMAL /retrieve statt 6-fach expliziter Collections.
Advisor ist collection-agnostisch (Vertrag Compiler→Collections→Retriever→Advisor);
COMPLIANCE_COLLECTIONS/searchCollection entfernt.
Validierung: Go-Unit (Router-Selektion, dedup); e2e gegen dev-Qdrant (echter
Retrieve(), CB-100-Stichprobe stride 5): OLD-hit 11/20 → NEW-hit 15/20, GAIN 4
(alle DS-Guidance), REGR 0 — reproduziert den +28/0-Regr durch den Produktionscode.
TS-Tests auf den Single-/retrieve-Call angepasst.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
package ucca
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func TestRouterBaseCollections(t *testing.T) {
|
|
c := &LegalRAGClient{}
|
|
os.Unsetenv("RAG_ROUTER_COLLECTIONS")
|
|
def := c.routerBaseCollections()
|
|
if len(def) != 6 || def[1] != "bp_compliance_ce" {
|
|
t.Fatalf("default base collections unexpected: %v", def)
|
|
}
|
|
|
|
os.Setenv("RAG_ROUTER_COLLECTIONS", " bp_compliance_ce , kb_2026_1_build ,, ")
|
|
defer os.Unsetenv("RAG_ROUTER_COLLECTIONS")
|
|
got := c.routerBaseCollections()
|
|
if len(got) != 2 || got[0] != "bp_compliance_ce" || got[1] != "kb_2026_1_build" {
|
|
t.Fatalf("env override parse failed (trim/empty): %v", got)
|
|
}
|
|
}
|
|
|
|
func TestRouterSliceSelection(t *testing.T) {
|
|
// The router appends the slice exactly when the query is in scope (inKBScope) and routing is on.
|
|
// Mirror the selection logic so a regression in either is caught without a live Qdrant.
|
|
c := &LegalRAGClient{kbSliceCollection: "kb_2026_1_build", kbScopeRoutingEnabled: true}
|
|
sel := func(q string) bool {
|
|
colls := c.routerBaseCollections()
|
|
if c.kbScopeRoutingEnabled && c.kbSliceCollection != "" && inKBScope(q) {
|
|
colls = append(colls, c.kbSliceCollection)
|
|
}
|
|
for _, x := range colls {
|
|
if x == c.kbSliceCollection {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if !sel("Welche neun Kriterien nennt WP248 fuer ein hohes Risiko?") {
|
|
t.Error("in-scope guidance query must include the slice")
|
|
}
|
|
if sel("Was sagt NIST SP 800-53 zu Access Control?") {
|
|
t.Error("out-of-scope query must NOT include the slice")
|
|
}
|
|
c.kbScopeRoutingEnabled = false
|
|
if sel("Welche Kriterien nennt WP248?") {
|
|
t.Error("routing disabled => slice never included")
|
|
}
|
|
}
|
|
|
|
func TestDedupResults(t *testing.T) {
|
|
in := []LegalSearchResult{
|
|
{RegulationCode: "EDPB WP248", ArticleLabel: "III.B", Text: "lorem", Score: 0.7},
|
|
{RegulationCode: "EDPB WP248", ArticleLabel: "III.B", Text: "lorem", Score: 0.9}, // dup, higher score
|
|
{RegulationCode: "DSGVO", ArticleLabel: "Art. 35", Text: "ipsum", Score: 0.8},
|
|
}
|
|
out := dedupResults(in)
|
|
if len(out) != 2 {
|
|
t.Fatalf("expected 2 deduped, got %d", len(out))
|
|
}
|
|
for _, r := range out {
|
|
if r.RegulationCode == "EDPB WP248" && r.Score != 0.9 {
|
|
t.Errorf("dedup must keep highest score, got %v", r.Score)
|
|
}
|
|
}
|
|
}
|