feat: Control-Detail Provenance + Atomare Controls Seite
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 41s
CI/CD / test-python-backend-compliance (push) Successful in 40s
CI/CD / test-python-document-crawler (push) Successful in 23s
CI/CD / test-python-dsms-gateway (push) Successful in 18s
CI/CD / validate-canonical-controls (push) Successful in 11s
CI/CD / Deploy (push) Successful in 4s

Backend: provenance endpoint (obligations, doc refs, merged duplicates,
regulations summary) + atomic-stats aggregation endpoint.
Frontend: ControlDetail mit Provenance-Sektionen, klickbare Navigation,
neue /sdk/atomic-controls Seite mit Stats-Bar und gefilterer Liste.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-24 10:38:34 +01:00
parent 200facda6a
commit 6d3bdf8e74
8 changed files with 1210 additions and 5 deletions

View File

@@ -473,6 +473,61 @@ async def controls_meta():
}
@router.get("/controls/atomic-stats")
async def atomic_stats():
"""Return aggregated statistics for atomic controls (masters only)."""
with SessionLocal() as db:
total_active = db.execute(text("""
SELECT count(*) FROM canonical_controls
WHERE decomposition_method = 'pass0b'
AND release_state NOT IN ('duplicate', 'deprecated', 'rejected')
""")).scalar() or 0
total_duplicate = db.execute(text("""
SELECT count(*) FROM canonical_controls
WHERE decomposition_method = 'pass0b'
AND release_state = 'duplicate'
""")).scalar() or 0
by_domain = db.execute(text("""
SELECT UPPER(SPLIT_PART(control_id, '-', 1)) AS domain, count(*) AS cnt
FROM canonical_controls
WHERE decomposition_method = 'pass0b'
AND release_state NOT IN ('duplicate', 'deprecated', 'rejected')
GROUP BY domain ORDER BY cnt DESC
""")).fetchall()
by_regulation = db.execute(text("""
SELECT cpl.source_regulation AS regulation, count(DISTINCT cc.id) AS cnt
FROM canonical_controls cc
JOIN control_parent_links cpl ON cpl.control_uuid = cc.id
WHERE cc.decomposition_method = 'pass0b'
AND cc.release_state NOT IN ('duplicate', 'deprecated', 'rejected')
AND cpl.source_regulation IS NOT NULL
GROUP BY cpl.source_regulation ORDER BY cnt DESC
""")).fetchall()
avg_coverage = db.execute(text("""
SELECT COALESCE(AVG(reg_count), 0)
FROM (
SELECT cc.id, count(DISTINCT cpl.source_regulation) AS reg_count
FROM canonical_controls cc
LEFT JOIN control_parent_links cpl ON cpl.control_uuid = cc.id
WHERE cc.decomposition_method = 'pass0b'
AND cc.release_state NOT IN ('duplicate', 'deprecated', 'rejected')
GROUP BY cc.id
) sub
""")).scalar() or 0
return {
"total_active": total_active,
"total_duplicate": total_duplicate,
"by_domain": [{"domain": r[0], "count": r[1]} for r in by_domain],
"by_regulation": [{"regulation": r[0], "count": r[1]} for r in by_regulation],
"avg_regulation_coverage": round(float(avg_coverage), 1),
}
@router.get("/controls/{control_id}")
async def get_control(control_id: str):
"""Get a single canonical control by its control_id (e.g. AUTH-001)."""
@@ -620,6 +675,239 @@ async def get_control_traceability(control_id: str):
return result
@router.get("/controls/{control_id}/provenance")
async def get_control_provenance(control_id: str):
"""Get full provenance chain for a control — extends traceability with
obligations, document references, merged duplicates, and regulations summary.
"""
with SessionLocal() as db:
ctrl = db.execute(
text("""
SELECT id, control_id, title, parent_control_uuid,
decomposition_method, source_citation
FROM canonical_controls WHERE control_id = :cid
"""),
{"cid": control_id.upper()},
).fetchone()
if not ctrl:
raise HTTPException(status_code=404, detail="Control not found")
ctrl_uuid = str(ctrl.id)
is_atomic = ctrl.decomposition_method == "pass0b"
result: dict[str, Any] = {
"control_id": ctrl.control_id,
"title": ctrl.title,
"is_atomic": is_atomic,
}
# --- Parent links (same as traceability) ---
parent_links = db.execute(
text("""
SELECT cpl.parent_control_uuid, cpl.link_type,
cpl.confidence, cpl.source_regulation,
cpl.source_article, cpl.obligation_candidate_id,
cc.control_id AS parent_control_id,
cc.title AS parent_title,
cc.source_citation AS parent_citation,
oc.obligation_text, oc.action, oc.object,
oc.normative_strength
FROM control_parent_links cpl
JOIN canonical_controls cc ON cc.id = cpl.parent_control_uuid
LEFT JOIN obligation_candidates oc ON oc.id = cpl.obligation_candidate_id
WHERE cpl.control_uuid = CAST(:uid AS uuid)
ORDER BY cpl.source_regulation, cpl.source_article
"""),
{"uid": ctrl_uuid},
).fetchall()
result["parent_links"] = [
{
"parent_control_id": pl.parent_control_id,
"parent_title": pl.parent_title,
"link_type": pl.link_type,
"confidence": float(pl.confidence) if pl.confidence else 1.0,
"source_regulation": pl.source_regulation,
"source_article": pl.source_article,
"parent_citation": pl.parent_citation,
"obligation": {
"text": pl.obligation_text,
"action": pl.action,
"object": pl.object,
"normative_strength": pl.normative_strength,
} if pl.obligation_text else None,
}
for pl in parent_links
]
# Legacy 1:1 parent (backwards compat)
if ctrl.parent_control_uuid:
parent_uuids_in_links = {
str(pl.parent_control_uuid) for pl in parent_links
}
parent_uuid_str = str(ctrl.parent_control_uuid)
if parent_uuid_str not in parent_uuids_in_links:
legacy = db.execute(
text("""
SELECT control_id, title, source_citation
FROM canonical_controls WHERE id = CAST(:uid AS uuid)
"""),
{"uid": parent_uuid_str},
).fetchone()
if legacy:
result["parent_links"].insert(0, {
"parent_control_id": legacy.control_id,
"parent_title": legacy.title,
"link_type": "decomposition",
"confidence": 1.0,
"source_regulation": None,
"source_article": None,
"parent_citation": legacy.source_citation,
"obligation": None,
})
# --- Children ---
children = db.execute(
text("""
SELECT control_id, title, category, severity,
decomposition_method
FROM canonical_controls
WHERE parent_control_uuid = CAST(:uid AS uuid)
ORDER BY control_id
"""),
{"uid": ctrl_uuid},
).fetchall()
result["children"] = [
{
"control_id": ch.control_id,
"title": ch.title,
"category": ch.category,
"severity": ch.severity,
"decomposition_method": ch.decomposition_method,
}
for ch in children
]
# Source count
regs = set()
for pl in result["parent_links"]:
if pl.get("source_regulation"):
regs.add(pl["source_regulation"])
result["source_count"] = len(regs)
# --- Obligations (for Rich Controls) ---
obligations = db.execute(
text("""
SELECT candidate_id, obligation_text, action, object,
normative_strength, release_state
FROM obligation_candidates
WHERE parent_control_uuid = CAST(:uid AS uuid)
AND release_state NOT IN ('rejected', 'merged')
ORDER BY candidate_id
"""),
{"uid": ctrl_uuid},
).fetchall()
result["obligations"] = [
{
"candidate_id": ob.candidate_id,
"obligation_text": ob.obligation_text,
"action": ob.action,
"object": ob.object,
"normative_strength": ob.normative_strength,
"release_state": ob.release_state,
}
for ob in obligations
]
result["obligation_count"] = len(obligations)
# --- Document References ---
doc_refs = db.execute(
text("""
SELECT DISTINCT oe.regulation_code, oe.article, oe.paragraph,
oe.extraction_method, oe.confidence
FROM obligation_extractions oe
WHERE oe.control_uuid = CAST(:uid AS uuid)
OR oe.obligation_id IN (
SELECT oc.candidate_id FROM obligation_candidates oc
JOIN control_parent_links cpl ON cpl.obligation_candidate_id = oc.id
WHERE cpl.control_uuid = CAST(:uid AS uuid)
)
ORDER BY oe.regulation_code, oe.article
"""),
{"uid": ctrl_uuid},
).fetchall()
result["document_references"] = [
{
"regulation_code": dr.regulation_code,
"article": dr.article,
"paragraph": dr.paragraph,
"extraction_method": dr.extraction_method,
"confidence": float(dr.confidence) if dr.confidence else None,
}
for dr in doc_refs
]
# --- Merged Duplicates ---
merged = db.execute(
text("""
SELECT cc.control_id, cc.title,
(SELECT cpl.source_regulation FROM control_parent_links cpl
WHERE cpl.control_uuid = cc.id LIMIT 1) AS source_regulation
FROM canonical_controls cc
WHERE cc.merged_into_uuid = CAST(:uid AS uuid)
AND cc.release_state = 'duplicate'
ORDER BY cc.control_id
"""),
{"uid": ctrl_uuid},
).fetchall()
result["merged_duplicates"] = [
{
"control_id": m.control_id,
"title": m.title,
"source_regulation": m.source_regulation,
}
for m in merged
]
result["merged_duplicates_count"] = len(merged)
# --- Regulations Summary (aggregated from parent_links + doc_refs) ---
reg_map: dict[str, dict[str, Any]] = {}
for pl in result["parent_links"]:
reg = pl.get("source_regulation")
if not reg:
continue
if reg not in reg_map:
reg_map[reg] = {"articles": set(), "link_types": set()}
if pl.get("source_article"):
reg_map[reg]["articles"].add(pl["source_article"])
reg_map[reg]["link_types"].add(pl.get("link_type", "decomposition"))
for dr in result["document_references"]:
reg = dr.get("regulation_code")
if not reg:
continue
if reg not in reg_map:
reg_map[reg] = {"articles": set(), "link_types": set()}
if dr.get("article"):
reg_map[reg]["articles"].add(dr["article"])
result["regulations_summary"] = [
{
"regulation_code": reg,
"articles": sorted(info["articles"]),
"link_types": sorted(info["link_types"]),
}
for reg, info in sorted(reg_map.items())
]
return result
# =============================================================================
# CONTROL CRUD (CREATE / UPDATE / DELETE)
# =============================================================================