'use client' // ETO / Onboarding-Advisor — thin operator surface over POST /api/compliance/onboarding/advisor-start. // Certifications + target + scanner findings -> Silent Pass -> Advisor. NOT the regulation gap engine // (/sdk/gap-analysis is a different flow: product -> applicable regulations). This tests the cert->delta // case: "TISAX/ISO27001 -> CRA, what is auto-detected, what stays an open question?". No new backend. import React, { useEffect, useState } from 'react' const CERTS = ['ISO27001', 'TISAX', 'ISO9001', 'IEC62443', 'ISO13485', 'ISO14001', 'ASPICE', 'IATF16949'] // label -> {signal_id, source_type} — demonstrates all three signal KINDS (observation / partial / requirement) const FINDINGS: Array<{ label: string; signal_id: string; source_type: string; kind: string }> = [ { label: 'SBOM im Repo (CycloneDX/SPDX)', signal_id: 'cyclonedx_found', source_type: 'repository', kind: 'observation' }, { label: 'security.txt / CVD-Policy veröffentlicht', signal_id: 'security_txt', source_type: 'website', kind: 'observation' }, { label: 'Signierte Releases', signal_id: 'signed_releases', source_type: 'repository', kind: 'observation' }, { label: 'Produkt-Risikobewertung (Dokument)', signal_id: 'risk_assessment_pdf', source_type: 'document', kind: 'observation' }, { label: 'CI-Pipeline vorhanden (nur Indikation)', signal_id: 'github_actions_ci', source_type: 'repository', kind: 'partial' }, { label: 'Cloud-/vernetztes Produkt', signal_id: 'cloud_hosted', source_type: 'product', kind: 'observation' }, { label: 'Ausschreibung FORDERT SBOM (Requirement)', signal_id: 'requires_sbom', source_type: 'tender', kind: 'requirement' }, { label: 'OEM FORDERT PSIRT (Requirement)', signal_id: 'supplier_requires_psirt', source_type: 'oem', kind: 'requirement' }, ] interface Question { capability_id: string; question_intent: string; why: string; information_value: number; priority: string } interface Inferred { certification: string; capabilities: string[]; statement: string } interface Rejected { certification?: string; statement: string; reason: string } interface Measure { capability_id: string; leverage: number; closes: string[] } interface AdvisorResponse { silent_intake_summary: string; headline: string; auto_detected: string[]; indications: string[] inferred_assumptions: Inferred[]; rejected_assumptions: Rejected[]; top_5_questions: Question[] capability_delta: string[]; top_measures: Measure[]; evidence_requests: string[] unsupported_domains: string[]; completeness_summary: string; capability_labels: Record } const PROXY = '/api/sdk/v1/compliance/onboarding' function Chips({ items, tone }: { items: string[]; tone: string }) { if (!items.length) return return (
{items.map(c => {c})}
) } function Section({ title, hint, children }: { title: string; hint?: string; children: React.ReactNode }) { return (

{title}

{hint &&

{hint}

}
{children}
) } export default function OnboardingAdvisorPage() { const [targets, setTargets] = useState([]) const [company, setCompany] = useState('Beispiel Maschinenbau') const [industry, setIndustry] = useState('machine_builder') const [certs, setCerts] = useState(['ISO27001', 'ISO9001']) const [target, setTarget] = useState('CRA') const [findings, setFindings] = useState(['cyclonedx_found', 'github_actions_ci', 'requires_sbom']) const [knownEvidence, setKnownEvidence] = useState('CE-Prozess') const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState('') useEffect(() => { fetch(`${PROXY}/targets`).then(r => r.json()).then(d => { if (Array.isArray(d.targets)) { setTargets(d.targets); if (!d.targets.includes('CRA') && d.targets[0]) setTarget(d.targets[0]) } }).catch(() => {}) }, []) const toggle = (list: string[], set: (v: string[]) => void, v: string) => set(list.includes(v) ? list.filter(x => x !== v) : [...list, v]) const lbl = (id: string) => result?.capability_labels?.[id] || id.replace(/_/g, ' ') const run = async () => { setLoading(true); setError(''); setResult(null) try { const scanner_findings = FINDINGS.filter(f => findings.includes(f.signal_id)) .map(f => ({ signal_id: f.signal_id, source_type: f.source_type })) const res = await fetch(`${PROXY}/advisor-start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ company, industry, products: [], markets: ['EU'], certifications: certs, known_evidence: knownEvidence ? knownEvidence.split(',').map(s => s.trim()).filter(Boolean) : [], target, scanner_findings, }), }) if (!res.ok) throw new Error(await res.text()) setResult(await res.json()) } catch (e) { setError(e instanceof Error ? e.message : 'Advisor fehlgeschlagen') } finally { setLoading(false) } } // auto-recompute when certifications / target / scanner signals change (no button click needed) useEffect(() => { if (certs.length) run() }, [certs, target, findings]) // eslint-disable-line react-hooks/exhaustive-deps return (

ETO / Onboarding-Advisor

Zertifikate + Ziel + Scanner-Signale → Silent Pass → Capability-Delta + nächste beste Fragen. Welt-1: ein Zertifikat legt nahe, beweist nichts (Verifikation erforderlich).

{CERTS.map(c => ( ))}
{FINDINGS.map(f => ( ))}
{error &&
{error}
} {result && (
{result.headline}
{result.silent_intake_summary}
{result.top_5_questions.length ? (
    {result.top_5_questions.map((q, i) => (
  1. {i + 1}. {lbl(q.capability_id)}
    {q.why}
  2. ))}
) : }
{result.inferred_assumptions.length ? result.inferred_assumptions.map(a => (
{a.certification}: {a.capabilities.map(lbl).join(', ')}
)) : }
{result.rejected_assumptions.length ? result.rejected_assumptions.map((a, i) => (
{a.statement}
)) : }
{result.completeness_summary || '—'}
)}
) }