feat(platform): live-wire AGB v2 + DSE v3 + Architektur-Tab (#29)
CI / detect-changes (push) Successful in 7s
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 9s
CI / validate-canonical-controls (push) Successful in 12s
CI / loc-budget (push) Successful in 24s
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 3m11s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 24s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 7s
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 9s
CI / validate-canonical-controls (push) Successful in 12s
CI / loc-budget (push) Successful in 24s
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 3m11s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Successful in 24s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
AGB v2 (decision_method routing, 71%FP->~0) + DSE v3 (4-layer, recovered from container) + Architektur-Tab into /sdk/agent live path. Incl CI robustness (detect-changes.sh + PR-head checkout) + security (hardcoded Qdrant key removed, gitleaks allowlist). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit was merged in pull request #29.
This commit is contained in:
@@ -0,0 +1,302 @@
|
||||
'use client'
|
||||
|
||||
// Erklärendes Architekturschema des Compliance-Check-Tools — Muster aus dem
|
||||
// CE-Modul (/sdk/iace/.../architektur) übernommen: hand-kurierte Boxen/Pfeile +
|
||||
// Schritt-Akkordeon. Inhalt spiegelt den Code-Pfad (api/agent_check/_orchestrator
|
||||
// + services/specialist_agents). Bewusst statisch (der Doc-Check ist Python, hat
|
||||
// keinen Architektur-Endpoint wie das Go-IACE-Modul) — bei Bedarf später aus einem
|
||||
// Backend-Handler speisbar.
|
||||
|
||||
import { useState, type ReactNode } from 'react'
|
||||
|
||||
function Box({ title, sub, accent }: { title: string; sub?: string; accent?: 'purple' | 'amber' | 'green' | 'gray' }) {
|
||||
const c =
|
||||
accent === 'purple'
|
||||
? 'border-purple-300 bg-purple-50/60 dark:border-purple-700 dark:bg-purple-900/20'
|
||||
: accent === 'amber'
|
||||
? 'border-amber-300 bg-amber-50/60 dark:border-amber-700 dark:bg-amber-900/20'
|
||||
: accent === 'green'
|
||||
? 'border-green-300 bg-green-50/60 dark:border-green-700 dark:bg-green-900/20'
|
||||
: 'border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800'
|
||||
return (
|
||||
<div className={`rounded-lg border ${c} px-2.5 py-1.5`}>
|
||||
<div className="text-[11px] font-medium text-gray-800 dark:text-gray-200 leading-tight">{title}</div>
|
||||
{sub && <div className="text-[10px] text-gray-500 leading-tight mt-0.5">{sub}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Lane({ label, children }: { label: string; children: ReactNode }) {
|
||||
return (
|
||||
<div className="flex-1 min-w-[150px] space-y-2">
|
||||
<div className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 text-center">{label}</div>
|
||||
<div className="space-y-1.5">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Arrow() {
|
||||
return (
|
||||
<div className="flex items-center justify-center text-gray-300 dark:text-gray-600 shrink-0 px-0.5">
|
||||
<span className="hidden lg:block text-lg">→</span>
|
||||
<span className="lg:hidden text-sm">↓</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type Stage = {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
input: string
|
||||
logic: string
|
||||
source: string
|
||||
example: string
|
||||
}
|
||||
|
||||
// Spiegelt run_compliance_check (Phasen A–F) + die Spezialagenten-Schicht.
|
||||
const STAGES: Stage[] = [
|
||||
{
|
||||
id: 'a',
|
||||
title: 'Phase A — Auflösen & Crawl',
|
||||
summary: 'URLs + hochgeladene Dokumente einsammeln, fehlende Pflichtseiten automatisch finden.',
|
||||
input: 'Start-URL, Dokument-Uploads, 8 Wizard-Felder (scan_context)',
|
||||
logic: 'Discovery (Sitemap/Heuristik) + Fetch je Seite, Text-Extraktion pro Doc-Typ',
|
||||
source: 'consent-tester /dsi-discovery, Playwright',
|
||||
example: 'Findet /impressum, /datenschutz, /agb ohne manuelle Eingabe',
|
||||
},
|
||||
{
|
||||
id: 'b',
|
||||
title: 'Phase B — Profil & Dokument-Checks',
|
||||
summary: 'Geschäftsprofil erkennen, jedes Dokument gegen seine Controls prüfen.',
|
||||
input: 'Doc-Texte je Typ + Business-Scope',
|
||||
logic: 'Regex-Runner + MC-Keyword + BGE-M3-Embedding + LLM-Verify (nur unscharf)',
|
||||
source: 'doc_check_controls (DB), mc_classification.db (Embeddings)',
|
||||
example: 'DSE: 267 Text-MCs, Keyword + semantischer Recall',
|
||||
},
|
||||
{
|
||||
id: 'agents',
|
||||
title: 'Spezialagenten (nebenläufig)',
|
||||
summary: 'Pro Dokumenttyp ein typisierter Agent → eigener Ergebnis-Tab, gefüllt per SSE.',
|
||||
input: 'Doc-Text, Scope, scan_context',
|
||||
logic: 'Impressum + AGB + DSE laufen parallel (asyncio.gather), je ein AgentOutput',
|
||||
source: 'api/agent_check/_agent_outputs._TOPIC_AGENTS',
|
||||
example: 'AGB-Tab + DSE-Tab erscheinen, sobald ihr Agent fertig ist',
|
||||
},
|
||||
{
|
||||
id: 'c',
|
||||
title: 'Phase C — Cookie-Banner',
|
||||
summary: 'Consent-Banner + gesetzte Cookies vor/nach Einwilligung live prüfen.',
|
||||
input: 'Live-Seite im Browser',
|
||||
logic: 'Consent-Tester-Scan: Banner, Vendors, Enforcement, Browser-Matrix',
|
||||
source: 'consent-tester /scan',
|
||||
example: 'Cookie vor Einwilligung gesetzt → Verstoß-Kandidat',
|
||||
},
|
||||
{
|
||||
id: 'd',
|
||||
title: 'Phase D — Vendors & Plausibilität',
|
||||
summary: 'Dritt-Dienste extrahieren + Findings auf Plausibilität prüfen.',
|
||||
input: 'Banner-/Seiten-Daten, Findings',
|
||||
logic: 'Vendor-Extraktion (+OCR-Fallback), Plausibilitäts-Check je FAIL',
|
||||
source: 'Cookie-/Vendor-Kataloge, LLM-Kaskade',
|
||||
example: 'Analytics ohne Rechtsgrundlage → bestätigtes Finding',
|
||||
},
|
||||
{
|
||||
id: 'reconcile',
|
||||
title: 'Cross-Finding-Abgleich',
|
||||
summary: 'Findings über Dokumente hinweg abgleichen — Doppel & Scheinverstöße auflösen.',
|
||||
input: 'Alle Modul-Findings',
|
||||
logic: 'Deckt ein anderes Dokument die Pflicht ab, wird das Cross-Finding unterdrückt',
|
||||
source: 'cross_doc_reconcile (B-Wirings)',
|
||||
example: '§36 VSBG im Impressum statt DSE → kein Doppel-Finding',
|
||||
},
|
||||
{
|
||||
id: 'f',
|
||||
title: 'Phase E/F — Bericht & Snapshot',
|
||||
summary: 'Ergebnis persistieren, Snapshot für die Historie speichern.',
|
||||
input: 'Konsolidiertes Ergebnis',
|
||||
logic: 'Mail-Render + DB-Persist + Snapshot (Tab-Ansicht ohne Re-Crawl)',
|
||||
source: 'compliance_check_snapshots',
|
||||
example: 'Historie erneut öffnen, ohne die Seite neu zu crawlen',
|
||||
},
|
||||
]
|
||||
|
||||
type ModuleEngine = { name: string; mechanism: string }
|
||||
const MODULES: ModuleEngine[] = [
|
||||
{ name: 'Impressum', mechanism: 'Scope-Gate + Feld-Matcher (§5 DDG / §18 MStV)' },
|
||||
{ name: 'AGB', mechanism: 'decision_method-Routing: Keyword → Geschäftsmodell-Gate → Embedding/Reference/LLM' },
|
||||
{ name: 'DSE', mechanism: '4-Layer: Regex-Boost → Keyword → BGE-M3-Recall (0.65) → Semantic-Validator' },
|
||||
{ name: 'Cookie-Banner', mechanism: 'Consent-Tester: Banner, Vendors, Enforcement, Browser-Matrix' },
|
||||
]
|
||||
|
||||
type Pruefer = { method: string; mechanism: string; deterministic: string; example: string }
|
||||
// Meta-Modell: jede Pflicht → ein Prüfertyp (decision_method). Wenige
|
||||
// wiederverwendbare Prüfer statt Logik pro Control.
|
||||
const PRUEFER: Pruefer[] = [
|
||||
{ method: 'REGEX', mechanism: 'Kuratierte Muster / Keyword', deterministic: 'ja', example: 'Pflicht-Stichwort im Text' },
|
||||
{ method: 'EMBEDDING', mechanism: 'BGE-M3 Kosinus ≥ Schwelle', deterministic: 'ja (feste Funktion)', example: '„Recht auf Berichtigung" ≈ Umschreibung' },
|
||||
{ method: 'REFERENCE', mechanism: 'Link-/Verweis-Auflösung', deterministic: 'ja', example: 'Verweis auf die Datenschutzerklärung' },
|
||||
{ method: 'LLM', mechanism: 'Kaskade Qwen→OVH→Claude, nur unscharfe Fälle', deterministic: 'nein (eskaliert)', example: 'Speicherdauer inhaltlich erfüllt?' },
|
||||
{ method: 'BEHAVIOR', mechanism: 'Playwright: Live-Verhalten', deterministic: 'ja', example: 'Cookies vor Einwilligung gesetzt?' },
|
||||
{ method: 'SCANNER', mechanism: 'Repo-/Netzwerk-/Prozess-Scan', deterministic: 'ja', example: 'Geplant: technische Nachweise' },
|
||||
]
|
||||
|
||||
function Field({ label, value, mono }: { label: string; value: string; mono?: boolean }) {
|
||||
return (
|
||||
<div>
|
||||
<dt className="text-[10px] uppercase tracking-wide text-gray-400">{label}</dt>
|
||||
<dd className={`text-gray-600 dark:text-gray-300 ${mono ? 'font-mono text-[11px]' : ''}`}>{value}</dd>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function StageRow({ stage, last, open, onToggle }: { stage: Stage; last: boolean; open: boolean; onToggle: () => void }) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className={`w-full text-left rounded-lg border p-3 transition-colors ${
|
||||
open
|
||||
? 'border-purple-300 bg-purple-50/60 dark:border-purple-700 dark:bg-purple-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-gray-800 dark:text-gray-200">{stage.title}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{stage.summary}</div>
|
||||
</div>
|
||||
<span className="text-gray-400 text-xs shrink-0">{open ? '▲' : '▼'}</span>
|
||||
</div>
|
||||
{open && (
|
||||
<dl className="mt-3 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-2 text-xs">
|
||||
<Field label="Input" value={stage.input} />
|
||||
<Field label="Logik" value={stage.logic} />
|
||||
<Field label="Datenquelle" value={stage.source} mono />
|
||||
<Field label="Beispiel" value={stage.example} />
|
||||
</dl>
|
||||
)}
|
||||
</button>
|
||||
{!last && <div className="flex justify-center text-gray-300 dark:text-gray-600 text-xs leading-none py-0.5">↓</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ArchitekturView() {
|
||||
const [open, setOpen] = useState<string | null>('b')
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">Architektur & Datenfluss</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-3xl mt-1">
|
||||
Nachvollziehbar: <strong>woher jedes Finding stammt</strong> und <strong>wie es geprüft wird</strong>.
|
||||
Die Engine ist überwiegend <strong>deterministisch</strong> (Regex + Embedding); ein LLM entscheidet nur
|
||||
die unscharfen Fälle. Ergebnisse erscheinen pro Modul progressiv und werden am Ende per
|
||||
Cross-Finding-Abgleich bereinigt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Datenfluss (Überblick)</h3>
|
||||
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-900/20 p-3 overflow-x-auto">
|
||||
<div className="flex flex-col lg:flex-row gap-1.5 lg:items-stretch min-w-[280px]">
|
||||
<Lane label="Eingabe">
|
||||
<Box title="Website + Dokumente" sub="Impressum · DSE · AGB · Cookies" accent="purple" />
|
||||
<Box title="Wizard-Kontext" sub="8 Felder: Shop, Drittland, Beruf…" accent="purple" />
|
||||
</Lane>
|
||||
<Arrow />
|
||||
<Lane label="Crawl + Text">
|
||||
<Box title="Discovery + Fetch" sub="consent-tester, Playwright" />
|
||||
<Box title="Doc-Text je Typ" />
|
||||
</Lane>
|
||||
<Arrow />
|
||||
<Lane label="Engine (deterministisch)">
|
||||
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-1.5 space-y-1">
|
||||
{STAGES.map((s) => (
|
||||
<div key={s.id} className="text-[10px] text-gray-600 dark:text-gray-300 leading-tight">
|
||||
{s.title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Lane>
|
||||
<Arrow />
|
||||
<Lane label="Ausgaben">
|
||||
<Box title="Findings je Modul-Tab" sub="Impressum/AGB/DSE/Cookie" accent="green" />
|
||||
<Box title="Severity + Maßnahme" accent="green" />
|
||||
<Box title="Snapshot + Bericht" sub="ohne Re-Crawl" accent="green" />
|
||||
</Lane>
|
||||
</div>
|
||||
<p className="text-[10px] text-gray-400 mt-2">
|
||||
Links→rechts reproduzierbar. Embedding ist semantisch UND deterministisch (feste Funktion: gleicher
|
||||
Text → gleicher Vektor). Das LLM läuft nur für unscharfe Fälle und eskaliert mit Selbstkonfidenz.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Pipeline (Schritt für Schritt)</h3>
|
||||
<div className="space-y-1">
|
||||
{STAGES.map((s, i) => (
|
||||
<StageRow
|
||||
key={s.id}
|
||||
stage={s}
|
||||
last={i === STAGES.length - 1}
|
||||
open={open === s.id}
|
||||
onToggle={() => setOpen(open === s.id ? null : s.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Modul-Engines (live)</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
{MODULES.map((m) => (
|
||||
<div key={m.name} className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-3">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">{m.name}</span>
|
||||
<span className="inline-block rounded px-1.5 py-0.5 text-[10px] font-medium bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300">
|
||||
live
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">{m.mechanism}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-3">
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">Prüfer-Matrix (Meta-Modell)</h3>
|
||||
<p className="text-xs text-gray-500 max-w-3xl">
|
||||
Jede Pflicht wird einem <strong>Prüfertyp</strong> zugeordnet — so braucht es nicht pro Control eigene
|
||||
Logik, sondern wenige wiederverwendbare Prüfer.
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="text-gray-500 border-b border-gray-200 dark:border-gray-700 text-left">
|
||||
<th className="py-1.5 pr-3">Prüfer</th>
|
||||
<th className="py-1.5 pr-3">Mechanismus</th>
|
||||
<th className="py-1.5 pr-3">Deterministisch</th>
|
||||
<th className="py-1.5">Beispiel</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{PRUEFER.map((p) => (
|
||||
<tr key={p.method} className="border-b border-gray-100 dark:border-gray-700/50 align-top">
|
||||
<td className="py-1.5 pr-3">
|
||||
<code className="text-[11px] bg-gray-100 dark:bg-gray-700 rounded px-1">{p.method}</code>
|
||||
</td>
|
||||
<td className="py-1.5 pr-3 text-gray-600 dark:text-gray-300">{p.mechanism}</td>
|
||||
<td className="py-1.5 pr-3 text-gray-500">{p.deterministic}</td>
|
||||
<td className="py-1.5 text-gray-500">{p.example}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user