feat(cra): hard CRA<->IACE link — IACE tab pulls the linked assessment [migration-approved]

Migration 153 adds compliance_cra_projects.linked_iace_project_id (additive,
idempotent). New thin router cra_link_routes.py: POST /projects/{id}/link-iace
sets the reference; GET /by-iace/{iace_project_id} returns the linked CRA project
+ its latest assessment snapshot. The IACE "CRA / Cyber" tab now resolves the
linked CRA assessment first (real, from the snapshot) and only falls back to the
demo scenario when nothing is linked. One assessment, two views.

[migration-approved] — user approved the new column for the CRA<->IACE reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-14 19:22:29 +02:00
parent b19d76407d
commit 60f988f3cb
4 changed files with 109 additions and 8 deletions
@@ -102,17 +102,36 @@ export function useCRA(projectId?: string) {
useEffect(() => {
let cancelled = false
fetch('/api/v1/cra/assess', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(buildPayload()),
})
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
.then((j) => { if (!cancelled) { setData(merge(j)); setLive(true) } })
.catch((err) => {
;(async () => {
// 1. Is a CRA project LINKED to this IACE project? Then show its real
// assessment (latest snapshot) instead of the demo scenario.
if (projectId) {
try {
const lr = await fetch(`/api/v1/cra/by-iace/${projectId}`)
if (lr.ok) {
const link = await lr.json()
if (link?.linked && link.assessment) {
if (!cancelled) { setData(merge(link.assessment)); setLive(true) }
return
}
}
} catch { /* fall through to the demo assess */ }
}
// 2. Fallback: live demo assess (scenario findings).
try {
const r = await fetch('/api/v1/cra/assess', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(buildPayload()),
})
if (!r.ok) throw new Error(`HTTP ${r.status}`)
const j = await r.json()
if (!cancelled) { setData(merge(j)); setLive(true) }
} catch (err) {
console.error('CRA assess fetch failed, using static scenario:', err)
if (!cancelled) { setData(DEMO_SCENARIO); setLive(false) }
})
}
})()
return () => { cancelled = true }
}, [buildPayload])
}, [buildPayload, projectId])
const refreshSnapshots = useCallback(() => {
if (!projectId) return