chore(cra): align CRA module to the dev/demo tenant + demo-customer seed script
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function ChecksPage({
|
||||
const [running, setRunning] = useState<string | null>(null)
|
||||
const [urlInputs, setUrlInputs] = useState<Record<string, string>>({})
|
||||
|
||||
const tenant = '00000000-0000-0000-0000-000000000001'
|
||||
const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function DocumentsPage({
|
||||
const [approving, setApproving] = useState<string | null>(null)
|
||||
const [signedBy, setSignedBy] = useState('')
|
||||
|
||||
const tenant = '00000000-0000-0000-0000-000000000001'
|
||||
const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function SBOMPage({
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const fileRef = useRef<HTMLInputElement>(null)
|
||||
const tenant = '00000000-0000-0000-0000-000000000001'
|
||||
const tenant = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e'
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user