diff --git a/admin-compliance/app/api/sdk/v1/canonical/route.ts b/admin-compliance/app/api/sdk/v1/canonical/route.ts
index 5b1b608..06c87c7 100644
--- a/admin-compliance/app/api/sdk/v1/canonical/route.ts
+++ b/admin-compliance/app/api/sdk/v1/canonical/route.ts
@@ -26,7 +26,7 @@ export async function GET(request: NextRequest) {
case 'controls': {
const controlParams = new URLSearchParams()
- const passthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category',
+ const passthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', 'evidence_type',
'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates', 'sort', 'order', 'limit', 'offset']
for (const key of passthrough) {
const val = searchParams.get(key)
@@ -39,7 +39,7 @@ export async function GET(request: NextRequest) {
case 'controls-count': {
const countParams = new URLSearchParams()
- const countPassthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category',
+ const countPassthrough = ['severity', 'domain', 'release_state', 'verification_method', 'category', 'evidence_type',
'target_audience', 'source', 'search', 'control_type', 'exclude_duplicates']
for (const key of countPassthrough) {
const val = searchParams.get(key)
diff --git a/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx b/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx
index d4a047f..b0386c6 100644
--- a/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx
+++ b/admin-compliance/app/sdk/control-library/components/ControlDetail.tsx
@@ -8,10 +8,10 @@ import {
} from 'lucide-react'
import {
CanonicalControl, EFFORT_LABELS, BACKEND_URL,
- SeverityBadge, StateBadge, LicenseRuleBadge, VerificationMethodBadge, CategoryBadge, TargetAudienceBadge,
+ SeverityBadge, StateBadge, LicenseRuleBadge, VerificationMethodBadge, CategoryBadge, EvidenceTypeBadge, TargetAudienceBadge,
ObligationTypeBadge, GenerationStrategyBadge,
ExtractionMethodBadge, RegulationCountBadge,
- VERIFICATION_METHODS, CATEGORY_OPTIONS,
+ VERIFICATION_METHODS, CATEGORY_OPTIONS, EVIDENCE_TYPE_OPTIONS,
ObligationInfo, DocumentReference, MergedDuplicate, RegulationSummary,
} from './helpers'
@@ -185,6 +185,7 @@ export function ControlDetail({
+
diff --git a/admin-compliance/app/sdk/control-library/components/helpers.tsx b/admin-compliance/app/sdk/control-library/components/helpers.tsx
index cc460c3..dd50c58 100644
--- a/admin-compliance/app/sdk/control-library/components/helpers.tsx
+++ b/admin-compliance/app/sdk/control-library/components/helpers.tsx
@@ -44,6 +44,7 @@ export interface CanonicalControl {
customer_visible?: boolean
verification_method: string | null
category: string | null
+ evidence_type: string | null
target_audience: string | string[] | null
generation_metadata?: Record | null
generation_strategy?: string | null
@@ -102,6 +103,7 @@ export const EMPTY_CONTROL = {
tags: [] as string[],
verification_method: null as string | null,
category: null as string | null,
+ evidence_type: null as string | null,
target_audience: null as string | null,
}
@@ -145,6 +147,18 @@ export const CATEGORY_OPTIONS = [
{ value: 'identity', label: 'Identitaetsmanagement' },
]
+export const EVIDENCE_TYPE_CONFIG: Record = {
+ code: { bg: 'bg-sky-100 text-sky-700', label: 'Code' },
+ process: { bg: 'bg-amber-100 text-amber-700', label: 'Prozess' },
+ hybrid: { bg: 'bg-violet-100 text-violet-700', label: 'Hybrid' },
+}
+
+export const EVIDENCE_TYPE_OPTIONS = [
+ { value: 'code', label: 'Code — Technisch (Source Code, IaC, CI/CD)' },
+ { value: 'process', label: 'Prozess — Organisatorisch (Dokumente, Policies)' },
+ { value: 'hybrid', label: 'Hybrid — Code + Prozess' },
+]
+
export const TARGET_AUDIENCE_OPTIONS: Record = {
// Legacy English keys
enterprise: { bg: 'bg-cyan-100 text-cyan-700', label: 'Unternehmen' },
@@ -244,6 +258,13 @@ export function CategoryBadge({ category }: { category: string | null }) {
)
}
+export function EvidenceTypeBadge({ type }: { type: string | null }) {
+ if (!type) return null
+ const config = EVIDENCE_TYPE_CONFIG[type]
+ if (!config) return null
+ return {config.label}
+}
+
export function TargetAudienceBadge({ audience }: { audience: string | string[] | null }) {
if (!audience) return null
diff --git a/admin-compliance/app/sdk/control-library/page.tsx b/admin-compliance/app/sdk/control-library/page.tsx
index 07549c9..3fe398d 100644
--- a/admin-compliance/app/sdk/control-library/page.tsx
+++ b/admin-compliance/app/sdk/control-library/page.tsx
@@ -8,9 +8,9 @@ import {
} from 'lucide-react'
import {
CanonicalControl, Framework, BACKEND_URL, EMPTY_CONTROL,
- SeverityBadge, StateBadge, LicenseRuleBadge, VerificationMethodBadge, CategoryBadge, TargetAudienceBadge,
+ SeverityBadge, StateBadge, LicenseRuleBadge, VerificationMethodBadge, CategoryBadge, EvidenceTypeBadge, TargetAudienceBadge,
GenerationStrategyBadge, ObligationTypeBadge,
- VERIFICATION_METHODS, CATEGORY_OPTIONS, TARGET_AUDIENCE_OPTIONS,
+ VERIFICATION_METHODS, CATEGORY_OPTIONS, EVIDENCE_TYPE_OPTIONS, TARGET_AUDIENCE_OPTIONS,
} from './components/helpers'
import { ControlForm } from './components/ControlForm'
import { ControlDetail } from './components/ControlDetail'
@@ -51,6 +51,7 @@ export default function ControlLibraryPage() {
const [stateFilter, setStateFilter] = useState('')
const [verificationFilter, setVerificationFilter] = useState('')
const [categoryFilter, setCategoryFilter] = useState('')
+ const [evidenceTypeFilter, setEvidenceTypeFilter] = useState('')
const [audienceFilter, setAudienceFilter] = useState('')
const [sourceFilter, setSourceFilter] = useState('')
const [typeFilter, setTypeFilter] = useState('')
@@ -94,6 +95,7 @@ export default function ControlLibraryPage() {
if (stateFilter) p.set('release_state', stateFilter)
if (verificationFilter) p.set('verification_method', verificationFilter)
if (categoryFilter) p.set('category', categoryFilter)
+ if (evidenceTypeFilter) p.set('evidence_type', evidenceTypeFilter)
if (audienceFilter) p.set('target_audience', audienceFilter)
if (sourceFilter) p.set('source', sourceFilter)
if (typeFilter) p.set('control_type', typeFilter)
@@ -101,7 +103,7 @@ export default function ControlLibraryPage() {
if (debouncedSearch) p.set('search', debouncedSearch)
if (extra) for (const [k, v] of Object.entries(extra)) p.set(k, v)
return p.toString()
- }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch])
+ }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, evidenceTypeFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch])
// Load metadata (domains, sources — once + on refresh)
const loadMeta = useCallback(async () => {
@@ -169,7 +171,7 @@ export default function ControlLibraryPage() {
useEffect(() => { loadControls() }, [loadControls])
// Reset page when filters change
- useEffect(() => { setCurrentPage(1) }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch, sortBy])
+ useEffect(() => { setCurrentPage(1) }, [severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, evidenceTypeFilter, audienceFilter, sourceFilter, typeFilter, hideDuplicates, debouncedSearch, sortBy])
// Pagination
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE))
@@ -654,6 +656,16 @@ export default function ControlLibraryPage() {
))}
+