feat: add verification method, categories, and dedup UI to control library
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 44s
CI/CD / test-python-backend-compliance (push) Successful in 40s
CI/CD / test-python-document-crawler (push) Successful in 22s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Successful in 4s

- Migration 047: verification_method + category columns, 17 category lookup table
- Backend: new filters, GET /categories, GET /controls/{id}/similar (embedding-based)
- Frontend: filter dropdowns, badges, dedup UI in ControlDetail with merge workflow
- ControlForm: verification method + category selects
- Provenance: verification methods, categories, master library strategy sections
- Fix UUID cast syntax in generator routes (::uuid -> CAST)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-14 07:55:22 +01:00
parent 8a05fcc2f0
commit b6e6ffaaee
10 changed files with 577 additions and 44 deletions

View File

@@ -7,7 +7,8 @@ import {
} from 'lucide-react'
import {
CanonicalControl, Framework, BACKEND_URL, EMPTY_CONTROL,
SeverityBadge, StateBadge, LicenseRuleBadge, getDomain,
SeverityBadge, StateBadge, LicenseRuleBadge, VerificationMethodBadge, CategoryBadge,
getDomain, VERIFICATION_METHODS, CATEGORY_OPTIONS,
} from './components/helpers'
import { ControlForm } from './components/ControlForm'
import { ControlDetail } from './components/ControlDetail'
@@ -29,6 +30,8 @@ export default function ControlLibraryPage() {
const [severityFilter, setSeverityFilter] = useState<string>('')
const [domainFilter, setDomainFilter] = useState<string>('')
const [stateFilter, setStateFilter] = useState<string>('')
const [verificationFilter, setVerificationFilter] = useState<string>('')
const [categoryFilter, setCategoryFilter] = useState<string>('')
// CRUD state
const [mode, setMode] = useState<'list' | 'detail' | 'create' | 'edit'>('list')
@@ -75,6 +78,8 @@ export default function ControlLibraryPage() {
if (severityFilter && c.severity !== severityFilter) return false
if (domainFilter && getDomain(c.control_id) !== domainFilter) return false
if (stateFilter && c.release_state !== stateFilter) return false
if (verificationFilter && c.verification_method !== verificationFilter) return false
if (categoryFilter && c.category !== categoryFilter) return false
if (searchQuery) {
const q = searchQuery.toLowerCase()
return (
@@ -86,7 +91,7 @@ export default function ControlLibraryPage() {
}
return true
})
}, [controls, severityFilter, domainFilter, stateFilter, searchQuery])
}, [controls, severityFilter, domainFilter, stateFilter, verificationFilter, categoryFilter, searchQuery])
// Review queue items
const reviewItems = useMemo(() => {
@@ -257,6 +262,7 @@ export default function ControlLibraryPage() {
onEdit={() => setMode('edit')}
onDelete={handleDelete}
onReview={handleReview}
onRefresh={loadData}
reviewMode={reviewMode}
reviewIndex={reviewIndex}
reviewTotal={reviewItems.length}
@@ -387,6 +393,26 @@ export default function ControlLibraryPage() {
<option value="too_close">Zu aehnlich</option>
<option value="duplicate">Duplikat</option>
</select>
<select
value={verificationFilter}
onChange={e => setVerificationFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Nachweismethoden</option>
{Object.entries(VERIFICATION_METHODS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
</select>
<select
value={categoryFilter}
onChange={e => setCategoryFilter(e.target.value)}
className="text-sm border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Alle Kategorien</option>
{CATEGORY_OPTIONS.map(c => (
<option key={c.value} value={c.value}>{c.label}</option>
))}
</select>
</div>
{/* Processing Stats */}
@@ -433,6 +459,8 @@ export default function ControlLibraryPage() {
<SeverityBadge severity={ctrl.severity} />
<StateBadge state={ctrl.release_state} />
<LicenseRuleBadge rule={ctrl.license_rule} />
<VerificationMethodBadge method={ctrl.verification_method} />
<CategoryBadge category={ctrl.category} />
{ctrl.risk_score !== null && (
<span className="text-xs text-gray-400">Score: {ctrl.risk_score}</span>
)}