From 398eaf3c367726a90abf0f463850c2d4a96aca8d Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 14 Jun 2026 12:23:37 +0200 Subject: [PATCH] =?UTF-8?q?feat(cra):=20two-lane=20breadth=20=E2=80=94=20C?= =?UTF-8?q?RA-specific=20corpus=20+=20technical=20depth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 6 security use_cases are atom-grain now. Per finding we draw two lanes: the CRA corpus (use_case=cra, the most on-point CRA obligations) + the technical depth (code_security for secure-dev, else network_security). Controls merged, deduped, each tagged with its use_case (shown in the best-practice depth). Co-Authored-By: Claude Opus 4.7 --- .../cra/_components/CRACyberView.tsx | 6 +- .../sdk/iace/[projectId]/cra/_hooks/useCRA.ts | 1 - .../iace/[projectId]/cra/_hooks/useCRADemo.ts | 5 +- .../services/cra_use_case_controls.py | 59 ++++++++++--------- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx index 4a7e5e96..d202d4c9 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_components/CRACyberView.tsx @@ -130,12 +130,14 @@ function FindingsTable({ findings }: { findings: CRAFinding[] }) { {f.regulatory_breadth && f.regulatory_breadth.length > 0 && (

- Regulatorische Breite{f.sub_topic ? ` — ${f.sub_topic}` : ''} - {f.breadth_use_case ? ` · ${f.breadth_use_case}` : ''} (NIST/OWASP/ENISA-Quellen) + Regulatorische Breite{f.sub_topic ? ` — ${f.sub_topic}` : ''} (CRA + NIST/OWASP/ENISA-Quellen)

    {f.regulatory_breadth.map((c) => (
  • + {c.use_case && ( + {c.use_case} + )} {c.control_id} {c.title} · {c.source_regulation}
  • diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts index 86469f8e..040c4a83 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts @@ -58,7 +58,6 @@ function merge(live: any): CRADemo { measures: m.measures || [], evidence_type: m.evidence_type, sub_topic: m.sub_topic, - breadth_use_case: m.breadth_use_case, regulatory_breadth: m.regulatory_breadth || [], priority_tier: m.priority_tier, priority_score: m.priority_score, diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts index 82ce7cca..55ddcd33 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRADemo.ts @@ -28,10 +28,9 @@ export interface CRAFinding { risk_level: string measures: string[] evidence_type?: string // code | process | hybrid | document — drives the remediation-class badge - // regulatory breadth (atom-grain shared Controls-API: network_security / code_security), live only + // regulatory breadth (atom-grain shared Controls-API: cra + code/network_security), live only sub_topic?: string - breadth_use_case?: string - regulatory_breadth?: { control_id: string; title: string; source_regulation: string; severity?: string }[] + regulatory_breadth?: { control_id: string; title: string; source_regulation: string; severity?: string; use_case?: string }[] // priority layer (set live by the backend prioritizer; optional in the static fallback) priority_tier?: string priority_score?: number diff --git a/backend-compliance/compliance/services/cra_use_case_controls.py b/backend-compliance/compliance/services/cra_use_case_controls.py index 16637e81..03910ca0 100644 --- a/backend-compliance/compliance/services/cra_use_case_controls.py +++ b/backend-compliance/compliance/services/cra_use_case_controls.py @@ -38,27 +38,26 @@ _REQ_TO_SUBTOPIC = { } -# Which atom-grain use_case is most relevant per sub_topic. Both network_security -# and code_security are atom-grain; secure-dev is best served by code_security. -_SUBTOPIC_TO_USECASE = { - "secure_development": "code_security", -} -_DEFAULT_USECASE = "network_security" +# Two breadth lanes per finding: the CRA-specific corpus (always — most on-point +# for a CRA assessment) + the technical-depth corpus for the sub_topic +# (code_security for secure-dev, else network_security). All atom-grain. +# isms/dora/kritis are also atom-grain — available for future per-regime routing. +_TECHNICAL_USECASE = {"secure_development": "code_security"} # default: network_security def subtopic_for(req_id: str): return _REQ_TO_SUBTOPIC.get(req_id) -def usecase_for(sub_topic: str) -> str: - return _SUBTOPIC_TO_USECASE.get(sub_topic, _DEFAULT_USECASE) +def usecases_for(sub_topic: str) -> list: + return ["cra", _TECHNICAL_USECASE.get(sub_topic, "network_security")] -def enrich_findings_with_breadth(mapped: list, db, limit: int = 5) -> None: - """Attach `sub_topic` + `regulatory_breadth` (atom controls) to each finding. - - Queries network_security once per distinct sub_topic (cached). Best-effort: - on any error a finding just gets an empty breadth — never breaks the assessment. +def enrich_findings_with_breadth(mapped: list, db, per_use_case: int = 3) -> None: + """Attach `sub_topic` + `regulatory_breadth` (atom controls from the CRA corpus + + the technical-depth corpus) to each finding, each control tagged with its + use_case. Queries are cached per (use_case, sub_topic). Best-effort: on any + error a finding just gets fewer/empty breadth — never breaks the assessment. """ svc = UseCaseControlsService(db) cache: dict = {} @@ -67,19 +66,23 @@ def enrich_findings_with_breadth(mapped: list, db, limit: int = 5) -> None: m["sub_topic"] = st if not st: m["regulatory_breadth"] = [] - m["breadth_use_case"] = None continue - uc = usecase_for(st) - m["breadth_use_case"] = uc - key = (uc, st) - if key not in cache: - try: - res = svc.controls_for_use_case(uc, sub_topic=st, limit=limit) - cache[key] = [ - {"control_id": c.get("control_id"), "title": c.get("title"), - "source_regulation": c.get("source_regulation"), "severity": c.get("severity")} - for c in res.get("controls", []) - ] - except Exception: - cache[key] = [] - m["regulatory_breadth"] = cache[key] + merged, seen = [], set() + for uc in usecases_for(st): + key = (uc, st) + if key not in cache: + try: + res = svc.controls_for_use_case(uc, sub_topic=st, limit=per_use_case) + cache[key] = [ + {"control_id": c.get("control_id"), "title": c.get("title"), + "source_regulation": c.get("source_regulation"), + "severity": c.get("severity"), "use_case": uc} + for c in res.get("controls", []) + ] + except Exception: + cache[key] = [] + for c in cache[key]: + if c["control_id"] and c["control_id"] not in seen: + seen.add(c["control_id"]) + merged.append(c) + m["regulatory_breadth"] = merged