feat(dsms): Stufe 2+3 — Evidence/TechFile → DSMS + Version Chains + Audit Timeline
Build + Deploy / build-admin-compliance (push) Successful in 1m58s
Build + Deploy / build-backend-compliance (push) Successful in 12s
Build + Deploy / build-ai-sdk (push) Successful in 11s
Build + Deploy / build-developer-portal (push) Successful in 11s
Build + Deploy / build-tts (push) Successful in 21s
Build + Deploy / build-document-crawler (push) Successful in 11s
Build + Deploy / build-dsms-gateway (push) Successful in 14s
Build + Deploy / build-dsms-node (push) Successful in 14s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 15s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m40s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 40s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 22s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Successful in 2m26s

Stufe 2A: Evidence Upload → automatische DSMS-Archivierung
- Nach SHA-256 Hash → archive_to_dsms(), CID im Audit-Trail
- Evidence mit CID wird automatisch zu E2 (hash-verifiziert) hochgestuft

Stufe 2B: IACE Tech-File Export → DSMS
- PDF/Excel/DOCX/Markdown Exporte werden nach DSMS archiviert
- archiveTechFile() Helper fuer alle 4 Formate

Stufe 3A: DSMS Gateway — parent_cid + History Endpoint
- parent_cid + tenant_id Felder in DocumentMetadata
- GET /documents/{cid}/history — folgt parent_cid-Chain (max 50 deep)

Stufe 3C: Audit Timeline UI
- Neue Seite /sdk/audit-timeline
- Vertikale Timeline mit farbigen Action-Dots
- Filter: Alle, Nachweis, DSMS-Archiv, Control, Dokument, DSFA, VVT, TOM
- CID-Badges fuer DSMS-archivierte Eintraege

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-12 13:55:07 +02:00
parent 06bfbd1dca
commit edbf6d2be5
6 changed files with 241 additions and 4 deletions
@@ -248,7 +248,10 @@ async def create_evidence(
)
db.commit()
return _build_evidence_response(evidence)
resp = _build_evidence_response(evidence)
if dsms_cid:
resp["dsms_cid"] = dsms_cid
return resp
@router.delete("/evidence/{evidence_id}")
@@ -313,6 +316,25 @@ async def upload_evidence(
evidence.confidence_level = EvidenceConfidenceEnum.E1
evidence.truth_status = EvidenceTruthStatusEnum.UPLOADED
# Archive to DSMS (best-effort, non-blocking)
dsms_cid = None
try:
from compliance.services.dsms_client import archive_to_dsms
dsms_result = await archive_to_dsms(
content=content, filename=file.filename,
document_type="evidence", document_id=str(evidence.id),
version="1", tenant_id=control_id,
)
dsms_cid = dsms_result.get("cid")
if dsms_cid:
evidence.confidence_level = EvidenceConfidenceEnum.E2
from compliance.api.audit_trail_utils import log_audit_trail
log_audit_trail(db, "evidence", str(evidence.id), title, "archive",
"system", field_changed="dsms_cid", new_value=dsms_cid,
change_summary=f"Evidence archived to DSMS: {dsms_cid}")
except Exception:
pass # DSMS unavailable
# Four-Eyes: check if the linked control's domain requires it
control_domain = control.domain.value if control.domain else ""
if _requires_four_eyes(control_domain):
@@ -321,7 +343,10 @@ async def upload_evidence(
db.commit()
return _build_evidence_response(evidence)
resp = _build_evidence_response(evidence)
if dsms_cid:
resp["dsms_cid"] = dsms_cid
return resp
# ============================================================================
@@ -813,7 +838,10 @@ async def review_evidence(
db.commit()
db.refresh(evidence)
return _build_evidence_response(evidence)
resp = _build_evidence_response(evidence)
if dsms_cid:
resp["dsms_cid"] = dsms_cid
return resp
@router.patch("/evidence/{evidence_id}/reject", response_model=EvidenceResponse)
@@ -840,7 +868,10 @@ async def reject_evidence(
db.commit()
db.refresh(evidence)
return _build_evidence_response(evidence)
resp = _build_evidence_response(evidence)
if dsms_cid:
resp["dsms_cid"] = dsms_cid
return resp
# ============================================================================