From 66d30568e2a7873b948785394a2ec4ccd30f349f Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Mon, 11 May 2026 23:39:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(dsms):=20Stufe=201=20=E2=80=94=20Gap-Analy?= =?UTF-8?q?se=20Report=20wird=20in=20DSMS=20archiviert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Go DSMS Client (internal/dsms/client.go): Archive() + Verify() - Python DSMS Client (compliance/services/dsms_client.py): archive_to_dsms() + verify_dsms() - Gap-Analyse AnalyzeProject() archiviert Report-JSON nach DSMS - Response enthält dsms_cid wenn Archivierung erfolgreich - Frontend: Grünes "Revisionssicher archiviert" Badge mit CID im GapDashboard - DSMS Proxy Route (/api/sdk/v1/dsms/[...path]) für Verify-Abfragen Stufe 2 (Evidence Upload → DSMS) und Stufe 3 (Version Chains) folgen. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/api/sdk/v1/dsms/[[...path]]/route.ts | 22 ++++ .../gap-analysis/_components/GapDashboard.tsx | 15 +++ .../internal/api/handlers/gap_handler.go | 26 ++++- ai-compliance-sdk/internal/dsms/client.go | 103 ++++++++++++++++++ .../compliance/services/dsms_client.py | 91 ++++++++++++++++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 admin-compliance/app/api/sdk/v1/dsms/[[...path]]/route.ts create mode 100644 ai-compliance-sdk/internal/dsms/client.go create mode 100644 backend-compliance/compliance/services/dsms_client.py diff --git a/admin-compliance/app/api/sdk/v1/dsms/[[...path]]/route.ts b/admin-compliance/app/api/sdk/v1/dsms/[[...path]]/route.ts new file mode 100644 index 0000000..546d019 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/dsms/[[...path]]/route.ts @@ -0,0 +1,22 @@ +/** + * DSMS Gateway Proxy — forwards verify/history requests to dsms-gateway. + */ +import { NextRequest, NextResponse } from 'next/server' + +const DSMS_URL = process.env.DSMS_GATEWAY_URL || 'http://dsms-gateway:8082' + +export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { + const { path } = await params + const target = `${DSMS_URL}/api/v1/${path.join('/')}` + + try { + const resp = await fetch(target, { + headers: { Authorization: 'Bearer system-frontend' }, + signal: AbortSignal.timeout(15000), + }) + const data = await resp.json() + return NextResponse.json(data, { status: resp.status }) + } catch { + return NextResponse.json({ error: 'DSMS not available' }, { status: 503 }) + } +} diff --git a/admin-compliance/app/sdk/gap-analysis/_components/GapDashboard.tsx b/admin-compliance/app/sdk/gap-analysis/_components/GapDashboard.tsx index 5201cb7..2f395bf 100644 --- a/admin-compliance/app/sdk/gap-analysis/_components/GapDashboard.tsx +++ b/admin-compliance/app/sdk/gap-analysis/_components/GapDashboard.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react' interface GapReport { + dsms_cid?: string profile_name: string regulations: Array<{ id: string @@ -79,6 +80,20 @@ export function GapDashboard({ report, onBack }: Props) { ← Neue Analyse + {/* DSMS Archive Badge */} + {report.dsms_cid && ( +
+ + + + Revisionssicher archiviert + + {report.dsms_cid.length > 20 ? report.dsms_cid.slice(0, 8) + '...' + report.dsms_cid.slice(-6) : report.dsms_cid} + + DSMS/IPFS +
+ )} + {/* Summary Cards */}
dict: + """Archive binary content to DSMS (IPFS). + + Returns dict with keys: cid, size, gateway_url. + Returns empty dict on failure (non-critical — logs warning). + """ + data = { + "document_type": document_type, + "document_id": document_id, + "version": version, + "language": language, + } + if parent_cid: + data["parent_cid"] = parent_cid + if tenant_id: + data["tenant_id"] = tenant_id + + try: + async with httpx.AsyncClient(timeout=60.0) as client: + resp = await client.post( + f"{DSMS_GATEWAY_URL}/api/v1/documents", + files={"file": (filename, content)}, + data=data, + headers={"Authorization": "Bearer system-backend"}, + ) + + if resp.status_code != 200: + logger.warning("DSMS archive failed (%d): %s", resp.status_code, resp.text[:200]) + return {} + + result = resp.json() + logger.info("DSMS archived: %s → CID %s (%d bytes)", filename, result.get("cid", "?"), result.get("size", 0)) + return result + + except Exception as e: + logger.warning("DSMS archive unavailable: %s", e) + return {} + + +async def verify_dsms(cid: str) -> dict: + """Verify a document's integrity by CID. + + Returns dict with verification result or empty dict on failure. + """ + try: + async with httpx.AsyncClient(timeout=15.0) as client: + resp = await client.get(f"{DSMS_GATEWAY_URL}/api/v1/verify/{cid}") + + if resp.status_code != 200: + return {"valid": False, "error": f"HTTP {resp.status_code}"} + + return resp.json() + + except Exception as e: + logger.warning("DSMS verify unavailable: %s", e) + return {"valid": False, "error": str(e)}