feat(licenses): Task #25 step 1 — SourceBadge in atomic-controls + correct LicenseRuleBadge labels

Per the SDK-Modul Attribution-Matrix (project_sdk_module_attribution_matrix.md),
the controls/atomic-controls listings render canonical_controls directly and are
the highest-audit-visibility integration point for Stufe 3.

Two changes:

1. atomic-controls/page.tsx: embed <SourceBadge controlUuid={ctrl.id} compact />
   next to the existing badge row in each control item. The badge fetches
   /api/compliance/licenses/source-info/{uuid} on first hover and reveals the
   source regulation, license type, and attribution text in a tooltip.

2. control-library/components/helpers.tsx: fix LicenseRuleBadge labels. The
   existing pill said "Free Use / Zitation / Reformuliert" — exactly the
   inverted understanding of the rules that Task #21 surfaced. Corrected to
   R1 (verbatim, Hoheitsrecht/PD), R2 (verbatim + attribution), R3 (identifier
   only). Added native title attribute for hover-explanation; the existing
   ControlListItem in control-library now shows the right semantics
   without any other code change.

Next module per matrix: VVT (Bundeslaender-Vorlagen) and DSFA.
This commit is contained in:
Benjamin Admin
2026-05-21 22:42:52 +02:00
parent 2eb7349577
commit d0274674a0
2 changed files with 18 additions and 5 deletions
@@ -13,6 +13,7 @@ import {
CATEGORY_OPTIONS,
} from '../control-library/components/helpers'
import { ControlDetail } from '../control-library/components/ControlDetail'
import { SourceBadge } from '@/components/sdk/SourceBadge'
// =============================================================================
// TYPES
@@ -310,6 +311,7 @@ export default function AtomicControlsPage() {
<TargetAudienceBadge audience={ctrl.target_audience} />
<GenerationStrategyBadge strategy={ctrl.generation_strategy} pipelineInfo={ctrl} />
<ObligationTypeBadge type={ctrl.generation_metadata?.obligation_type as string} />
<SourceBadge controlUuid={ctrl.id} compact />
</div>
<h3 className="text-sm font-medium text-gray-900 group-hover:text-violet-700">{ctrl.title}</h3>
<p className="text-xs text-gray-500 mt-1 line-clamp-2">{ctrl.objective}</p>
@@ -232,14 +232,25 @@ export function StateBadge({ state }: { state: string }) {
export function LicenseRuleBadge({ rule }: { rule: number | null | undefined }) {
if (!rule) return null
const config: Record<number, { bg: string; label: string }> = {
1: { bg: 'bg-green-100 text-green-700', label: 'Free Use' },
2: { bg: 'bg-blue-100 text-blue-700', label: 'Zitation' },
3: { bg: 'bg-amber-100 text-amber-700', label: 'Reformuliert' },
// Corrected labels per Task #21 LICENSE_RULES.md mapping:
// R1 = woertlich (Hoheitsrecht/Public Domain, no attribution required)
// R2 = woertlich + Attribution-Pflicht (CC-BY, OWASP, OECD, ENISA)
// R3 = nur Identifier zitieren (DIN/ANSI/IEC/DGUV/proprietary — pipeline drops full text)
const config: Record<number, { bg: string; label: string; title: string }> = {
1: { bg: 'bg-emerald-100 text-emerald-800', label: 'R1', title: 'Woertlich uebernehmbar (Hoheitsrecht/Public Domain)' },
2: { bg: 'bg-amber-100 text-amber-800', label: 'R2', title: 'Woertlich mit Attribution (CC-BY/OWASP/OECD/ENISA)' },
3: { bg: 'bg-slate-100 text-slate-700', label: 'R3', title: 'Nur Identifier-Verweis (DIN/ANSI/IEC/proprietaer)' },
}
const c = config[rule]
if (!c) return null
return <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${c.bg}`}>{c.label}</span>
return (
<span
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${c.bg}`}
title={c.title}
>
{c.label}
</span>
)
}
export function VerificationMethodBadge({ method }: { method: string | null }) {