test(dse): adopt canonical v3 tests + criteria/GT/validation
CI / detect-changes (pull_request) Failing after 5s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Failing after 4s
CI / secret-scan (pull_request) Failing after 4s
CI / dep-audit (pull_request) Failing after 2s
CI / sbom-scan (pull_request) Failing after 2s
CI / build-sha-integrity (pull_request) Failing after 3s
CI / validate-canonical-controls (pull_request) Failing after 3s
CI / loc-budget (pull_request) Has been skipped
CI / go-lint (pull_request) Has been skipped
CI / python-lint (pull_request) Has been skipped
CI / nodejs-lint (pull_request) Has been skipped
CI / nodejs-build (pull_request) Has been skipped
CI / test-go (pull_request) Has been skipped
CI / iace-gt-coverage (pull_request) Has been skipped
CI / test-python-backend (pull_request) Has been skipped
CI / test-python-document-crawler (pull_request) Has been skipped
CI / test-python-dsms-gateway (pull_request) Has been skipped

Replace the reconstructed test_dse_agent.py with the canonical version and add
the companion unit tests (classification_gate, embedding_recall) covering the
recovered v3 modules. Include the curated DSE criteria backup + changelog
(legal-note rationale per control), the v1 validation writeup, and the
multi-company DSE ground-truth fulltexts (elli/eto/mercedes/safetykon) used
for threshold calibration.

18 DSE tests green offline (DB/embedding/LLM stubbed).

dev-only, no deploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-21 12:35:16 +02:00
parent ce6b4c58e3
commit 8af9584d09
10 changed files with 1278 additions and 72 deletions
@@ -0,0 +1,67 @@
"""DSE Embedding-Recall — deterministische semantische Schicht (gecacht).
Testet die reine Logik OHNE Embedding-Service: Cache-Treffer-Pfad,
Schwellen-Filter, Kandidaten-Schnitt, Reachability-Guard. Das Einbetten selbst
(Embedding-Service) ist Integration und wird auf macmini/Prod validiert.
"""
from __future__ import annotations
import asyncio
import json
import compliance.services.specialist_agents.dse._embedding_recall as er
_TEXT = ("Datenschutzerklaerung der Muster GmbH. " * 20) # > 100 Zeichen
def _seed_cache(tmp_path, scores: dict[str, float]) -> str:
p = tmp_path / "dse_embed_cache.json"
p.write_text(json.dumps({er._doc_hash(_TEXT): scores}))
return str(p)
def test_doc_hash_deterministic():
# feste Funktion: gleicher Text → gleicher Hash (Reproduzierbarkeit)
assert er._doc_hash(_TEXT) == er._doc_hash(_TEXT)
assert er._doc_hash("a") != er._doc_hash("b")
def test_cache_hit_threshold_filter(tmp_path, monkeypatch):
# Cache-Treffer: kein Embedding-Service nötig. Nur Scores >= Schwelle UND
# in den Kandidaten werden zurückgegeben.
scores = {"DATA-1": 0.71, "DATA-2": 0.60, "AUTH-3": 0.68, "SEC-4": 0.50}
monkeypatch.setenv("DSE_EMBED_CACHE", _seed_cache(tmp_path, scores))
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "dse_embed_cache.json"))
cands = ["DATA-1", "DATA-2", "AUTH-3", "SEC-4"]
out = asyncio.run(er.embedding_recall(_TEXT, cands, threshold=0.65))
# >=0.65: DATA-1 (0.71), AUTH-3 (0.68). NICHT DATA-2 (0.60), SEC-4 (0.50).
assert out == {"DATA-1", "AUTH-3"}
def test_cache_hit_candidate_intersection(tmp_path, monkeypatch):
# Nur Kandidaten (durchgefallene Controls) zählen — andere ignoriert.
scores = {"DATA-1": 0.90, "DATA-2": 0.90}
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "c.json"))
(tmp_path / "c.json").write_text(json.dumps({er._doc_hash(_TEXT): scores}))
out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"], threshold=0.65))
assert out == {"DATA-1"} # DATA-2 nicht in Kandidaten
def test_empty_inputs():
assert asyncio.run(er.embedding_recall("zu kurz", ["X"])) == set()
assert asyncio.run(er.embedding_recall(_TEXT, [])) == set()
def test_service_down_returns_empty(tmp_path, monkeypatch):
# Kein Cache + Service nicht erreichbar → leer (deterministischer Layer trägt),
# KEIN Hang.
monkeypatch.setattr(er, "_CACHE_PATH", str(tmp_path / "none.json"))
async def _unreachable(timeout=2.0):
return False
monkeypatch.setattr(er, "_embedding_reachable", _unreachable)
out = asyncio.run(er.embedding_recall(_TEXT, ["DATA-1"]))
assert out == set()