Compare commits

...

11 Commits

Author SHA1 Message Date
Benjamin Admin 6f0afa98be chore: gitignore build/test artifacts
docs-site (generated), screenshots, e2e/test/audit reports, one-off shot scripts + a stray folder. Identified during 2026-06-21 working-tree cleanup.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:42:06 +02:00
Benjamin Admin 629cb377d5 docs(salvage): benchmark-archive note + impressum ground-truth
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:40:43 +02:00
Benjamin Admin 9cf53090eb wip(test): category_tester + audit_walk_zip_builder (salvaged)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:40:43 +02:00
Benjamin Admin e2bc6d02d5 wip(use-case): use_case_registry update + test (salvaged)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:40:43 +02:00
Benjamin Admin abe7e59625 fix(impressum): sector-gate respects on-topic criteria (parity with DSE)
A clearly impressum-thematic control (e.g. MStV 18 with inherited GOV prefix) is no longer dropped by the sector-prefix gate - topic overlap is the stronger signal. Mirrors dse/v3_engine. Salvaged WIP.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:40:43 +02:00
Benjamin Admin 28798a3ef6 feat(controls): control_pendants table - license-pendant mapping
Maps self-written controls to commercially-usable source controls expressing the same obligation (Phase-2 reconcile, embedding-kNN + Haiku 2026-06-15). Additive, idempotent. Salvaged from uncommitted WIP. [migration-approved]

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 12:40:43 +02:00
Benjamin Admin 6b9c7984b4 fix(ci): regulatory_news Zeitbomben-Test entschaerfen — test-go + Deploy entsperren
CI / detect-changes (push) Successful in 8s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 4s
CI / loc-budget (push) Successful in 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m2s
CI / test-go (push) Successful in 1m8s
CI / iace-gt-coverage (push) Successful in 19s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
test-go failte seit 2026-06-19: VBR-OBL-001 ("Widerrufsbutton ab 19.06.2026") ist
seit dem Stichtag abgelaufen und faellt aus dem Zukunfts-Horizont von GetRegulatoryNews,
wodurch TestGetRegulatoryNews_FromRealFiles bricht. Fix: now-Referenz injizierbar
(GetRegulatoryNewsAt), Test nutzt fixes Datum -> deterministisch. Produktions-Caller
unveraendert (Wrapper). admin rag-query Marker, damit detect-changes admin mitbaut
(article_label-Rendering). go vet + alle ai-sdk-Tests lokal gruen.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 00:51:47 +02:00
Benjamin Admin e646091ba2 chore(deploy): ai-sdk + admin neu bauen — Legal-Zitatfelder (article_label) nach Prod-Re-Ingest aktiv
CI / detect-changes (push) Successful in 8s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / nodejs-lint (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 6s
CI / loc-budget (push) Successful in 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m3s
CI / test-go (push) Failing after 57s
CI / iace-gt-coverage (push) Successful in 16s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
Triggert CI + detect-changes fuer ai-compliance-sdk + admin-compliance, nachdem der
vorige Deploy am last-build/main Tag-Bug haengenblieb (Builds uebersprungen). Nur
Doku-Kommentare, Logik unveraendert. Daten-Merge (Qdrant) ist bereits live.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-21 00:15:48 +02:00
Benjamin Admin 069b855b49 ci: re-trigger deploy — last-build/main tag-bug uebersprang ai-sdk/admin builds (Daten-Merge bereits live)
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 6s
CI / validate-canonical-controls (push) Successful in 4s
CI / nodejs-build (push) Successful in 3m3s
CI / secret-scan (push) Has been skipped
CI / loc-budget (push) Successful in 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Failing after 59s
CI / iace-gt-coverage (push) Successful in 16s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
2026-06-20 21:30:53 +02:00
Benjamin Admin 01af9b56a6 Merge branch 'main' of ssh://gitea.meghsakha.com:22222/Benjamin_Boenisch/breakpilot-compliance
CI / detect-changes (push) Successful in 9s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / build-sha-integrity (push) Successful in 6s
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 4s
CI / loc-budget (push) Successful in 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Failing after 1m3s
CI / iace-gt-coverage (push) Successful in 18s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / nodejs-build (push) Successful in 3m8s
2026-06-20 20:58:33 +02:00
Benjamin Admin 017c9b3c12 feat(advisor): Legal-RAG Zitier-Metadaten — ai-sdk + Advisor/Drafting lesen article_label
ai-sdk (legal_rag_client/scroll/types) liest die gepinnten Spec-Felder
article_label/regulation_code/article/paragraph/sub/citation_style/is_recital
mit Fallback auf alt-ingestierte Chunks (regulation_id, section); neuer getBool-Helfer.
Advisor + Drafting-Engine bilden die Quellenzeile primaer aus article_label
("BDSG § 38 Abs. 1"), sonst aus den strukturierten Feldern. 17 Tests gruen, tsc sauber.
Vertrag: docs-src/development/rag_reingest_spec.md (§2/§7). Deploy an den Re-Ingest
gekoppelt — neue Felder sind bis dahin leer (graceful Fallback).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-20 14:34:15 +02:00
19 changed files with 1064 additions and 12 deletions
+13
View File
@@ -48,3 +48,16 @@ backups/*.backup
*.wav
ai-compliance-sdk/server
*.bak
# Build/test artifacts (2026-06-21 cleanup)
docs-site/
ux-screenshots/
**/test-results/
**/audit-reports/
admin-compliance/e2e/reports/
admin-compliance/e2e/e2e/
design/redesign/*-preview.png
admin-compliance/BreakPilot-Pitch-Submission.html
admin-compliance/shot-ds.mjs
admin-compliance/ux-shots.mjs
Neuer Ordner mit Objekten/
@@ -24,6 +24,20 @@ describe('advisor-rag', () => {
expect(out).toEqual([{ content: 'Art. 35 DSGVO ...', source: 'DSGVO', score: 0.91 }])
})
it('haengt article/paragraph an die Quelle an (Fallback ohne article_label)', () => {
const out = mod.mapSdkResults([
{ text: 'Pflicht zur Benennung ...', regulation_short: 'BDSG', article: '§ 38', paragraph: '(1)', score: 0.8 },
])
expect(out).toEqual([{ content: 'Pflicht zur Benennung ...', source: 'BDSG § 38 (1)', score: 0.8 }])
})
it('nutzt article_label direkt, wenn vorhanden (druckbare Fundstelle)', () => {
const out = mod.mapSdkResults([
{ text: 'x', regulation_short: 'BDSG', article: '38', paragraph: '1', sub: 'Satz 2', article_label: 'BDSG § 38 Abs. 1', score: 0.9 },
])
expect(out[0].source).toBe('BDSG § 38 Abs. 1')
})
it('faellt auf regulation_name/code zurueck und filtert leere Inhalte', () => {
const out = mod.mapSdkResults([
{ text: '', regulation_short: 'X' },
+16 -1
View File
@@ -7,6 +7,7 @@
* — damit profitiert der Advisor vom reicheren Embedding.
*
* Fehler je Collection werden geschluckt (graceful: Antwort ohne diesen Treffer).
* Fundstellen via article_label sind live ab dem Prod-Re-Ingest 2026-06.
*/
const SDK_URL =
@@ -31,6 +32,12 @@ interface SdkRagResult {
regulation_code?: string
regulation_name?: string
regulation_short?: string
article_label?: string
article?: string
paragraph?: string
sub?: string
citation_style?: string
is_recital?: boolean
category?: string
source_url?: string
score?: number
@@ -47,7 +54,15 @@ export function mapSdkResults(results: SdkRagResult[] | undefined): ScoredPassag
return (results || [])
.map((r) => ({
content: r.text || '',
source: r.regulation_short || r.regulation_name || r.regulation_code || 'Unbekannt',
// Fundstelle: article_label ist die fertig formatierte, druckbare Quelle aus der
// Ingestion ("BDSG § 38 Abs. 1"); Fallback baut sie aus den strukturierten Feldern
// (bzw. alt-ingestierte Chunks ohne Legal-Metadaten). Siehe rag_reingest_spec.md §2/§7.
source:
(r.article_label && r.article_label.trim()) ||
[r.regulation_short || r.regulation_name || r.regulation_code, r.article, r.paragraph, r.sub]
.filter(Boolean)
.join(' ') ||
'Unbekannt',
score: typeof r.score === 'number' ? r.score : 0,
}))
.filter((p) => p.content)
@@ -5,6 +5,7 @@
* Frueher: bp-core-rag-service:8097 — der existiert auf prod NICHT (nur macmini/dev),
* dadurch lieferte die Drafting-Engine dort keinen RAG-Kontext. Die ai-sdk embeddet
* mit bge-m3 und ist prod-erreichbar. Genutzt von draft-, chat- und vendor-review-Routes.
* Fundstellen via article_label sind live ab dem Prod-Re-Ingest 2026-06.
*/
const SDK_URL =
@@ -18,6 +19,10 @@ interface SdkRagResult {
regulation_code?: string
regulation_name?: string
regulation_short?: string
article_label?: string
article?: string
paragraph?: string
sub?: string
// Rueckwaerts-kompatibel, falls eine Quelle noch das alte rag-service-Format liefert:
content?: string
source_name?: string
@@ -56,12 +61,13 @@ export async function queryRAG(query: string, topK = 3, collection?: string): Pr
return results
.map((r, i) => {
const base =
r.regulation_short || r.regulation_name || r.regulation_code || r.source_name || r.source_code
// article_label = fertig formatierte Fundstelle aus der Ingestion ("BDSG § 38 Abs. 1");
// Fallback baut sie aus den strukturierten Feldern. Siehe rag_reingest_spec.md §2/§7.
const source =
r.regulation_short ||
r.regulation_name ||
r.regulation_code ||
r.source_name ||
r.source_code ||
(r.article_label && r.article_label.trim()) ||
[base, r.article, r.paragraph, r.sub].filter(Boolean).join(' ') ||
'Unbekannt'
const content = r.text || r.content || ''
return `[Quelle ${i + 1}: ${source}]\n${content}`
@@ -95,12 +95,29 @@ func (c *LegalRAGClient) searchInternal(ctx context.Context, collection string,
results := make([]LegalSearchResult, len(hits))
for i, hit := range hits {
// Legal-Metadaten nach rag_reingest_spec.md §2: bevorzugt die normalisierten Felder
// (article_label/regulation_code/article/...); Fallback auf alte Feldnamen, solange der
// Korpus noch nicht re-ingestiert ist (regulation_id, section="§ 38").
regCode := getString(hit.Payload, "regulation_code")
if regCode == "" {
regCode = getString(hit.Payload, "regulation_id")
}
article := getString(hit.Payload, "article")
if article == "" {
article = getString(hit.Payload, "section")
}
results[i] = LegalSearchResult{
Text: getString(hit.Payload, "chunk_text"),
RegulationCode: getString(hit.Payload, "regulation_id"),
RegulationCode: regCode,
RegulationName: getString(hit.Payload, "regulation_name_de"),
RegulationShort: getString(hit.Payload, "regulation_short"),
Category: getString(hit.Payload, "category"),
ArticleLabel: getString(hit.Payload, "article_label"),
Article: article,
Paragraph: getString(hit.Payload, "paragraph"),
Sub: getString(hit.Payload, "sub"),
IsRecital: getBool(hit.Payload, "is_recital"),
CitationStyle: getString(hit.Payload, "citation_style"),
Pages: getIntSlice(hit.Payload, "pages"),
SourceURL: getString(hit.Payload, "source"),
Score: hit.Score,
@@ -191,6 +191,13 @@ func (c *LegalRAGClient) ScrollDocumentIndex(ctx context.Context, collection str
// Helper functions
func getBool(m map[string]interface{}, key string) bool {
if v, ok := m[key].(bool); ok {
return v
}
return false
}
func getString(m map[string]interface{}, key string) string {
if v, ok := m[key]; ok {
if s, ok := v.(string); ok {
@@ -3,6 +3,8 @@ package ucca
import "time"
// LegalSearchResult represents a single search result from the compliance corpus.
// Legal-Zitatfelder (article_label/article/paragraph/...) sind live ab dem
// Prod-Re-Ingest 2026-06 (siehe docs-src/development/rag_reingest_spec.md §2).
type LegalSearchResult struct {
Text string `json:"text"`
RegulationCode string `json:"regulation_code"`
@@ -10,7 +12,11 @@ type LegalSearchResult struct {
RegulationShort string `json:"regulation_short"`
Category string `json:"category"`
Article string `json:"article,omitempty"`
ArticleLabel string `json:"article_label,omitempty"`
Paragraph string `json:"paragraph,omitempty"`
Sub string `json:"sub,omitempty"`
IsRecital bool `json:"is_recital,omitempty"`
CitationStyle string `json:"citation_style,omitempty"`
Pages []int `json:"pages,omitempty"`
SourceURL string `json:"source_url"`
Score float64 `json:"score"`
@@ -33,6 +33,12 @@ type RegulatoryNewsFilter struct {
// GetRegulatoryNews scans all v2 obligations for upcoming deadlines
// and returns formatted news items sorted by urgency.
func GetRegulatoryNews(regulations map[string]*V2RegulationFile, filter RegulatoryNewsFilter) []RegulatoryNewsItem {
return GetRegulatoryNewsAt(regulations, filter, time.Now().UTC())
}
// GetRegulatoryNewsAt is GetRegulatoryNews with an injectable reference time so the
// upcoming-deadline window is deterministic in tests (no time-bomb once a deadline passes).
func GetRegulatoryNewsAt(regulations map[string]*V2RegulationFile, filter RegulatoryNewsFilter, now time.Time) []RegulatoryNewsItem {
if filter.HorizonDays <= 0 {
filter.HorizonDays = 365
}
@@ -40,7 +46,7 @@ func GetRegulatoryNews(regulations map[string]*V2RegulationFile, filter Regulato
filter.Limit = 5
}
today := time.Now().UTC().Truncate(24 * time.Hour)
today := now.UTC().Truncate(24 * time.Hour)
horizon := today.AddDate(0, 0, filter.HorizonDays)
var items []RegulatoryNewsItem
@@ -174,7 +174,10 @@ func TestGetRegulatoryNews_FromRealFiles(t *testing.T) {
if err != nil {
t.Skipf("could not load v2 regulations: %v", err)
}
items := GetRegulatoryNews(regs, RegulatoryNewsFilter{Limit: 20, HorizonDays: 730})
// Fixed reference date so the test is deterministic regardless of the wall clock:
// VBR-OBL-001 (deadline 2026-06-19) must fall within [ref, ref+730d].
ref := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
items := GetRegulatoryNewsAt(regs, RegulatoryNewsFilter{Limit: 20, HorizonDays: 730}, ref)
// Should find at least the Widerrufsbutton obligation
found := false
for _, item := range items {
@@ -231,6 +231,17 @@ _USE_CASES: tuple[UseCase, ...] = (
UseCase("bafin_it", "BaFin IT-Aufsicht (VAIT/BAIT)", "security",
regulations=("VAIT", "BAIT"),
verification_methods=("it_process", "document", "network")),
UseCase("eidas", "eIDAS / Vertrauensdienste (VO 910/2014)", "product",
regulations=("eIDAS",), verification_methods=("document", "it_process"),
categories=("compliance", "security"),
keyword_tokens=("eidas", "vertrauensdienst", "signatur", "siegel",
"zeitstempel", "zertifikat")),
UseCase("geschaeftsgeheimnis", "Geschäftsgeheimnisse (GeschGehG)", "cross_cutting",
regulations=("GeschGehG",),
verification_methods=("document", "it_process", "manual"),
categories=("compliance", "security"),
keyword_tokens=("geschäftsgeheimnis", "vertraulichkeit", "geheimhaltung",
"betriebsgeheimnis")),
)
@@ -341,6 +352,11 @@ _REGULATION_RULES: tuple[tuple[str, str], ...] = (
("bait", "bafin_it"),
("gobd", "steuerrecht"),
("dienstleistungs-informationspflichten", "impressum"),
# eIDAS / Geschäftsgeheimnis (neue Use Cases 2026-06-17)
("eidas", "eidas"),
("910/2014", "eidas"),
("geschäftsgeheim", "geschaeftsgeheimnis"),
("geschgehg", "geschaeftsgeheimnis"),
# Datenschutz-Catch-alls (zuletzt)
("nist privacy framework", "dse"),
("dsgvo", "dse"),
@@ -182,12 +182,18 @@ def _filter_controls(
for c in controls:
cid = c.get("control_id") or ""
prefix = cid.split("-")[0].upper() if "-" in cid else ""
on_topic = criteria_on_topic(c.get("pass_criteria"),
c.get("fail_criteria"))
required = SECTOR_PREFIXES.get(prefix)
if required and not (scope_lc & required):
# Sektor-Gate nur fuer NICHT-on-topic Controls: ein klar
# impressum-thematischer Control (z.B. MStV §18(1) mit GOV-Prefix
# aus der Domain-Erkennung der Control-Generierung) darf nicht am
# Branchen-Prefix scheitern. Der Themen-Ueberlapp ist der staerkere
# Relevanz-Beweis als ein vererbter ID-Prefix.
if required and not (scope_lc & required) and not on_topic:
sector_dropped += 1
continue
if not criteria_on_topic(c.get("pass_criteria"),
c.get("fail_criteria")):
if not on_topic:
offtopic_dropped += 1
continue
kept.append(c)
@@ -0,0 +1,18 @@
-- Migration 154: control_pendants — self-written (license_rule=3) -> sourced
-- (license_rule 1/2) Pendant-Mapping aus dem Phase-2-Reconcile (Embedding-kNN +
-- Haiku-Adjudikation, 2026-06-15). Ein hier gelistetes self-written Atom hat ein
-- kommerziell nutzbares Quell-Control, das DIESELBE Pflicht ausdrueckt -> das
-- Retrieval soll das lizenzierte Quell-Control bevorzugen. Additiv, idempotent.
-- [migration-approved]
SET search_path TO compliance, public;
CREATE TABLE IF NOT EXISTS control_pendants (
control_uuid uuid PRIMARY KEY,
pendant_control_uuid uuid NOT NULL,
cosine numeric,
method varchar(40) NOT NULL DEFAULT 'embed_haiku',
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_control_pendants_pendant
ON control_pendants (pendant_control_uuid);
@@ -0,0 +1,87 @@
"""Tests for the audit-walk ZIP-builder."""
import io
import json
import zipfile
from unittest.mock import patch, MagicMock
from compliance.services.audit_walk_zip_builder import (
_readme,
build_audit_walk_zip,
)
_FAKE_WALK = {
"walk_id": "abc123def456",
"url": "https://example.com/",
"started_at": "2026-06-07T10:00:00+00:00",
"completed_at": "2026-06-07T10:00:30+00:00",
"video": {
"filename": "video.webm",
"size_bytes": 12345,
"sha256": "a" * 64,
"dsms": {"cid": "QmFakeCidVideo"},
},
"walk_json_dsms": {"cid": "QmFakeCidMeta"},
"actions": [
{"action": "navigate", "url": "https://example.com/dse"},
{"action": "navigate", "url": "https://example.com/imprint"},
{"action": "expand_accordions", "expanded": 3},
],
}
class TestReadme:
def test_contains_walk_id_and_url(self):
r = _readme(_FAKE_WALK)
assert "abc123def456" in r
assert "https://example.com/" in r
def test_contains_dsms_cids(self):
r = _readme(_FAKE_WALK)
assert "QmFakeCidVideo" in r
assert "QmFakeCidMeta" in r
def test_counts_navigates_and_accordions(self):
r = _readme(_FAKE_WALK)
assert "2 Compliance-Seiten" in r
assert "3 Akkordeon" in r
class TestBuildZip:
def test_empty_walk_returns_empty(self):
assert build_audit_walk_zip({}) == b""
def test_zip_contains_three_entries(self):
# Mock the video fetch to return tiny content
with patch(
"compliance.services.audit_walk_zip_builder.httpx.Client"
) as mock_client:
instance = mock_client.return_value.__enter__.return_value
instance.get.return_value = MagicMock(
status_code=200, content=b"fakevideo",
)
zip_bytes = build_audit_walk_zip(_FAKE_WALK)
assert zip_bytes
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as z:
names = set(z.namelist())
assert {"video.webm", "walk.json", "README.txt"}.issubset(names)
walk_content = json.loads(z.read("walk.json"))
assert walk_content["walk_id"] == "abc123def456"
assert z.read("video.webm") == b"fakevideo"
def test_video_fetch_failure_still_produces_zip(self):
# consent-tester down → no video, but ZIP still contains
# walk.json + README so the recipient has the metadata.
with patch(
"compliance.services.audit_walk_zip_builder.httpx.Client"
) as mock_client:
instance = mock_client.return_value.__enter__.return_value
instance.get.side_effect = Exception("network down")
zip_bytes = build_audit_walk_zip(_FAKE_WALK)
assert zip_bytes
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as z:
names = z.namelist()
assert "video.webm" not in names
assert "walk.json" in names
assert "README.txt" in names
@@ -159,3 +159,13 @@ def test_all_regulation_rules_point_to_valid_use_cases():
for _needle, uc in reg._REGULATION_RULES:
assert uc in reg.REGISTRY, uc
assert reg.REGISTRY[uc].enabled
def test_new_use_cases_eidas_geschaeftsgeheimnis():
# Korpus-Luecken 2026-06-17: eIDAS (VO 910/2014) + GeschGehG als eigene
# Use Cases ingestiert + klassifiziert.
assert reg.is_valid_use_case("eidas")
assert reg.is_valid_use_case("geschaeftsgeheimnis")
assert reg.use_case_for_regulation("eIDAS-Verordnung (EU) Nr. 910/2014") == "eidas"
assert reg.use_case_for_regulation(
"Gesetz zum Schutz von Geschäftsgeheimnissen") == "geschaeftsgeheimnis"
View File
@@ -0,0 +1,62 @@
# Benchmark-Archiv & RC-Freeze — `v1` (2026-06-19)
> **Zweck:** Reproduzierbarkeits-Record der Doc-Check-Kalibrierung (DSE / Cookie / Impressum).
> Diese Datei enthält **nur Metadaten + Hashes****kein** Drittanbieter-Dokumenttext (Urheber-/Datenbankrecht).
> Die vollständigen Artefakte (Korpora, GTs, Ergebnisse, Skripte) liegen im **internen Audit-Archiv**, getrennt von Repo / RAG / Produkt.
## 1. Daten-Klassen (Retention-Entscheidung 2026-06-19)
Drei Risikoklassen, drei Regeln:
| Klasse | Regel |
|---|---|
| **RAG-Korpus** | Control ableiten → Dokument **verwerfen**. Keine Volltexte als Wissensbasis. |
| **Kundendaten (Prod)** | Speichern: Finding · Evidence · Hash · Version · URL · Zeitpunkt. **Keine** Dauer-Volltextkopie. Datensparsamkeit. |
| **Benchmark/Validierung** | **Versioniert behalten** — sonst sind Messungen nicht reproduzierbar. Intern, off-RAG, off-Produkt. Wie ein Test-/Audit-Archiv, nicht wie eine Wissensbasis. |
Begründung: Das Risiko eines kleinen internen Benchmark-Archivs (öffentlich zugängliche Dokumente) ist geringer als das Risiko, die gesamte Validierung später nicht mehr belegen zu können.
## 2. Release-Candidates (eingefroren)
| RC | doc_type | Opus-GT (Archiv) | Testfirmen | FP / FN | Status |
|---|---|---|---|---|---|
| **DSE_RC_v1** | dse | `gt_opus_dse.json` (5 orig) + `gt_opus_dse_fresh.json` (3 frisch) | 8 (db, otto, ikea, ob, teamviewer + GT-Roster) | FP 11 %→**6 %**, FN ~7 %; frisch FP 7 % / FN 5 % | Release-Candidate |
| **COOKIE_RC_v1** | cookie | `gt_opus_cookie_v2.json` (Mehrfach-Sampling offen) | 7 (db, ikea, lieferando, mediamarkt, ob, tchibo, teamviewer) | Prec 0,81→**0,95**, Rec 0,26→**0,44**, verpasste Lücken→**0 %** | Wave-1 (GT-Rauschen-Vorbehalt) |
| **IMPRESSUM_RC_v1** | impressum | `gt_opus_impressum.json` | 9 (db, ikea, lieferando, mediamarkt, ob, otto, tchibo, teamviewer, zalando) | Text-Check FP **0 %** / FN **2 %** (81 anwendbar, 9 Faktenfeld-Controls) | Release-Candidate |
Detail-Methodik + Fehlerkarte: [`platform_validation_v1.md`](platform_validation_v1.md). Per-Modul-Zahlen: Gedächtnis `project_engine_quality.md`.
## 3. Archiv-Ort + Index
```
macmini:~/bp-benchmark-archive/v1_2026-06-19/
├── MANIFEST.json # 54 Dateien, je SHA256 + Bytes (autoritativ)
├── gt_<firma>_<doctype>.txt # Korpora (Drittanbieter-Volltext — NUR hier)
├── gt_opus_*.json # Opus-Oracle-GTs
├── *_candidates*.json, *_resid.json, *_falsefindings*.json
├── *_criteria_changelog.json / *_criteria_backup.json
└── scripts/ # 46 Mess-Skripte (cc_*.py) = "wie gemessen"
```
**Versionsdefinierende Hashes** (12-stellig gekürzt; voll in `MANIFEST.json`):
| Artefakt | sha256… | Rolle |
|---|---|---|
| `gt_opus_dse.json` | `c5c8975afa42` | DSE-GT (orig) |
| `gt_opus_dse_fresh.json` | `f3940da2e420` | DSE-GT (Anti-Overfit) |
| `gt_opus_cookie_v2.json` | `fcb61dc9b332` | Cookie-GT |
| `gt_opus_impressum.json` | `3e0f2f8d5f5f` | Impressum-GT |
| `dse_criteria_changelog.json` | `d8d461527f5b` | DSE-Kriterien-Diff |
| `cookie_criteria_changelog.json` | `9d29d7b515a5` | Cookie-Kriterien-Diff |
| `impressum_fp_by_cause.json` | `9477f98c0577` | Impressum SCOPE/JUDGE-Split |
## 4. Reproduktion
1. Archiv = Grundwahrheit (Korpus-Hash belegt die damalige Dokumentversion; ändert die Firma ihr Dokument → neuer Hash, alte Messung bleibt über das Archiv belegbar).
2. Mess-Skripte unter `scripts/` gegen die GTs laufen lassen (Pattern: `docker exec -i bp-compliance-backend python3 - < scripts/cc_engine_*.py`).
3. OVH ist stochastisch → Zahlen ±Rauschen; RC-Werte sind Mittel über den dokumentierten Lauf.
## 5. Was NICHT passiert
- Korpus-Volltexte gehen **nicht** ins Repo, **nicht** in Qdrant/RAG, **nicht** ins Produkt.
- Das Archiv ist read-only Referenz; Kalibrierungs-Änderungen sind über die Changelog-Artefakte reversibel.
+130
View File
@@ -0,0 +1,130 @@
# RAG Re-Ingest-Spezifikation — Zitierfähige Chunks (Cross-Session-Vertrag)
> **Status:** v1 — Vertrag zwischen **core-Session** (Ingestion/Pipeline) und **compliance-Session** (Consumer: ai-sdk/Advisor).
> **Datum:** 2026-06-19
> **Ziel:** Chunks neu ingestieren mit sauberem Text + vollständigen **Legal-Metadaten**, damit Controls/Findings **zitierfähig** werden ("BDSG § 38 Abs. 1", "Art. 13 Abs. 1 lit. c DSGVO") — **ohne** die teuren Pass 0a/0b/Dedup.
## 0. Kernentscheidung (Frage 1): Re-Link statt Regenerieren
Controls hängen **an `control_uuid`, nicht an Chunks** (`canonical_controls` hat keine `chunk_id`/`chunk_hash`-Spalte; ebenso atom_classification, control_classification, doc_check_controls). **Ein Chunk-Re-Ingest bricht keine Control.** Das einzige stale-werdende Artefakt ist das Idempotenz-Ledger `compliance.canonical_processed_chunks` (Key `sha256(text)+collection+document_version`) — relevant nur für *zukünftige* Generierung.
**Pass 0a/0b/Dedup entfallen.** Stattdessen werden Controls per **Textabgleich** an die neuen Chunks **re-gelinkt** (Zitat-Anreicherung). Reichweite:
- **7 %** der 315.914 Controls haben `source_original_text` (Re-Link-Anker) → direkter Abgleich.
- **~93 % Atome** erben das Zitat über `parent_control_uuid`.
- **self-written** brauchen kein Chunk-Zitat (eigene Bibliothek).
- **unmatched Reste** → billiges per-Control-LLM-Zitat (Tier-3), keine Regenerierung.
## 1. Ingestion-Anforderungen (Frage 2)
1. **`chunk_strategy="legal"` EXPLIZIT setzen.** (Korrektur zur Historie: `recursive` aliased inzwischen auf `chunk_text_legal`, `embedding-service/main.py:1079/1093`, live-verifiziert — Upload mit `recursive` lieferte `section:"§ 38"`, `paragraph:"(1)"`, `paragraph_num:1`. Trotzdem `legal` explizit, nicht aufs Alias verlassen.)
2. **Deterministische Chunk-ID** (heute random) = `sha1(regulation_code|article|paragraph|chunk_index|document_version)` → stabiler Re-Link + Alt/Neu-Koexistenz.
3. **`chunk_hash` IN die Payload** schreiben (heute nur im PG-Ledger) = `sha256(normalisierter chunk_text)`.
4. **Echte `document_version`** (heute hardcoded `"1.0"`) → Re-Chunk kollidiert sonst im Ledger.
5. **Upload-before-delete** je Collection (alte Chunks erst nach Verify löschen).
## 2. PAYLOAD-FELD-VERTRAG (verbindlich — Consumer liest GENAU diese Namen)
Die ai-sdk/Advisor liest die **consumer-facing** Felder. Ingestion füllt alle.
| Feld | Typ | Consumer | Beschreibung / Beispiel |
|---|---|---|---|
| `article_label` | string | **JA (Anzeige)** | **Fertig formatiert, direkt druckbar.** "BDSG § 38 Abs. 1" · "Art. 13 Abs. 1 lit. c DSGVO". Ingestion formatiert (kennt §- vs Art.-Stil). |
| `regulation_code` | string | **JA** | Kurzcode, UPPERCASE: `BDSG`,`DSGVO`,`TTDSG`,`DDG`,`CRA`,`NIS2` |
| `citation_style` | enum | **JA** | `paragraph` (§-Gesetze) \| `article` (EU-Verordnungen) — steuert §/Art.-Rendering, falls Consumer selbst formatiert |
| `article` | string | **JA** | bare Nummer: `"38"` bzw. `"13"` (egal ob § oder Art.) |
| `paragraph` | string | **JA** | bare Absatz: `"1"` (nicht `"(1)"`) |
| `sub` | string\|null | **JA** | feinste Granularität: `"lit. c"` · `"Satz 2"` · `"Nr. 3"` |
| `is_recital` | bool | **JA** | Erwägungsgrund vs operativer Artikel |
| `regulation_name` | string | optional | Volltext: "Bundesdatenschutzgesetz" |
| `page` | int\|null | optional | Seite (PDF-Quellen) |
| `chunk_text` | string | **JA** | sauberer Text (keine Soft-Hyphens/OCR-Reste) |
| `section_header` | string | optional | Kontext-Überschrift, **separat** (nicht inline im chunk_text) |
| `chunk_id` | string | — | deterministisch (s. §1.2) |
| `chunk_hash` | string | — | sha256(normalisierter Text) |
| `document_id`, `document_version`, `chunk_index` | — | — | Identität/Versionierung |
| `doc_type`, `use_case[]`, `source_type`, `license`, `bundesland`, `year` | — | Scope | Routing/Scope (source_type: gesetz/leitlinie/urteil) |
**Verbindlich:** `article_label` ist der bevorzugte Anzeige-Pfad (Ingestion ownt die Zitat-Formatierung, weil sie die Regulierung kennt). Die strukturierten Teile (`regulation_code`/`article`/`paragraph`/`sub`) sind zusätzlich für Filtern/Gruppieren da.
## 3. Normalisierung alt → neu (core-seitiger Transform)
Der legal-Chunker emittiert heute uneinheitlich; Ingestion normalisiert:
| heute (raw payload) | → neu |
|---|---|
| `section` = `"§ 38"` / `"Artikel 13"` | `citation_style` (§→`paragraph`, Art/Artikel→`article`) + `article`=`"38"`/`"13"` |
| `section_title` | `section_header` |
| `paragraph`=`"(1)"` / `paragraph_num`=1 | `paragraph`=`"1"` |
| (lit./Satz/Nr. aus `_PARAGRAPH_RE`) | `sub` (best-effort) |
| `regulation_id`/`regulation_short` (extra_metadata) | `regulation_code` (UPPERCASE) |
| — | `article_label` (formatiert aus regulation_code+style+article+paragraph+sub) |
`article_label`-Formatierung:
- `paragraph`-Stil: `"{regulation_code} § {article} Abs. {paragraph}"` (+ `" {sub}"`)
- `article`-Stil: `"Art. {article} Abs. {paragraph} {sub} {regulation_code}"`
## 4. Control-Re-Link (core)
Erweiterung von `control-pipeline/services/citation_backfill.py` (heute 3-Tier, Tier-1 = `sha256(source_original_text)` → Chunk-Hash-Index): Tier-1 bricht beim Re-Chunk (neuer Text → neuer Hash) → **Fuzzy/Embedding-Alignment** ergänzen (`source_original_text` ↔ neue Chunk-Texte, Substring + Cosine). Präzedenz: PDF-QA-Matcher (~52 % Trefferquote). Füllt `canonical_controls.source_citation = {regulation_code, article, paragraph, sub, page, source_type, license, url}`. Atome erben über `parent_control_uuid`. `doc_check_controls` re-derivieren danach automatisch zitierfähig (`derive_doc_check_controls.py` liest `source_citation->>'article'/'source'`).
**Beim künftigen Generieren IMMER `source_original_text` setzen** (warum heute nur 7 % re-linkbar sind).
## 5. Pipeline-Reihenfolge
1. Re-Ingest je Collection: `strategy="legal"`, deterministische IDs, neue `document_version`, normalisierte Payload + `chunk_hash`.
2. Verify: Zähler alt/neu + Stichprobe `article`/`paragraph`/`article_label` befüllt.
3. Re-Link (`citation_backfill` erweitert) → `source_citation`; Atome erben.
4. Reste → Tier-3-LLM-Zitat.
5. Alte Chunks löschen.
6. *(optional, nur für künftige Generierung)* Ledger `canonical_processed_chunks` neu aufbauen.
## 6. Arbeitsteilung + Akzeptanz
**core-Session:** Ingest-Spec umsetzen, `citation_backfill` Hash→Fuzzy→Embedding, Payload-Normalisierung (§3), deterministische IDs/`chunk_hash`/`document_version`, AGG-Lücke.
**compliance-Session (Consumer):** ai-sdk `legal_rag_client.go` + Advisor/Drafting auf die **§2-Feldnamen** ummappen (kein Deploy vor Pin — jetzt gepinnt), Prod-Qdrant-Verify, **6-Fragen-Re-Test** (sind §38 BDSG / AGG / CRA Art. 14 grounded zitiert?).
**Akzeptanzkriterium:** Advisor rendert für eine Beispielfrage eine echte Fundstelle aus `article_label` (z. B. "BDSG § 38 Abs. 1"), nicht nur "Quelle: BDSG".
## 7. Consumer-Detailabschnitt (compliance-Session — implementiert 2026-06-19)
Status: Code steht + getestet (17 Tests grün, tsc sauber). **Deploy gekoppelt an den Re-Ingest** (liest bis dahin leere Felder → graceful, Advisor zeigt wie heute "Quelle: BDSG").
### 7.1 ai-sdk: Payload → Response (`internal/ucca/legal_rag_client.go` `searchInternal`, Struct `legal_rag_types.go`)
| Response-Feld (JSON) | gelesen aus Payload | Fallback (alt-Korpus) |
|---|---|---|
| `article_label` | `article_label` | — (leer → Consumer baut selbst) |
| `regulation_code` | `regulation_code` | → `regulation_id` |
| `article` | `article` | → `section` ("§ 38") |
| `paragraph` | `paragraph` | — |
| `sub` | `sub` | — |
| `citation_style` | `citation_style` | — |
| `is_recital` | `is_recital` (bool) | — |
| `text` | `chunk_text` | — |
| `regulation_name` | `regulation_name_de` | — |
| `regulation_short` | `regulation_short` | — |
| `category`,`pages`,`source_url`,`score` | `category`,`pages`,`source`,(score) | — |
`/sdk/v1/rag/search` liefert diese Felder snake_case. Neuer Bool-Helfer `getBool` ergänzt.
### 7.2 Advisor + Drafting: Fundstellen-Format
Beide Konsumenten (`admin-compliance/lib/sdk/agents/advisor-rag.ts`, `.../drafting-engine/rag-query.ts`) bilden die Quellenzeile so:
```
source = article_label?.trim() // bevorzugt: druckbar aus Ingestion
|| [regulation_short|regulation_name|regulation_code, article, paragraph, sub]
.filter(Boolean).join(' ') // Fallback: strukturiert zusammensetzen
|| 'Unbekannt'
Ausgabe je Treffer: [Quelle N: {source}]\n{text}
```
→ Der Advisor **druckt `article_label` direkt** (kein §-vs-Art-Ableiten); `citation_style` nur nötig, falls wir später selbst formatieren. Erfüllt das Akzeptanzkriterium (§6): "BDSG § 38 Abs. 1" statt nur "Quelle: BDSG".
### 7.3 Deploy-Kopplung
Code ist additiv/safe (neue Felder leer bis Re-Ingest). **Kein Solo-Deploy** — geht mit dem Re-Ingest-Go-Live live, danach **6-Fragen-Re-Test** auf prod (§38/AGG/CRA Art. 14 grounded zitiert?).
## 8. Offen (core)
- **AGG-Lücke**: Quelle (§ 15 Abs. 4 — Bewerberdaten-Frist) zu spezifizieren + ingestieren.
- Soft-Hyphen/OCR-Normalisierung des `chunk_text` — Regelsatz definieren.
@@ -0,0 +1,147 @@
# Ground Truth: Conrady Gruppe
**URL:** https://conradygruppe.com
**Typ:** Mittelstand / B2B Industrie (CookieYes-CMP)
**Datum:** 2026-05-20
**Tester:** Benjamin (manuell verifiziert)
---
## Business Profile (erwartet)
| Feld | Erwarteter Wert |
|------|----------------|
| business_type | b2b |
| industry | manufacturing / services |
| has_online_shop | false |
| no_direct_sales | true |
---
## Dokumente
| Dokumenttyp | Vorhanden | URL / Hinweis |
|-------------|-----------|---------------|
| DSE | Ja | https://conradygruppe.com/datenschutz |
| Impressum | Ja | https://conradygruppe.com/impressum |
| **Cookie-Richtlinie** | **NEIN** | nicht erreichbar — separate Seite fehlt |
| AGB | (offen) | B2B, vermutlich individuell |
| Widerruf | In DSE | — |
| DSB-Kontakt | In DSE | E-Mail-Adresse benannt |
| Social Media DSE | (zu pruefen) | siehe SoMe-Findings |
---
## Erwartete Findings — Impressum (Art. 5 TMG)
| Check | Erwartet | Begruendung |
|-------|----------|-------------|
| Handelsregister/Registernummer | **FEHLT** | §5(1) Nr.4 TMG — Pflichtangabe |
| **USt-IdNr.** | **FEHLT** | §5(1) Nr.6 TMG — wenn vorhanden Pflicht. Steuernummer ist KEIN Ersatz |
| Verantwortlicher | OK | Name + Anschrift vorhanden |
| Vertretungsberechtigter | (zu pruefen) | bei juristischer Person Pflicht |
| Kontakt (E-Mail/Tel) | (zu pruefen) | §5(1) Nr.2 TMG |
---
## Cookie-Banner (CookieYes)
**3 Buttons im Banner:**
1. **"Speichern"** — funktional ein Reject mit aktuellen Einstellungen
2. **"Alle akzeptieren"**
3. **"Nur essenzielle Cookies akzeptieren"** — funktional der Ablehnen-Button
| Check | Erwartet | Anmerkung |
|-------|----------|-----------|
| Banner detected (CookieYes) | OK | Bot erkennt es als CookieYes |
| Ablehnen-Mechanismus vorhanden | OK (implicit via "Nur essenzielle") | aber: |
| Explizites "Ablehnen"-Label | **FEHLT** | "Nur essenzielle" + "Speichern" — kein "Ablehnen" |
| **"Speichern" als Button-Text fragwürdig** | **MEDIUM-Finding** | Speichert was? Mehrdeutig fuer Nutzer |
| Cookie-Richtlinie aus Banner verlinkt | **NEIN** | separate Seite fehlt |
| Re-Access (Floating-Icon o.ae.) | (zu pruefen) | CookieYes default hat Floating-Icon |
**Sprachlich-Fragwürdig:** Der primäre Action-Button heißt "Speichern" — semantisch unklar (speichert "essenziell"? speichert "alle"?). DSK-OH 2024 fordert eindeutige Beschriftung der Consent-Optionen.
---
## Cookie-Richtlinie (Sonderfall)
**Status:** **NICHT VORHANDEN** als separates Dokument.
→ HIGH-Finding: "Cookie-Richtlinie nicht auffindbar".
→ Cookie-Auflistung muss laut DSK-OH Telemedien 2024 pro Cookie enthalten: Name, Anbieter, Zweck, Speicherdauer, Drittlandtransfer.
---
## Datenschutzerklärung
**Widerruf:** In DSE benannt (Standard-Text "Widerruf jederzeit per E-Mail an…").
**DSB:** In DSE als E-Mail-Adresse benannt (kein separater Name/Bestellung).
| Check | Erwartet | Anmerkung |
|-------|----------|-----------|
| Verantwortlicher | OK | — |
| DSB | OK (E-Mail) | Name optional, E-Mail ausreichend nach Art. 13(1)(b) |
| Zwecke | (zu pruefen) | — |
| Rechtsgrundlage | (zu pruefen) | — |
| Empfaengerkategorien | **zu pruefen** | wer ist als Empfaenger genannt? Vergleich mit Cookie-Banner-Vendor-Liste |
| Speicherdauer | (zu pruefen) | — |
| Betroffenenrechte | (zu pruefen) | Art. 15-21 vollstaendig? |
---
## Social Media Verlinkung (zu pruefen)
| Plattform | Verlinkt? | DSGVO-konform (Shariff/2-Klick)? |
|-----------|-----------|-----------------------------------|
| LinkedIn | (zu pruefen) | — |
| Facebook | (zu pruefen) | — |
| Instagram | (zu pruefen) | — |
| X (Twitter) | (zu pruefen) | — |
| YouTube | (zu pruefen) | — |
**Pruefungspflicht:** SoMe-Buttons müssen DSGVO-konform eingebunden sein. Direkte iframes/Pixel zu Facebook etc. setzen vor Consent Tracking → Verstoss §25 TDDDG.
Lösungen: Shariff/2-Klick/Self-hosted-Icons.
---
## Konsistenz-Check (NEUE Pruefung — P33)
**3-Spalten-Vergleich:**
| Verarbeiter | in DSE | in Cookie-Richtlinie | im Cookie-Banner |
|-------------|--------|---------------------|------------------|
| TeleData GmbH | (zu pruefen) | n/a (Cookie-Richtlinie fehlt) | (zu pruefen) |
| CookieYes | (zu pruefen) | n/a | Ja (CMP-Anbieter) |
| ... | ... | ... | ... |
**Erwartung:** Inkonsistenzen wahrscheinlich, da Cookie-Richtlinie fehlt und DSE+Banner getrennt gepflegt werden.
---
## Score-Erwartung
| Bereich | Erwartet | Kommentar |
|---------|----------|-----------|
| DSE | ~80% | Standardpflichten OK, Drittland-Empfänger ggf. ungenau |
| Impressum | ~60% | Handelsregister + USt-IdNr. fehlen |
| Cookie-Richtlinie | 0% | nicht vorhanden |
| Banner-Quality | ~80% | implicit Reject OK, aber "Speichern"-Label problematisch + Cookie-Richtlinie nicht verlinkt |
| **Gesamt** | **~70-75%** | mittelmäßig, Mittelstand-Standard |
---
## Anlässe für neue Checks
1. **P33: 3-Spalten-Konsistenz-Check** (DSE vs Cookie-Richtlinie vs Banner-Vendors)
2. **P28b-Erweiterung: "Speichern" als problematisches Reject-Label** flaggen (mehrdeutig)
3. **P36: Social-Media-Einbindungs-Check** (Shariff/2-Klick/direkt?)
---
## Offene Fragen an User
- USt-IdNr. — hat Conrady Gruppe als juristische Person eine? (vermutlich ja als Gruppe mit Auslandsumsatz)
- Cookie-Richtlinie tatsächlich gar nicht vorhanden, oder nur schwer zu finden (z.B. tief im Datenschutz-Dokument)?
@@ -0,0 +1,489 @@
{
"site": "BMW AG",
"doc_type": "impressum",
"note": "GT=Haiku-Verdikt; v3=unsere deterministische Engine (Layer-0 Regex-Boost)",
"created": "2026-06-17T18:55:16.265861+00:00",
"haiku_pass": 24,
"v3_pass": 14,
"gate_dropped": 39,
"agreement_evaluated_pct": 92,
"controls": [
{
"control_id": "SEC-9172-A08",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Aktualisierte Informationen zu Online-Schnittstellen unverzüglich zugänglich machen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "TRD-658-A01",
"regulation": "DDG",
"article": "§ 33 Absatz 3",
"title": "Unternehmeridentität auf Plattform erkennbar machen",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A03",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Gesetzliche Berufsbezeichnung offenlegen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A02",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Anschrift des Anbieters leicht erkennbar verfügbarmachen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "SEC-9175-A04",
"regulation": "DDG",
"article": "§ 6 Absatz 1 Nr. 1-2",
"title": "Kommerzielle Kommunikationen deutlich kennzeichnen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "AUTH-4120-A02",
"regulation": "DDG",
"article": "§ 33 Digitale-Dienste-Gesetz Bezug zu Artikel 11 Absatz 4",
"title": "Nutzerinformationen regelmäßig überprüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9158-A09",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Zuständigkeitsbereiche mehrerer Verantwortlicher dokumentieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9158-A10",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Identität und Aufenthalt der Verantwortlichen überprüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A02",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Informationen zu kommerziellen Angeboten leicht zugänglich bereitstellen",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A04",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Staat oder Region der Berufsbezeichnungs-Verleihung angeben",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "SEC-9174-A05",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Bezeichnung geltender berufsrechtlicher Regelungen bereitstellen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "SEC-9170-A04",
"regulation": "DDG",
"article": "§ 33 Absatz 1",
"title": "Kommerzieller Charakter in Nachrichten erkennbar machen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9175-A01",
"regulation": "DDG",
"article": "§ 6 Absatz 1 Nr. 1-2",
"title": "Auftraggeber kommerzieller Kommunikation eindeutig identifizierbar machen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A01",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Aktualität und Korrektheit von Anbieter-Kontaktdaten verifizieren",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "COMP-4123-A01",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Verantwortlichen für journalistisch-redaktionelle Angebote benennen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A02",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Vollständigen Namen des Verantwortlichen aufführen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9173-A03",
"regulation": "DDG",
"article": "§ 33 Digitale-Dienste-Gesetz Bezug zu Artikel 12 Absatz 6, Artikel 13 Absatz 4, Artikel 14 Absatz 3",
"title": "Angaben gemäß Artikel 13 Abs. 4 aktuell halten",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "GOV-3881-A07",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Impressum oder Kontaktseite auf Webseite oder App lokalisieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A04",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Verantwortliche zusätzlich zu DGA-Angaben benennen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A06",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Kommerzielle Angebote eindeutig als solche kennzeichnen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "GOV-3887-A03",
"regulation": "DDG",
"article": "§ 20 Absatz 1",
"title": "Fortbestand der Ansprechpartnerfunktion bei Fremdbearbeitung mitteilen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9170-A01",
"regulation": "DDG",
"article": "§ 33 Absatz 1",
"title": "Absender kommerzieller Nachrichten eindeutig und unverschleiert offenlegen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "LOG-2095-A03",
"regulation": "DDG",
"article": "§ 9 Abs. 1",
"title": "Identifikationskriterien nach § 2 DDG in Anbieterlisten dokumentieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9170-A05",
"regulation": "DDG",
"article": "§ 33 Absatz 1",
"title": "Versteckte Kommunikation von Absender oder kommerziellem Zweck verhindern",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9170-A08",
"regulation": "DDG",
"article": "§ 33 Absatz 1",
"title": "Erkennbarkeit des kommerziellen Charakters auf den ersten Blick prüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A03",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Anschrift des Verantwortlichen angeben",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9175-A02",
"regulation": "DDG",
"article": "§ 6 Absatz 1 Nr. 1-2",
"title": "Kennzeichnung kommerzieller Kommunikation gut erkennbar anbringen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9175-A03",
"regulation": "DDG",
"article": "§ 6 Absatz 1 Nr. 1-2",
"title": "Kennzeichnungsanforderungen auf alle kommerziellen Kommunikationen anwenden",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "GOV-3881-A03",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Name und Anschrift des Vertretungsberechtigten verfügbarmachen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A04",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Anbieter-Kontaktdaten an prominenter Stelle positionieren",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A05",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Anbieter-Kontaktdaten auf allen relevanten Plattformen und Kanälen verfügbar machen",
"haiku_erfuellt": false,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A06",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Name des Anbieters leicht erkennbar und ständig verfügbar machen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A08",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Deutliche Erkennbarkeit von Name und Anschrift des Anbieters verifizieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "GOV-3881-A09",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Name und Anschrift des Vertretungsberechtigten bei juristischen Personen überprüfen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "GOV-3881-A10",
"regulation": "MStV",
"article": "§ 18 Absatz 1",
"title": "Auffindbarkeit der Kontaktdaten in maximal 2 Klicks testen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9172-A02",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Vorhandensein aller vorgeschriebenen Informationselemente kontrollieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9172-A03",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Aktualität der Schnittstelleninformationen durch Vergleich mit technischen Dokumentationen überprüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9172-A04",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Zugangsbarkeit und Verständlichkeit der Schnittstelleninformationen prüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9172-A05",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Zeitliche Verzögerung zwischen Änderungen und Informationsaktualisierung kontrollieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A05",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Verantwortlicher im Impressum oder auf Kontaktseite benennen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A06",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Name und Anschrift des benannten Verantwortlichen verifizieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A07",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Zusätzliche Angaben neben DGG-Informationen des Verantwortlichen überprüfen",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4123-A08",
"regulation": "MStV",
"article": "§ 18 Absatz 2",
"title": "Auffindbarkeit und Aktualität der Verantwortlichen-Informationen verifizieren",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A07",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Zugangsweg zu berufsrechtlichen Regelungen angeben",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "SEC-9174-A08",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Informationen leicht erkennbar gestalten",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A09",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Informationen unmittelbar erreichbar bereitstellen",
"haiku_erfuellt": true,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9174-A10",
"regulation": "DDG",
"article": "§ 5 Absatz 1, Nr. 2 (b, c)",
"title": "Informationen ständig verfügbar halten",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "AUTH-4120-A03",
"regulation": "DDG",
"article": "§ 33 Digitale-Dienste-Gesetz Bezug zu Artikel 11 Absatz 4",
"title": "Überprüfungen von Nutzerinformationen dokumentieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "AUTH-4120-A04",
"regulation": "DDG",
"article": "§ 33 Digitale-Dienste-Gesetz Bezug zu Artikel 11 Absatz 4",
"title": "Veraltete oder fehlerhafte Nutzerinformationen zeitnah aktualisieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "AUTH-4120-A05",
"regulation": "DDG",
"article": "§ 33 Digitale-Dienste-Gesetz Bezug zu Artikel 11 Absatz 4",
"title": "Aktualität von Nutzerinformationen nachweisbar gewährleisten",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "COMP-4124-A04",
"regulation": "DDG",
"article": "§ 25 Abs. 1",
"title": "Informationen vollständig, wahrheitsgemäß und verständlich bereitstellen",
"haiku_erfuellt": true,
"v3_passed": true,
"v3_status": "evaluated"
},
{
"control_id": "SEC-9172-A06",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Informationen zu Online-Schnittstellen-Konzeption vollständig dokumentieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
},
{
"control_id": "SEC-9172-A07",
"regulation": "DDG",
"article": "§ 33 Absatz mit Verweis auf Artikel 32 Absatz 2",
"title": "Informationen zu Online-Schnittstellen in vorgeschriebener Weise präsentieren",
"haiku_erfuellt": false,
"v3_passed": null,
"v3_status": "gate_dropped"
}
]
}