From b19d76407d07efa24db902a4d1b66aa8b802bdc0 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 14 Jun 2026 15:52:49 +0200 Subject: [PATCH] chore(cra): align CRA module to the dev/demo tenant + demo-customer seed script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRA frontend pages hardcoded tenant 00000000-…-001 while IACE uses the dev tenant 9282a473-… → a demo customer was split/invisible across modules. Align all app/sdk/cra pages to 9282a473-… so the whole CRA<->IACE journey lives under ONE tenant. Add scripts/seed_demo_customer.py: seeds CompanyProfile + IACE project (components, hazards, mitigations) + CRA project (intake, scope-check, assessment snapshot from faked repo findings + components + safety functions) — the source- repo layer is faked so the full frontend is walkable once. Co-Authored-By: Claude Opus 4.7 --- .../app/sdk/cra/[projectId]/backlog/page.tsx | 2 +- .../app/sdk/cra/[projectId]/checks/page.tsx | 2 +- .../sdk/cra/[projectId]/documents/page.tsx | 2 +- .../app/sdk/cra/[projectId]/intake/page.tsx | 2 +- .../sdk/cra/[projectId]/monitoring/page.tsx | 2 +- .../app/sdk/cra/[projectId]/page.tsx | 2 +- .../app/sdk/cra/[projectId]/path/page.tsx | 2 +- .../sdk/cra/[projectId]/requirements/page.tsx | 2 +- .../app/sdk/cra/[projectId]/sbom/page.tsx | 2 +- .../app/sdk/cra/[projectId]/scope/page.tsx | 2 +- .../app/sdk/cra/[projectId]/vuln/page.tsx | 2 +- admin-compliance/app/sdk/cra/page.tsx | 2 +- .../scripts/seed_demo_customer.py | 140 ++++++++++++++++++ 13 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 backend-compliance/scripts/seed_demo_customer.py diff --git a/admin-compliance/app/sdk/cra/[projectId]/backlog/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/backlog/page.tsx index 118b162c..c93d8fb7 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/backlog/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/backlog/page.tsx @@ -39,7 +39,7 @@ export default function BacklogPage({ const load = useCallback(async () => { try { const res = await fetch(`/api/sdk/v1/cra/projects/${projectId}/backlog`, { - headers: { 'X-Tenant-ID': '00000000-0000-0000-0000-000000000001' }, + headers: { 'X-Tenant-ID': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' }, }) if (!res.ok) throw new Error(await res.text()) setData(await res.json()) diff --git a/admin-compliance/app/sdk/cra/[projectId]/checks/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/checks/page.tsx index 2737ad46..ef66f196 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/checks/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/checks/page.tsx @@ -39,7 +39,7 @@ export default function ChecksPage({ const [running, setRunning] = useState(null) const [urlInputs, setUrlInputs] = useState>({}) - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/documents/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/documents/page.tsx index a7de32a0..774710bd 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/documents/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/documents/page.tsx @@ -61,7 +61,7 @@ export default function DocumentsPage({ const [approving, setApproving] = useState(null) const [signedBy, setSignedBy] = useState('') - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/intake/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/intake/page.tsx index 6dfd0a07..ae0cc18c 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/intake/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/intake/page.tsx @@ -54,7 +54,7 @@ export default function IntakePage({ const [isCriticalInfra, setIsCriticalInfra] = useState(false) const [intendedUse, setIntendedUse] = useState('') - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/monitoring/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/monitoring/page.tsx index f8ef7737..9781eeea 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/monitoring/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/monitoring/page.tsx @@ -30,7 +30,7 @@ export default function MonitoringPage({ const load = useCallback(async () => { try { const res = await fetch(`/api/sdk/v1/cra/projects/${projectId}/monitoring`, { - headers: { 'X-Tenant-ID': '00000000-0000-0000-0000-000000000001' }, + headers: { 'X-Tenant-ID': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' }, }) if (!res.ok) throw new Error(await res.text()) setData(await res.json()) diff --git a/admin-compliance/app/sdk/cra/[projectId]/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/page.tsx index 233fc447..e9b9bbf5 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/page.tsx @@ -59,7 +59,7 @@ export default function CRAProjectDashboard({ const load = useCallback(async () => { try { - const headers = { 'X-Tenant-ID': '00000000-0000-0000-0000-000000000001' } + const headers = { 'X-Tenant-ID': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' } const [projRes, backlogRes] = await Promise.all([ fetch(`/api/sdk/v1/cra/projects/${projectId}`, { headers }), fetch(`/api/sdk/v1/cra/projects/${projectId}/backlog`, { headers }), diff --git a/admin-compliance/app/sdk/cra/[projectId]/path/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/path/page.tsx index a01e31f4..6d72ad98 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/path/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/path/page.tsx @@ -100,7 +100,7 @@ export default function PathSelectPage({ const [saving, setSaving] = useState(false) const [error, setError] = useState('') - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/requirements/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/requirements/page.tsx index 81555ae2..c976067a 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/requirements/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/requirements/page.tsx @@ -42,7 +42,7 @@ export default function RequirementsPage({ const load = useCallback(async () => { try { const res = await fetch(`/api/sdk/v1/cra/projects/${projectId}/requirements`, { - headers: { 'X-Tenant-ID': '00000000-0000-0000-0000-000000000001' }, + headers: { 'X-Tenant-ID': '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' }, }) if (!res.ok) throw new Error(await res.text()) setData(await res.json()) diff --git a/admin-compliance/app/sdk/cra/[projectId]/sbom/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/sbom/page.tsx index 0934338d..8e0c14e3 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/sbom/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/sbom/page.tsx @@ -32,7 +32,7 @@ export default function SBOMPage({ const [uploading, setUploading] = useState(false) const [error, setError] = useState('') const fileRef = useRef(null) - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/scope/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/scope/page.tsx index c706c0f2..2c90ef51 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/scope/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/scope/page.tsx @@ -38,7 +38,7 @@ export default function ScopeCheckPage({ const [checking, setChecking] = useState(false) const [error, setError] = useState('') - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/[projectId]/vuln/page.tsx b/admin-compliance/app/sdk/cra/[projectId]/vuln/page.tsx index e1fbc918..4bda7c2a 100644 --- a/admin-compliance/app/sdk/cra/[projectId]/vuln/page.tsx +++ b/admin-compliance/app/sdk/cra/[projectId]/vuln/page.tsx @@ -89,7 +89,7 @@ export default function VulnPage({ const [reporterSource, setReporterSource] = useState('internal') const [reporterContact, setReporterContact] = useState('') - const tenant = '00000000-0000-0000-0000-000000000001' + const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const load = useCallback(async () => { try { diff --git a/admin-compliance/app/sdk/cra/page.tsx b/admin-compliance/app/sdk/cra/page.tsx index cb9ca208..41366f96 100644 --- a/admin-compliance/app/sdk/cra/page.tsx +++ b/admin-compliance/app/sdk/cra/page.tsx @@ -47,7 +47,7 @@ export default function CRAProjectsPage() { const [newDescription, setNewDescription] = useState('') const [creating, setCreating] = useState(false) - const tenantHeader = '00000000-0000-0000-0000-000000000001' + const tenantHeader = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' const loadProjects = useCallback(async () => { try { diff --git a/backend-compliance/scripts/seed_demo_customer.py b/backend-compliance/scripts/seed_demo_customer.py new file mode 100644 index 00000000..5de043b0 --- /dev/null +++ b/backend-compliance/scripts/seed_demo_customer.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +"""Seed ONE coherent demo customer so the whole CRA<->IACE frontend journey is +walkable: CompanyProfile (complete) + an IACE CE risk-assessment project +(Kistenhubgeraet, with components + hazards + mitigations) + a CRA project +(intake + scope-check) + a persisted CRA assessment snapshot driven by faked +repo findings + the machine's networked components + its safety functions. + +Run inside the backend container (reaches both APIs): + docker exec -i bp-compliance-backend python3 - < scripts/seed_demo_customer.py + +Idempotency: not enforced — re-running creates duplicates. Resilient: each step +prints its outcome and continues on error. The source-repo layer is FAKED +(findings below) so the entire frontend can be seen once. +""" +import json +import urllib.request + +TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" +BACKEND = "http://localhost:8002" +AISDK = "http://ai-compliance-sdk:8090" +H = {"Content-Type": "application/json", "X-Tenant-ID": TENANT, "X-Tenant-Id": TENANT} + + +def call(method, url, body=None): + data = json.dumps(body).encode() if body is not None else None + req = urllib.request.Request(url, data=data, headers=H, method=method) + try: + with urllib.request.urlopen(req, timeout=30) as r: + txt = r.read().decode() + return r.getcode(), (json.loads(txt) if txt.strip() else {}) + except urllib.error.HTTPError as e: + return e.code, {"error": e.read().decode()[:300]} + except Exception as e: + return 0, {"error": str(e)} + + +def step(label, method, url, body=None): + code, resp = call(method, url, body) + ok = 200 <= code < 300 + rid = resp.get("id") if isinstance(resp, dict) else None + print((" OK " if ok else " !! ") + f"[{code}] {label}" + (f" id={rid}" if rid else "") + + ("" if ok else f" {resp.get('error','')[:160]}")) + return resp if ok else None + + +print("== 1. CompanyProfile ==") +step("company-profile", "POST", f"{BACKEND}/api/v1/company-profile", { + "company_name": "DEMO Kistenhub GmbH", "legal_form": "GmbH", "industry": "Maschinenbau", + "founded_year": 2015, "business_model": "B2B", "company_size": "medium", "employee_count": "50-99", + "headquarters_country": "DE", "headquarters_city": "Augsburg", "is_data_controller": True, + "is_complete": True, +}) + +print("== 2. IACE project (CE risk assessment) ==") +proj = step("iace project", "POST", f"{AISDK}/sdk/v1/iace/projects", { + "machine_name": "Kistenhubgeraet Demo", "machine_type": "Hydraulischer Kistenhubwagen (vernetzt)", + "manufacturer": "DEMO Kistenhub GmbH", + "description": "Palettierte-Gueter-Hubgeraet mit IoT-Modul, App-Parametrierung und Fernwartung.", + "narrative_text": "Zwei-Hub-System, hydraulisch, mit vernetzter Steuerung und Fernzugriff.", + "ce_marking_target": "Maschinenverordnung (EU) 2023/1230", +}) +pid = proj.get("id") if proj else None +if not pid: # IACE create response may not surface "id" — fall back to the list + _, lst = call("GET", f"{AISDK}/sdk/v1/iace/projects") + for p in (lst.get("projects") if isinstance(lst, dict) else []) or []: + if p.get("machine_name") == "Kistenhubgeraet Demo": + pid = p.get("id") + break + print(f" (fallback) iace project id={pid}") + +comp_ids = {} +if pid: + print("== 3. IACE components ==") + for c in [ + {"name": "Hub-Steuerung (SPS)", "component_type": "controller", "is_safety_relevant": True, "is_networked": True}, + {"name": "Bedienpanel (HMI)", "component_type": "hmi", "is_networked": True}, + {"name": "Hydraulikpumpe", "component_type": "hydraulic", "is_safety_relevant": True}, + {"name": "Fernwartungs-Gateway", "component_type": "gateway", "is_networked": True}, + ]: + c["project_id"] = pid + r = step(f"component {c['name']}", "POST", f"{AISDK}/sdk/v1/iace/projects/{pid}/components", c) + if r: + comp_ids[c["name"]] = r.get("id") + + print("== 4. IACE hazards + mitigations ==") + hazards = [ + {"name": "Quetschen zwischen Last und Rahmen", "category": "Mechanische Gefaehrdungen", + "scenario": "Bediener greift bei Hubbewegung zwischen Last und Rahmen", + "comp": "Hub-Steuerung (SPS)", "measure": "Zweihandschaltung + trennende Schutzeinrichtung (PL d)"}, + {"name": "Lastabsturz durch Ueberlast", "category": "Mechanische Gefaehrdungen", + "scenario": "Ueberschreiten der zulaessigen Last fuehrt zum Absturz", + "comp": "Hydraulikpumpe", "measure": "Ueberlastsicherung / Lastmomentbegrenzer"}, + {"name": "Schneiden an scharfen Kanten", "category": "Mechanische Gefaehrdungen", + "scenario": "Kontakt mit scharfen Gehaeusekanten bei Wartung", + "comp": "Bedienpanel (HMI)", "measure": "Brechen oder Runden aller zugaenglichen Kanten"}, + ] + for hz in hazards: + body = {"project_id": pid, "name": hz["name"], "category": hz["category"], "scenario": hz["scenario"]} + cid = comp_ids.get(hz["comp"]) + if cid: + body["component_id"] = cid + r = step(f"hazard {hz['name']}", "POST", f"{AISDK}/sdk/v1/iace/projects/{pid}/hazards", body) + hid = r.get("id") if r else None + if hid: + step(" mitigation", "POST", f"{AISDK}/sdk/v1/iace/projects/{pid}/hazards/{hid}/mitigations", + {"hazard_id": hid, "reduction_type": "design_measure", "name": hz["measure"]}) + +print("== 5. CRA project + intake + scope-check ==") +cra = step("cra project", "POST", f"{BACKEND}/api/v1/cra/projects", + {"name": "DEMO Kistenhub CRA", "description": "Cyber-Risikobeurteilung fuer das Kistenhubgeraet"}) +cra_id = cra.get("id") if cra else None +if cra_id: + step("intake (flags)", "PATCH", f"{BACKEND}/api/v1/cra/projects/{cra_id}", { + "intended_use": "Vernetztes Kistenhubgeraet mit App-Parametrierung und Fernwartung", + "connected_to_internet": True, "has_software_updates": True, "has_firmware": True, + "processes_personal_data": False, "is_critical_infra_supplier": False, + }) + step("scope-check", "POST", f"{BACKEND}/api/v1/cra/projects/{cra_id}/scope-check") + + print("== 6. CRA assessment snapshot (faked repo findings + components + safety functions) ==") + snap_body = { + "findings": [ + {"id": "REPO-001", "title": "Default-Passwort in Fernsteuer-UI", "cwe": "CWE-259", "severity": "critical", "location": "remote-ui/login"}, + {"id": "REPO-002", "title": "Schwache TLS-Konfiguration", "cwe": "CWE-327", "severity": "high", "location": "telemetry/tls"}, + {"id": "REPO-003", "title": "Veraltete Abhaengigkeit libmodbus", "category": "dependency", "cwe": "CWE-1104", "severity": "high", "location": "deps/libmodbus"}, + ], + "components": [ + {"name": "Hub-Steuerung (SPS)", "component_class": "controller", "networked": True}, + {"name": "Bedienpanel (HMI)", "component_class": "hmi", "networked": True}, + {"name": "Fernwartungs-Gateway", "component_class": "gateway", "networked": True}, + ], + "safety_functions": [ + {"name": "Zweihandschaltung + trennende Schutzeinrichtung", "hazard": "Quetschen zwischen Last und Rahmen", "kind": "prevent_unexpected_actuation"}, + {"name": "Ueberlastsicherung / Lastmomentbegrenzer", "hazard": "Lastabsturz durch Ueberlast", "kind": "signal_integrity"}, + ], + "weights": {"access": "high", "data": "high"}, + } + step("assess-snapshot", "POST", f"{BACKEND}/api/v1/cra/projects/{cra_id}/assess-snapshot", snap_body) + +print("\nDONE. tenant=%s | iace_project=%s | cra_project=%s" % (TENANT, pid, cra_id))