Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
{
"name": "@breakpilot/compliance-sdk-cli",
"version": "0.0.1",
"description": "BreakPilot Compliance SDK - CLI Tool",
"bin": {
"breakpilot": "./dist/cli.js",
"bp": "./dist/cli.js"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@breakpilot/compliance-sdk-core": "workspace:*",
"@breakpilot/compliance-sdk-types": "workspace:*",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"inquirer": "^9.2.0",
"ora": "^8.0.0"
},
"devDependencies": {
"@types/inquirer": "^9.0.0",
"tsup": "^8.0.0",
"typescript": "^5.3.0"
},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/breakpilot/compliance-sdk.git",
"directory": "packages/cli"
},
"keywords": [
"compliance",
"gdpr",
"dsgvo",
"cli"
]
}

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env node
/**
* BreakPilot Compliance SDK CLI
*
* Commands:
* - init: Initialize a new compliance project
* - deploy: Deploy to hardware (Mac Mini/Mac Studio)
* - scan: Run security scans
* - export: Export compliance reports
* - status: Check compliance status
*/
import { Command } from 'commander'
import { initCommand } from './commands/init'
import { deployCommand } from './commands/deploy'
import { scanCommand } from './commands/scan'
import { exportCommand } from './commands/export'
import { statusCommand } from './commands/status'
const program = new Command()
program
.name('breakpilot')
.description('BreakPilot Compliance SDK CLI')
.version('0.0.1')
// Register commands
program.addCommand(initCommand)
program.addCommand(deployCommand)
program.addCommand(scanCommand)
program.addCommand(exportCommand)
program.addCommand(statusCommand)
program.parse()

View File

@@ -0,0 +1,217 @@
/**
* Deploy command - Deploy to hardware (Mac Mini/Mac Studio)
*/
import { Command } from 'commander'
import * as fs from 'fs'
import * as path from 'path'
interface DeployOptions {
target?: string
config?: string
dryRun?: boolean
}
export const deployCommand = new Command('deploy')
.description('Deploy compliance services to hardware')
.option('-t, --target <target>', 'Deployment target (mac-mini, mac-studio, cloud)', 'mac-mini')
.option('-c, --config <path>', 'Path to config file', './breakpilot.config.json')
.option('--dry-run', 'Show what would be deployed without actually deploying', false)
.action(async (options: DeployOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
const inquirer = (await import('inquirer')).default
console.log(chalk.bold.blue('\n🚀 BreakPilot Compliance SDK - Deployment\n'))
// Check for config file
const configPath = path.resolve(options.config || './breakpilot.config.json')
if (!fs.existsSync(configPath)) {
console.log(chalk.yellow('⚠️ No config file found. Run `breakpilot init` first.'))
process.exit(1)
}
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
// Prompt for deployment details
const answers = await inquirer.prompt([
{
type: 'list',
name: 'target',
message: 'Select deployment target:',
choices: [
{ name: 'Mac Mini M4 Pro (64GB RAM, Qwen 2.5 32B)', value: 'mac-mini' },
{ name: 'Mac Studio M2 Ultra (512GB RAM, Qwen 2.5 40B)', value: 'mac-studio' },
{ name: 'Cloud (BreakPilot Servers)', value: 'cloud' },
],
default: options.target,
},
{
type: 'input',
name: 'hostname',
message: 'Target hostname/IP:',
when: (answers) => answers.target !== 'cloud',
default: 'localhost',
},
{
type: 'input',
name: 'sshUser',
message: 'SSH username:',
when: (answers) => answers.target !== 'cloud',
default: process.env.USER || 'admin',
},
{
type: 'confirm',
name: 'installLLM',
message: 'Install/update LLM model?',
when: (answers) => answers.target !== 'cloud',
default: true,
},
])
if (options.dryRun) {
console.log(chalk.yellow('\n📋 Dry run - no changes will be made\n'))
}
const spinner = ora('Preparing deployment...').start()
try {
// Generate docker-compose.yml
const dockerCompose = generateDockerCompose(answers.target, config)
if (options.dryRun) {
spinner.info('Would generate docker-compose.yml:')
console.log(chalk.gray(dockerCompose))
return
}
// For actual deployment, we'd:
// 1. SSH to target machine
// 2. Copy docker-compose.yml
// 3. Run docker-compose up -d
// 4. Install LLM if requested
spinner.text = 'Connecting to target...'
await sleep(1000) // Simulated
spinner.text = 'Deploying services...'
await sleep(2000) // Simulated
if (answers.installLLM) {
spinner.text = 'Installing LLM model (this may take a while)...'
await sleep(2000) // Simulated
}
spinner.succeed('Deployment complete!')
console.log(chalk.green('\n✅ Services deployed successfully'))
console.log(chalk.gray('\nDeployed services:'))
console.log(chalk.gray(' - API Gateway (port 443)'))
console.log(chalk.gray(' - Compliance Engine'))
console.log(chalk.gray(' - RAG Service'))
console.log(chalk.gray(' - Security Scanner'))
console.log(chalk.gray(' - PostgreSQL'))
console.log(chalk.gray(' - Qdrant'))
console.log(chalk.gray(' - MinIO'))
if (answers.installLLM) {
const model = answers.target === 'mac-studio' ? 'qwen2.5:40b' : 'qwen2.5:32b'
console.log(chalk.gray(` - Ollama (${model})`))
}
console.log(chalk.blue('\n🔗 Access your services at:'))
console.log(chalk.white(` https://${answers.hostname || 'localhost'}/api/v1`))
} catch (error) {
spinner.fail('Deployment failed')
console.error(chalk.red('Error:'), error)
process.exit(1)
}
})
function generateDockerCompose(target: string, config: Record<string, unknown>): string {
const llmModel = target === 'mac-studio' ? 'qwen2.5:40b' : 'qwen2.5:32b'
return `# Generated by BreakPilot CLI
# Target: ${target}
version: '3.8'
services:
api-gateway:
image: ghcr.io/breakpilot/compliance-sdk-gateway:latest
ports:
- "443:443"
- "80:80"
environment:
- DATABASE_URL=postgres://breakpilot:breakpilot@postgres:5432/compliance
- QDRANT_URL=http://qdrant:6333
- OLLAMA_URL=http://host.docker.internal:11434
- MINIO_URL=http://minio:9000
depends_on:
- postgres
- qdrant
- minio
restart: unless-stopped
compliance-engine:
image: ghcr.io/breakpilot/compliance-engine:latest
environment:
- DATABASE_URL=postgres://breakpilot:breakpilot@postgres:5432/compliance
depends_on:
- postgres
restart: unless-stopped
rag-service:
image: ghcr.io/breakpilot/rag-service:latest
environment:
- QDRANT_URL=http://qdrant:6333
- OLLAMA_URL=http://host.docker.internal:11434
- EMBEDDING_MODEL=bge-m3
depends_on:
- qdrant
restart: unless-stopped
security-scanner:
image: ghcr.io/breakpilot/security-scanner:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=breakpilot
- POSTGRES_PASSWORD=breakpilot
- POSTGRES_DB=compliance
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
qdrant:
image: qdrant/qdrant:v1.12.1
volumes:
- qdrant_data:/qdrant/storage
restart: unless-stopped
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
environment:
- MINIO_ROOT_USER=breakpilot
- MINIO_ROOT_PASSWORD=breakpilot123
volumes:
- minio_data:/data
restart: unless-stopped
volumes:
postgres_data:
qdrant_data:
minio_data:
# Note: Ollama runs on the host system
# Install with: ollama pull ${llmModel}
`
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -0,0 +1,222 @@
/**
* Export command - Export compliance reports
*/
import { Command } from 'commander'
import * as fs from 'fs'
import * as path from 'path'
interface ExportOptions {
format?: string
output?: string
type?: string
}
export const exportCommand = new Command('export')
.description('Export compliance reports and documentation')
.option('-f, --format <format>', 'Export format (pdf, docx, json, csv)', 'pdf')
.option('-o, --output <path>', 'Output file path')
.option('-t, --type <type>', 'Report type (full, summary, vvt, tom, dsfa, controls, risks)', 'summary')
.action(async (options: ExportOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
const inquirer = (await import('inquirer')).default
console.log(chalk.bold.blue('\n📄 BreakPilot Compliance Export\n'))
// Prompt for export details
const answers = await inquirer.prompt([
{
type: 'list',
name: 'type',
message: 'Select report type:',
choices: [
{ name: 'Full Compliance Report', value: 'full' },
{ name: 'Executive Summary', value: 'summary' },
{ name: 'Verarbeitungsverzeichnis (VVT)', value: 'vvt' },
{ name: 'Technische & Organisatorische Maßnahmen (TOM)', value: 'tom' },
{ name: 'Datenschutz-Folgenabschätzung (DSFA)', value: 'dsfa' },
{ name: 'Controls Overview', value: 'controls' },
{ name: 'Risk Register', value: 'risks' },
{ name: 'SBOM (Software Bill of Materials)', value: 'sbom' },
],
default: options.type,
},
{
type: 'list',
name: 'format',
message: 'Select export format:',
choices: [
{ name: 'PDF', value: 'pdf' },
{ name: 'Word Document (DOCX)', value: 'docx' },
{ name: 'JSON', value: 'json' },
{ name: 'CSV', value: 'csv' },
{ name: 'CycloneDX (SBOM only)', value: 'cyclonedx' },
{ name: 'SPDX (SBOM only)', value: 'spdx' },
],
default: options.format,
},
{
type: 'input',
name: 'output',
message: 'Output file path:',
default: (answers: { type: string; format: string }) =>
`compliance-${answers.type}-${new Date().toISOString().split('T')[0]}.${answers.format}`,
},
])
const spinner = ora('Generating report...').start()
try {
// In a real implementation, this would:
// 1. Connect to the compliance API
// 2. Fetch all relevant data
// 3. Generate the report in the requested format
spinner.text = 'Fetching compliance data...'
await sleep(1000)
spinner.text = 'Generating document...'
await sleep(1500)
const outputPath = path.resolve(answers.output)
// Generate mock output
const content = generateMockReport(answers.type, answers.format)
fs.writeFileSync(outputPath, content)
spinner.succeed('Report generated successfully!')
console.log(chalk.green('\n✅ Export complete'))
console.log(chalk.gray(` File: ${outputPath}`))
console.log(chalk.gray(` Type: ${answers.type}`))
console.log(chalk.gray(` Format: ${answers.format}`))
// Show report preview for JSON
if (answers.format === 'json') {
console.log(chalk.bold('\n📋 Preview:\n'))
const preview = JSON.parse(content)
console.log(chalk.gray(JSON.stringify(preview, null, 2).substring(0, 500) + '...'))
}
} catch (error) {
spinner.fail('Export failed')
console.error(chalk.red('Error:'), error)
process.exit(1)
}
})
function generateMockReport(type: string, format: string): string {
const reportData = {
generatedAt: new Date().toISOString(),
reportType: type,
format: format,
organization: 'Example Organization',
complianceScore: 78,
summary: {
totalControls: 44,
implementedControls: 35,
partialControls: 6,
openControls: 3,
totalRisks: 12,
criticalRisks: 1,
highRisks: 2,
regulations: ['DSGVO', 'NIS2', 'AI Act'],
},
sections: getSectionsForType(type),
}
if (format === 'json' || format === 'cyclonedx' || format === 'spdx') {
return JSON.stringify(reportData, null, 2)
}
// For PDF/DOCX, we'd use a proper document generation library
// For now, return a placeholder
return `[${format.toUpperCase()} Report - ${type}]\n\n${JSON.stringify(reportData, null, 2)}`
}
function getSectionsForType(type: string): Record<string, unknown>[] {
switch (type) {
case 'vvt':
return [
{
id: 'vvt-1',
name: 'Kundenmanagement',
purpose: 'Verwaltung von Kundenbeziehungen',
legalBasis: 'Art. 6 Abs. 1 lit. b DSGVO',
dataCategories: ['Kontaktdaten', 'Vertragsdetails', 'Kommunikationshistorie'],
retentionPeriod: '10 Jahre nach Vertragsende',
},
{
id: 'vvt-2',
name: 'Personalverwaltung',
purpose: 'Verwaltung von Mitarbeiterdaten',
legalBasis: 'Art. 6 Abs. 1 lit. b, c DSGVO',
dataCategories: ['Personaldaten', 'Gehaltsdaten', 'Leistungsdaten'],
retentionPeriod: '10 Jahre nach Beendigung',
},
]
case 'tom':
return [
{
category: 'Zutrittskontrolle',
measures: [
'Zutrittskontrollsystem mit Chipkarten',
'Videoüberwachung der Eingänge',
'Besucherregistrierung',
],
},
{
category: 'Zugangskontrolle',
measures: [
'Passwort-Policy (min. 12 Zeichen)',
'Multi-Faktor-Authentifizierung',
'Automatische Sperrung nach 5 Fehlversuchen',
],
},
]
case 'controls':
return [
{
id: 'ctrl-1',
domain: 'ACCESS_CONTROL',
title: 'Benutzerauthentifizierung',
status: 'IMPLEMENTED',
evidence: ['auth-policy.pdf', 'mfa-config.png'],
},
{
id: 'ctrl-2',
domain: 'DATA_PROTECTION',
title: 'Datenverschlüsselung',
status: 'IMPLEMENTED',
evidence: ['encryption-certificate.pdf'],
},
]
case 'risks':
return [
{
id: 'risk-1',
title: 'Datenverlust durch Cyberangriff',
likelihood: 3,
impact: 5,
severity: 'HIGH',
mitigation: 'Backup-Strategie, Incident Response Plan',
status: 'MITIGATED',
},
{
id: 'risk-2',
title: 'DSGVO-Bußgeld wegen unzureichender Dokumentation',
likelihood: 2,
impact: 4,
severity: 'MEDIUM',
mitigation: 'Regelmäßige Dokumentationsaudits',
status: 'MONITORING',
},
]
default:
return []
}
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -0,0 +1,221 @@
/**
* Init command - Initialize a new compliance project
*/
import { Command } from 'commander'
import * as fs from 'fs'
import * as path from 'path'
interface InitOptions {
template?: string
force?: boolean
}
export const initCommand = new Command('init')
.description('Initialize a new compliance project')
.argument('[directory]', 'Directory to initialize', '.')
.option('-t, --template <template>', 'Project template (react, vue, vanilla)', 'react')
.option('-f, --force', 'Overwrite existing files', false)
.action(async (directory: string, options: InitOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
const inquirer = (await import('inquirer')).default
console.log(chalk.bold.blue('\n🚀 BreakPilot Compliance SDK - Project Setup\n'))
// Prompt for configuration
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'Project name:',
default: path.basename(path.resolve(directory)),
},
{
type: 'list',
name: 'template',
message: 'Select a template:',
choices: [
{ name: 'React (Recommended)', value: 'react' },
{ name: 'Vue 3', value: 'vue' },
{ name: 'Vanilla JS', value: 'vanilla' },
],
default: options.template,
},
{
type: 'input',
name: 'apiEndpoint',
message: 'API Endpoint:',
default: 'https://compliance.breakpilot.app/api/v1',
},
{
type: 'confirm',
name: 'includeComponents',
message: 'Include pre-built UI components?',
default: true,
},
])
const spinner = ora('Setting up project...').start()
try {
const targetDir = path.resolve(directory)
// Create directory if it doesn't exist
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
// Create config file
const configPath = path.join(targetDir, 'breakpilot.config.json')
const config = {
projectName: answers.projectName,
template: answers.template,
apiEndpoint: answers.apiEndpoint,
version: '0.0.1',
features: {
dsgvo: true,
compliance: true,
rag: true,
security: true,
},
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
// Create .env.example
const envExamplePath = path.join(targetDir, '.env.example')
const envContent = `# BreakPilot Compliance SDK Configuration
BREAKPILOT_API_ENDPOINT=${answers.apiEndpoint}
BREAKPILOT_API_KEY=pk_live_xxx
BREAKPILOT_TENANT_ID=your_tenant_id
`
fs.writeFileSync(envExamplePath, envContent)
// Create example usage file based on template
const examplePath = path.join(targetDir, `compliance-example.${getExtension(answers.template)}`)
fs.writeFileSync(examplePath, getExampleCode(answers.template, answers.apiEndpoint))
spinner.succeed('Project initialized successfully!')
console.log(chalk.green('\n✅ Project created at:'), chalk.white(targetDir))
console.log(chalk.gray('\nNext steps:'))
console.log(chalk.gray(' 1. Copy .env.example to .env and fill in your API key'))
console.log(chalk.gray(` 2. Install the SDK: npm install @breakpilot/compliance-sdk-${answers.template}`))
console.log(chalk.gray(' 3. Import and use the SDK in your application'))
console.log(chalk.gray('\nDocumentation: https://docs.breakpilot.app/sdk'))
} catch (error) {
spinner.fail('Failed to initialize project')
console.error(chalk.red('Error:'), error)
process.exit(1)
}
})
function getExtension(template: string): string {
switch (template) {
case 'react':
return 'tsx'
case 'vue':
return 'vue'
default:
return 'js'
}
}
function getExampleCode(template: string, apiEndpoint: string): string {
switch (template) {
case 'react':
return `// Example React integration
import {
ComplianceProvider,
ConsentBanner,
DSRPortal,
useCompliance
} from '@breakpilot/compliance-sdk-react';
function App() {
return (
<ComplianceProvider
apiEndpoint="${apiEndpoint}"
apiKey={process.env.BREAKPILOT_API_KEY}
tenantId={process.env.BREAKPILOT_TENANT_ID}
>
<ConsentBanner position="BOTTOM" theme="LIGHT" />
<MyApp />
</ComplianceProvider>
);
}
function MyApp() {
const { state, compliance, rag } = useCompliance();
const askQuestion = async () => {
const answer = await rag.ask('Was ist bei Art. 9 DSGVO zu beachten?');
console.log(answer);
};
return (
<div>
<h1>Compliance Score: {compliance.calculateComplianceScore().overall}%</h1>
<button onClick={askQuestion}>Frage stellen</button>
</div>
);
}
export default App;
`
case 'vue':
return `<!-- Example Vue 3 integration -->
<script setup lang="ts">
import { useCompliance, useRAG } from '@breakpilot/compliance-sdk-vue';
const { state, complianceScore } = useCompliance();
const { ask, chatHistory, isLoading } = useRAG();
const askQuestion = async () => {
await ask('Was ist bei Art. 9 DSGVO zu beachten?');
};
</script>
<template>
<div>
<h1>Compliance Score: {{ complianceScore }}%</h1>
<button @click="askQuestion" :disabled="isLoading">
{{ isLoading ? 'Lädt...' : 'Frage stellen' }}
</button>
<div v-for="msg in chatHistory" :key="msg.id">
<strong>{{ msg.role }}:</strong> {{ msg.content }}
</div>
</div>
</template>
`
default:
return `// Example Vanilla JS integration
import { BreakPilotSDK } from '@breakpilot/compliance-sdk-vanilla';
BreakPilotSDK.init({
apiEndpoint: '${apiEndpoint}',
apiKey: 'pk_live_xxx',
autoInjectBanner: true,
bannerConfig: {
position: 'BOTTOM',
theme: 'LIGHT',
language: 'de'
},
onConsentChange: (consents) => {
console.log('Consents updated:', consents);
if (consents.ANALYTICS) {
// Load analytics
}
},
onReady: () => {
console.log('SDK ready!');
}
});
// Or use Web Components:
// <breakpilot-consent-banner api-key="pk_live_xxx" theme="light" position="bottom">
// </breakpilot-consent-banner>
`
}
}

View File

@@ -0,0 +1,228 @@
/**
* Scan command - Run security scans
*/
import { Command } from 'commander'
import * as path from 'path'
interface ScanOptions {
tools?: string
output?: string
format?: string
severity?: string
}
export const scanCommand = new Command('scan')
.description('Run security scans on your codebase')
.argument('[path]', 'Path to scan', '.')
.option('-t, --tools <tools>', 'Comma-separated list of tools (gitleaks,semgrep,bandit,trivy,grype,syft)', 'all')
.option('-o, --output <path>', 'Output file path')
.option('-f, --format <format>', 'Output format (json, sarif, table)', 'table')
.option('-s, --severity <level>', 'Minimum severity to report (low, medium, high, critical)', 'low')
.action(async (scanPath: string, options: ScanOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
console.log(chalk.bold.blue('\n🔒 BreakPilot Security Scanner\n'))
const targetPath = path.resolve(scanPath)
console.log(chalk.gray(`Scanning: ${targetPath}\n`))
const tools = options.tools === 'all'
? ['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype', 'syft']
: options.tools!.split(',').map(t => t.trim())
const results: ScanResult[] = []
for (const tool of tools) {
const spinner = ora(`Running ${tool}...`).start()
try {
// Simulate running the tool
await sleep(1500)
const toolResult = simulateToolResult(tool)
results.push(toolResult)
if (toolResult.findings.length > 0) {
spinner.warn(`${tool}: ${toolResult.findings.length} findings`)
} else {
spinner.succeed(`${tool}: No issues found`)
}
} catch (error) {
spinner.fail(`${tool}: Failed`)
}
}
// Summary
console.log(chalk.bold('\n📊 Scan Summary\n'))
const allFindings = results.flatMap(r => r.findings)
const bySeverity = {
critical: allFindings.filter(f => f.severity === 'CRITICAL'),
high: allFindings.filter(f => f.severity === 'HIGH'),
medium: allFindings.filter(f => f.severity === 'MEDIUM'),
low: allFindings.filter(f => f.severity === 'LOW'),
}
if (bySeverity.critical.length > 0) {
console.log(chalk.red(` 🔴 Critical: ${bySeverity.critical.length}`))
}
if (bySeverity.high.length > 0) {
console.log(chalk.red(` 🟠 High: ${bySeverity.high.length}`))
}
if (bySeverity.medium.length > 0) {
console.log(chalk.yellow(` 🟡 Medium: ${bySeverity.medium.length}`))
}
if (bySeverity.low.length > 0) {
console.log(chalk.gray(` 🟢 Low: ${bySeverity.low.length}`))
}
if (allFindings.length === 0) {
console.log(chalk.green(' ✅ No security issues found!'))
} else {
console.log(chalk.gray(`\n Total: ${allFindings.length} findings`))
// Show top findings
if (options.format === 'table') {
console.log(chalk.bold('\n📋 Top Findings\n'))
const topFindings = allFindings
.sort((a, b) => severityOrder(b.severity) - severityOrder(a.severity))
.slice(0, 10)
for (const finding of topFindings) {
const severityColor = getSeverityColor(finding.severity)
console.log(
` ${severityColor(finding.severity.padEnd(8))} ` +
`${chalk.gray(finding.tool.padEnd(10))} ` +
`${finding.title}`
)
if (finding.file) {
console.log(chalk.gray(` └─ ${finding.file}:${finding.line || '?'}`))
}
}
if (allFindings.length > 10) {
console.log(chalk.gray(`\n ... and ${allFindings.length - 10} more findings`))
}
}
}
// Write output if requested
if (options.output) {
const fs = await import('fs')
const output = {
scanDate: new Date().toISOString(),
targetPath,
tools,
summary: {
total: allFindings.length,
critical: bySeverity.critical.length,
high: bySeverity.high.length,
medium: bySeverity.medium.length,
low: bySeverity.low.length,
},
findings: allFindings,
}
fs.writeFileSync(options.output, JSON.stringify(output, null, 2))
console.log(chalk.gray(`\nResults written to: ${options.output}`))
}
// Exit with error if critical findings
if (bySeverity.critical.length > 0 || bySeverity.high.length > 0) {
process.exit(1)
}
})
interface Finding {
id: string
tool: string
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'
title: string
description?: string
file?: string
line?: number
recommendation?: string
}
interface ScanResult {
tool: string
findings: Finding[]
}
function simulateToolResult(tool: string): ScanResult {
// Simulate some findings for demonstration
const findings: Finding[] = []
switch (tool) {
case 'gitleaks':
// Secrets detection - usually finds nothing in clean repos
break
case 'semgrep':
findings.push({
id: 'semgrep-1',
tool: 'semgrep',
severity: 'MEDIUM',
title: 'Potential SQL injection',
description: 'User input used in SQL query without parameterization',
file: 'src/db/queries.ts',
line: 42,
recommendation: 'Use parameterized queries instead of string concatenation',
})
break
case 'bandit':
// Python security - skip if not a Python project
break
case 'trivy':
findings.push({
id: 'trivy-1',
tool: 'trivy',
severity: 'HIGH',
title: 'CVE-2024-1234 in lodash@4.17.20',
description: 'Prototype pollution vulnerability',
recommendation: 'Upgrade to lodash@4.17.21 or higher',
})
break
case 'grype':
findings.push({
id: 'grype-1',
tool: 'grype',
severity: 'LOW',
title: 'Outdated dependency: axios@0.21.0',
recommendation: 'Update to latest version',
})
break
case 'syft':
// SBOM generation - no findings, just metadata
break
}
return { tool, findings }
}
function severityOrder(severity: string): number {
switch (severity) {
case 'CRITICAL': return 4
case 'HIGH': return 3
case 'MEDIUM': return 2
case 'LOW': return 1
default: return 0
}
}
function getSeverityColor(severity: string): (text: string) => string {
const chalk = require('chalk')
switch (severity) {
case 'CRITICAL': return chalk.red.bold
case 'HIGH': return chalk.red
case 'MEDIUM': return chalk.yellow
case 'LOW': return chalk.gray
default: return chalk.white
}
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -0,0 +1,161 @@
/**
* Status command - Check compliance status
*/
import { Command } from 'commander'
interface StatusOptions {
json?: boolean
verbose?: boolean
}
export const statusCommand = new Command('status')
.description('Check current compliance status')
.option('-j, --json', 'Output as JSON', false)
.option('-v, --verbose', 'Show detailed status', false)
.action(async (options: StatusOptions) => {
const chalk = (await import('chalk')).default
const ora = (await import('ora')).default
if (!options.json) {
console.log(chalk.bold.blue('\n📊 BreakPilot Compliance Status\n'))
}
const spinner = options.json ? null : ora('Fetching status...').start()
try {
// Simulate fetching status from API
await sleep(1000)
const status = {
lastUpdated: new Date().toISOString(),
overallScore: 78,
trend: 'UP',
regulations: {
DSGVO: { score: 85, status: 'COMPLIANT' },
NIS2: { score: 72, status: 'PARTIAL' },
'AI Act': { score: 65, status: 'IN_PROGRESS' },
},
controls: {
total: 44,
implemented: 35,
partial: 6,
notImplemented: 3,
},
risks: {
total: 12,
critical: 1,
high: 2,
medium: 5,
low: 4,
},
evidence: {
total: 28,
valid: 24,
expiring: 3,
expired: 1,
},
dsrRequests: {
pending: 2,
inProgress: 1,
completed: 15,
},
nextActions: [
{
priority: 'HIGH',
action: 'Address critical risk: Data breach potential',
dueDate: '2024-02-15',
},
{
priority: 'MEDIUM',
action: 'Update expired evidence: Security audit report',
dueDate: '2024-02-20',
},
{
priority: 'MEDIUM',
action: 'Complete NIS2 gap assessment',
dueDate: '2024-03-01',
},
],
}
if (options.json) {
console.log(JSON.stringify(status, null, 2))
return
}
spinner?.succeed('Status retrieved')
// Overall Score
const scoreColor = status.overallScore >= 80 ? chalk.green :
status.overallScore >= 60 ? chalk.yellow : chalk.red
const trendIcon = status.trend === 'UP' ? '↑' :
status.trend === 'DOWN' ? '↓' : '→'
console.log(chalk.bold('\n🎯 Overall Compliance Score'))
console.log(` ${scoreColor.bold(status.overallScore + '%')} ${chalk.gray(trendIcon)}`)
// Regulations
console.log(chalk.bold('\n📜 Regulations'))
Object.entries(status.regulations).forEach(([reg, data]) => {
const color = data.score >= 80 ? chalk.green :
data.score >= 60 ? chalk.yellow : chalk.red
const statusIcon = data.status === 'COMPLIANT' ? '✓' :
data.status === 'PARTIAL' ? '◐' : '○'
console.log(` ${statusIcon} ${reg.padEnd(12)} ${color(data.score + '%')} ${chalk.gray(data.status)}`)
})
// Controls
console.log(chalk.bold('\n🔧 Controls'))
console.log(` ${chalk.green('●')} Implemented: ${status.controls.implemented}`)
console.log(` ${chalk.yellow('●')} Partial: ${status.controls.partial}`)
console.log(` ${chalk.red('●')} Not Implemented: ${status.controls.notImplemented}`)
console.log(chalk.gray(` Total: ${status.controls.total}`))
// Risks
console.log(chalk.bold('\n⚠ Risks'))
if (status.risks.critical > 0) {
console.log(` ${chalk.red.bold('🔴')} Critical: ${status.risks.critical}`)
}
if (status.risks.high > 0) {
console.log(` ${chalk.red('🟠')} High: ${status.risks.high}`)
}
console.log(` ${chalk.yellow('🟡')} Medium: ${status.risks.medium}`)
console.log(` ${chalk.gray('🟢')} Low: ${status.risks.low}`)
// Evidence
if (options.verbose) {
console.log(chalk.bold('\n📎 Evidence'))
console.log(` ${chalk.green('●')} Valid: ${status.evidence.valid}`)
console.log(` ${chalk.yellow('●')} Expiring: ${status.evidence.expiring}`)
console.log(` ${chalk.red('●')} Expired: ${status.evidence.expired}`)
}
// DSR Requests
if (options.verbose) {
console.log(chalk.bold('\n📬 DSR Requests'))
console.log(` Pending: ${status.dsrRequests.pending}`)
console.log(` In Progress: ${status.dsrRequests.inProgress}`)
console.log(` Completed: ${status.dsrRequests.completed}`)
}
// Next Actions
console.log(chalk.bold('\n📋 Next Actions'))
status.nextActions.forEach(action => {
const priorityColor = action.priority === 'HIGH' ? chalk.red :
action.priority === 'MEDIUM' ? chalk.yellow : chalk.gray
console.log(` ${priorityColor('●')} ${action.action}`)
console.log(chalk.gray(` Due: ${action.dueDate}`))
})
console.log('')
} catch (error) {
spinner?.fail('Failed to fetch status')
console.error(chalk.red('Error:'), error)
process.exit(1)
}
})
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -0,0 +1,18 @@
/**
* @breakpilot/compliance-sdk-cli
*
* CLI tool for BreakPilot Compliance SDK
*
* Commands:
* - init: Initialize a new compliance project
* - deploy: Deploy to hardware (Mac Mini/Mac Studio)
* - scan: Run security scans
* - export: Export compliance reports
* - status: Check compliance status
*/
export { initCommand } from './commands/init'
export { deployCommand } from './commands/deploy'
export { scanCommand } from './commands/scan'
export { exportCommand } from './commands/export'
export { statusCommand } from './commands/status'

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: {
index: 'src/index.ts',
cli: 'src/cli.ts',
},
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
shims: true,
banner: {
js: '#!/usr/bin/env node',
},
})

View File

@@ -0,0 +1,52 @@
{
"name": "@breakpilot/compliance-sdk-core",
"version": "1.0.0",
"description": "Core functionality for BreakPilot Compliance SDK",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./client": {
"import": "./dist/client.mjs",
"require": "./dist/client.js",
"types": "./dist/client.d.ts"
},
"./state": {
"import": "./dist/state.mjs",
"require": "./dist/state.js",
"types": "./dist/state.d.ts"
},
"./sync": {
"import": "./dist/sync.mjs",
"require": "./dist/sync.js",
"types": "./dist/sync.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist"
},
"dependencies": {
"@breakpilot/compliance-sdk-types": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.11.0",
"tsup": "^8.0.1",
"typescript": "^5.3.3",
"vitest": "^1.2.0"
},
"peerDependencies": {},
"publishConfig": {
"access": "restricted"
}
}

View File

@@ -0,0 +1,295 @@
/**
* Authentication Provider
*
* Manages authentication state and token lifecycle
*/
import type { AuthTokenResponse } from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from './client'
// =============================================================================
// TYPES
// =============================================================================
export interface AuthProviderOptions {
client: ComplianceClient
clientId: string
clientSecret?: string
storage?: Storage
onAuthStateChange?: (state: AuthState) => void
}
export interface AuthState {
isAuthenticated: boolean
accessToken: string | null
refreshToken: string | null
expiresAt: Date | null
user?: {
id: string
email: string
name: string
role: string
}
}
// =============================================================================
// CONSTANTS
// =============================================================================
const STORAGE_KEY = 'breakpilot-sdk-auth'
const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000 // 5 minutes before expiry
// =============================================================================
// AUTH PROVIDER
// =============================================================================
export class AuthProvider {
private client: ComplianceClient
private clientId: string
private clientSecret?: string
private storage: Storage | null
private state: AuthState
private refreshTimeout: ReturnType<typeof setTimeout> | null = null
private onAuthStateChange?: (state: AuthState) => void
constructor(options: AuthProviderOptions) {
this.client = options.client
this.clientId = options.clientId
this.clientSecret = options.clientSecret
this.storage = options.storage ?? (typeof localStorage !== 'undefined' ? localStorage : null)
this.onAuthStateChange = options.onAuthStateChange
this.state = {
isAuthenticated: false,
accessToken: null,
refreshToken: null,
expiresAt: null,
}
this.loadFromStorage()
}
// ---------------------------------------------------------------------------
// Private Methods
// ---------------------------------------------------------------------------
private loadFromStorage(): void {
if (!this.storage) return
try {
const stored = this.storage.getItem(STORAGE_KEY)
if (stored) {
const data = JSON.parse(stored)
if (data.expiresAt) {
data.expiresAt = new Date(data.expiresAt)
}
// Check if token is still valid
if (data.expiresAt && data.expiresAt > new Date()) {
this.state = data
this.client.setAccessToken(data.accessToken)
this.scheduleTokenRefresh()
this.notifyStateChange()
} else if (data.refreshToken) {
// Try to refresh
this.refreshToken().catch(() => {
this.clearAuth()
})
}
}
} catch (error) {
console.error('Failed to load auth from storage:', error)
}
}
private saveToStorage(): void {
if (!this.storage) return
try {
this.storage.setItem(STORAGE_KEY, JSON.stringify(this.state))
} catch (error) {
console.error('Failed to save auth to storage:', error)
}
}
private clearStorage(): void {
if (!this.storage) return
try {
this.storage.removeItem(STORAGE_KEY)
} catch (error) {
console.error('Failed to clear auth from storage:', error)
}
}
private updateState(tokenResponse: AuthTokenResponse): void {
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000)
this.state = {
isAuthenticated: true,
accessToken: tokenResponse.accessToken,
refreshToken: tokenResponse.refreshToken ?? this.state.refreshToken,
expiresAt,
}
this.client.setAccessToken(tokenResponse.accessToken)
this.saveToStorage()
this.scheduleTokenRefresh()
this.notifyStateChange()
}
private scheduleTokenRefresh(): void {
if (this.refreshTimeout) {
clearTimeout(this.refreshTimeout)
}
if (!this.state.expiresAt || !this.state.refreshToken) return
const timeUntilRefresh = this.state.expiresAt.getTime() - Date.now() - TOKEN_REFRESH_BUFFER
if (timeUntilRefresh > 0) {
this.refreshTimeout = setTimeout(() => {
this.refreshToken().catch(() => {
this.clearAuth()
})
}, timeUntilRefresh)
}
}
private notifyStateChange(): void {
this.onAuthStateChange?.(this.state)
}
// ---------------------------------------------------------------------------
// Public Methods
// ---------------------------------------------------------------------------
/**
* Authenticate using client credentials (server-to-server)
*/
async authenticateWithCredentials(): Promise<void> {
if (!this.clientSecret) {
throw new Error('Client secret is required for credentials authentication')
}
const response = await this.client.authenticate({
grantType: 'client_credentials',
clientId: this.clientId,
clientSecret: this.clientSecret,
})
this.updateState(response)
}
/**
* Authenticate using authorization code (OAuth flow)
*/
async authenticateWithCode(code: string, redirectUri: string): Promise<void> {
const response = await this.client.authenticate({
grantType: 'authorization_code',
clientId: this.clientId,
clientSecret: this.clientSecret,
code,
redirectUri,
})
this.updateState(response)
}
/**
* Refresh the access token
*/
async refreshToken(): Promise<void> {
if (!this.state.refreshToken) {
throw new Error('No refresh token available')
}
const response = await this.client.refreshToken(this.state.refreshToken)
this.updateState(response)
}
/**
* Set access token manually (e.g., from API key)
*/
setAccessToken(token: string, expiresIn?: number): void {
const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null
this.state = {
isAuthenticated: true,
accessToken: token,
refreshToken: null,
expiresAt,
}
this.client.setAccessToken(token)
this.saveToStorage()
this.notifyStateChange()
}
/**
* Clear authentication state
*/
clearAuth(): void {
if (this.refreshTimeout) {
clearTimeout(this.refreshTimeout)
}
this.state = {
isAuthenticated: false,
accessToken: null,
refreshToken: null,
expiresAt: null,
}
this.client.clearAccessToken()
this.clearStorage()
this.notifyStateChange()
}
/**
* Get current authentication state
*/
getState(): AuthState {
return { ...this.state }
}
/**
* Check if currently authenticated
*/
isAuthenticated(): boolean {
if (!this.state.isAuthenticated || !this.state.accessToken) {
return false
}
if (this.state.expiresAt && this.state.expiresAt <= new Date()) {
return false
}
return true
}
/**
* Get the authorization URL for OAuth flow
*/
getAuthorizationUrl(redirectUri: string, scope?: string, state?: string): string {
const params = new URLSearchParams({
client_id: this.clientId,
redirect_uri: redirectUri,
response_type: 'code',
...(scope && { scope }),
...(state && { state }),
})
// This would be configured based on the API endpoint
return `/oauth/authorize?${params.toString()}`
}
/**
* Destroy the auth provider
*/
destroy(): void {
if (this.refreshTimeout) {
clearTimeout(this.refreshTimeout)
}
}
}

View File

@@ -0,0 +1,521 @@
/**
* Compliance Client
*
* Main entry point for the SDK. Handles API communication with
* retry logic, timeout handling, and optimistic locking.
*/
import type {
APIResponse,
StateResponse,
CheckpointValidationResult,
AuthTokenRequest,
AuthTokenResponse,
RAGSearchRequest,
RAGSearchResponse,
RAGAskRequest,
RAGAskResponse,
ExportFormat,
SDKState,
CheckpointStatus,
} from '@breakpilot/compliance-sdk-types'
// =============================================================================
// TYPES
// =============================================================================
export interface ComplianceClientOptions {
apiEndpoint: string
apiKey?: string
tenantId: string
timeout?: number
maxRetries?: number
onError?: (error: Error) => void
onAuthError?: () => void
}
interface APIError extends Error {
status?: number
code?: string
retryable: boolean
}
// =============================================================================
// CONSTANTS
// =============================================================================
const DEFAULT_TIMEOUT = 30000
const DEFAULT_MAX_RETRIES = 3
const RETRY_DELAYS = [1000, 2000, 4000]
// =============================================================================
// COMPLIANCE CLIENT
// =============================================================================
export class ComplianceClient {
private apiEndpoint: string
private apiKey: string | null
private tenantId: string
private timeout: number
private maxRetries: number
private accessToken: string | null = null
private abortControllers: Map<string, AbortController> = new Map()
private onError?: (error: Error) => void
private onAuthError?: () => void
constructor(options: ComplianceClientOptions) {
this.apiEndpoint = options.apiEndpoint.replace(/\/$/, '')
this.apiKey = options.apiKey ?? null
this.tenantId = options.tenantId
this.timeout = options.timeout ?? DEFAULT_TIMEOUT
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
this.onError = options.onError
this.onAuthError = options.onAuthError
}
// ---------------------------------------------------------------------------
// Private Methods
// ---------------------------------------------------------------------------
private createError(message: string, status?: number, retryable = false): APIError {
const error = new Error(message) as APIError
error.status = status
error.retryable = retryable
return error
}
private getHeaders(): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'X-Tenant-ID': this.tenantId,
}
if (this.accessToken) {
headers['Authorization'] = `Bearer ${this.accessToken}`
} else if (this.apiKey) {
headers['Authorization'] = `Bearer ${this.apiKey}`
}
return headers
}
private async fetchWithTimeout(
url: string,
options: RequestInit,
requestId: string
): Promise<Response> {
const controller = new AbortController()
this.abortControllers.set(requestId, controller)
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
})
return response
} finally {
clearTimeout(timeoutId)
this.abortControllers.delete(requestId)
}
}
private async fetchWithRetry<T>(
url: string,
options: RequestInit,
retries = this.maxRetries
): Promise<T> {
const requestId = `${Date.now()}-${Math.random()}`
let lastError: Error | null = null
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const response = await this.fetchWithTimeout(url, options, requestId)
if (!response.ok) {
const errorBody = await response.text()
let errorMessage = `HTTP ${response.status}`
try {
const errorJson = JSON.parse(errorBody)
errorMessage = errorJson.error || errorJson.message || errorMessage
} catch {
// Keep HTTP status message
}
// Handle auth errors
if (response.status === 401) {
this.onAuthError?.()
throw this.createError(errorMessage, response.status, false)
}
// Don't retry client errors (4xx) except 429
const retryable = response.status >= 500 || response.status === 429
if (!retryable || attempt === retries) {
throw this.createError(errorMessage, response.status, retryable)
}
} else {
const data = await response.json()
return data as T
}
} catch (error) {
lastError = error as Error
if (error instanceof Error && error.name === 'AbortError') {
throw this.createError('Request timeout', 408, true)
}
const apiError = error as APIError
if (!apiError.retryable || attempt === retries) {
this.onError?.(error as Error)
throw error
}
}
// Exponential backoff
if (attempt < retries) {
await this.sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
}
}
throw lastError || this.createError('Unknown error', 500, false)
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
// ---------------------------------------------------------------------------
// Authentication
// ---------------------------------------------------------------------------
async authenticate(request: AuthTokenRequest): Promise<AuthTokenResponse> {
const response = await this.fetchWithRetry<APIResponse<AuthTokenResponse>>(
`${this.apiEndpoint}/auth/token`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
}
)
if (response.success && response.data) {
this.accessToken = response.data.accessToken
return response.data
}
throw this.createError(response.error || 'Authentication failed', 401, false)
}
async refreshToken(refreshToken: string): Promise<AuthTokenResponse> {
return this.authenticate({
grantType: 'refresh_token',
clientId: '',
refreshToken,
})
}
setAccessToken(token: string): void {
this.accessToken = token
}
clearAccessToken(): void {
this.accessToken = null
}
// ---------------------------------------------------------------------------
// State Management
// ---------------------------------------------------------------------------
async getState(): Promise<StateResponse | null> {
try {
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
`${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'GET',
headers: this.getHeaders(),
}
)
if (response.success && response.data) {
return response.data
}
return null
} catch (error) {
const apiError = error as APIError
if (apiError.status === 404) {
return null
}
throw error
}
}
async saveState(state: SDKState, version?: number): Promise<StateResponse> {
const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
`${this.apiEndpoint}/state`,
{
method: 'POST',
headers: {
...this.getHeaders(),
...(version !== undefined && { 'If-Match': String(version) }),
},
body: JSON.stringify({
tenantId: this.tenantId,
state,
version,
}),
}
)
if (!response.success) {
throw this.createError(response.error || 'Failed to save state', 500, true)
}
return response.data!
}
async deleteState(): Promise<void> {
await this.fetchWithRetry<APIResponse<void>>(
`${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'DELETE',
headers: this.getHeaders(),
}
)
}
// ---------------------------------------------------------------------------
// Checkpoints
// ---------------------------------------------------------------------------
async validateCheckpoint(
checkpointId: string,
data?: unknown
): Promise<CheckpointValidationResult> {
const response = await this.fetchWithRetry<APIResponse<CheckpointValidationResult>>(
`${this.apiEndpoint}/checkpoints/validate`,
{
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify({
tenantId: this.tenantId,
checkpointId,
data,
}),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'Checkpoint validation failed', 500, true)
}
return response.data
}
async getCheckpoints(): Promise<Record<string, CheckpointStatus>> {
const response = await this.fetchWithRetry<APIResponse<Record<string, CheckpointStatus>>>(
`${this.apiEndpoint}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`,
{
method: 'GET',
headers: this.getHeaders(),
}
)
return response.data || {}
}
// ---------------------------------------------------------------------------
// RAG
// ---------------------------------------------------------------------------
async searchRAG(request: RAGSearchRequest): Promise<RAGSearchResponse> {
const response = await this.fetchWithRetry<APIResponse<RAGSearchResponse>>(
`${this.apiEndpoint}/rag/search`,
{
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(request),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'RAG search failed', 500, true)
}
return response.data
}
async askRAG(request: RAGAskRequest): Promise<RAGAskResponse> {
const response = await this.fetchWithRetry<APIResponse<RAGAskResponse>>(
`${this.apiEndpoint}/rag/ask`,
{
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(request),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'RAG query failed', 500, true)
}
return response.data
}
// ---------------------------------------------------------------------------
// Export
// ---------------------------------------------------------------------------
async exportState(format: ExportFormat): Promise<Blob> {
const response = await this.fetchWithTimeout(
`${this.apiEndpoint}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`,
{
method: 'GET',
headers: {
...this.getHeaders(),
Accept:
format === 'json'
? 'application/json'
: format === 'pdf'
? 'application/pdf'
: 'application/octet-stream',
},
},
`export-${Date.now()}`
)
if (!response.ok) {
throw this.createError(`Export failed: ${response.statusText}`, response.status, true)
}
return response.blob()
}
// ---------------------------------------------------------------------------
// Document Generation
// ---------------------------------------------------------------------------
async generateDocument(
type: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner',
options?: Record<string, unknown>
): Promise<{ id: string; status: string; content?: string }> {
const response = await this.fetchWithRetry<
APIResponse<{ id: string; status: string; content?: string }>
>(
`${this.apiEndpoint}/generate/${type}`,
{
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify({
tenantId: this.tenantId,
options,
}),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'Document generation failed', 500, true)
}
return response.data
}
// ---------------------------------------------------------------------------
// Security Scan
// ---------------------------------------------------------------------------
async startSecurityScan(options?: {
tools?: string[]
targetPath?: string
severityThreshold?: string
generateSBOM?: boolean
}): Promise<{ id: string; status: string }> {
const response = await this.fetchWithRetry<APIResponse<{ id: string; status: string }>>(
`${this.apiEndpoint}/security/scan`,
{
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify({
tenantId: this.tenantId,
...options,
}),
}
)
if (!response.success || !response.data) {
throw this.createError(response.error || 'Security scan failed', 500, true)
}
return response.data
}
async getSecurityScanResult(scanId: string): Promise<unknown> {
const response = await this.fetchWithRetry<APIResponse<unknown>>(
`${this.apiEndpoint}/security/scan/${scanId}`,
{
method: 'GET',
headers: this.getHeaders(),
}
)
return response.data
}
// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
cancelAllRequests(): void {
this.abortControllers.forEach(controller => controller.abort())
this.abortControllers.clear()
}
setTenantId(tenantId: string): void {
this.tenantId = tenantId
}
getTenantId(): string {
return this.tenantId
}
async healthCheck(): Promise<boolean> {
try {
const response = await this.fetchWithTimeout(
`${this.apiEndpoint}/health`,
{ method: 'GET' },
`health-${Date.now()}`
)
return response.ok
} catch {
return false
}
}
}
// =============================================================================
// FACTORY
// =============================================================================
let clientInstance: ComplianceClient | null = null
export function getComplianceClient(options?: ComplianceClientOptions): ComplianceClient {
if (!clientInstance && !options) {
throw new Error('ComplianceClient not initialized. Provide options on first call.')
}
if (!clientInstance && options) {
clientInstance = new ComplianceClient(options)
}
return clientInstance!
}
export function resetComplianceClient(): void {
if (clientInstance) {
clientInstance.cancelAllRequests()
}
clientInstance = null
}

View File

@@ -0,0 +1,41 @@
/**
* @breakpilot/compliance-sdk-core
*
* Core functionality for BreakPilot Compliance SDK
*/
// Client
export { ComplianceClient, type ComplianceClientOptions } from './client'
// State Management
export {
createStore,
sdkReducer,
initialState,
type SDKStore,
type SDKStoreOptions,
} from './state'
// Sync
export {
StateSyncManager,
createStateSyncManager,
type SyncOptions,
type SyncCallbacks,
} from './sync'
// Auth
export {
AuthProvider,
type AuthProviderOptions,
type AuthState,
} from './auth'
// Modules
export * from './modules/dsgvo'
export * from './modules/compliance'
export * from './modules/rag'
export * from './modules/security'
// Utils
export * from './utils'

View File

@@ -0,0 +1,246 @@
/**
* Compliance Module
*
* General compliance functionality including controls, evidence,
* AI Act, NIS2, and regulatory obligations
*/
import type {
Control,
Evidence,
Requirement,
Obligation,
AIActResult,
AIActRiskCategory,
RegulationCode,
ComplianceScore,
SDKState,
RiskSeverity,
} from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from '../client'
export class ComplianceModule {
private client: ComplianceClient
private getState: () => SDKState
constructor(client: ComplianceClient, getState: () => SDKState) {
this.client = client
this.getState = getState
}
// ---------------------------------------------------------------------------
// Controls
// ---------------------------------------------------------------------------
getControls(): Control[] {
return this.getState().controls
}
getControlById(id: string): Control | undefined {
return this.getState().controls.find(c => c.id === id)
}
getControlsByDomain(domain: string): Control[] {
return this.getState().controls.filter(c => c.domain === domain)
}
getControlsByStatus(status: string): Control[] {
return this.getState().controls.filter(c => c.implementationStatus === status)
}
getControlComplianceRate(): number {
const controls = this.getControls()
if (controls.length === 0) return 0
const implemented = controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length
return Math.round((implemented / controls.length) * 100)
}
// ---------------------------------------------------------------------------
// Evidence
// ---------------------------------------------------------------------------
getEvidence(): Evidence[] {
return this.getState().evidence
}
getEvidenceById(id: string): Evidence | undefined {
return this.getState().evidence.find(e => e.id === id)
}
getEvidenceByControlId(controlId: string): Evidence[] {
return this.getState().evidence.filter(e => e.controlId === controlId)
}
getExpiringEvidence(days: number = 30): Evidence[] {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() + days)
return this.getState().evidence.filter(e => {
if (!e.validUntil) return false
const validUntil = new Date(e.validUntil)
return validUntil <= cutoff && e.status === 'ACTIVE'
})
}
// ---------------------------------------------------------------------------
// Requirements
// ---------------------------------------------------------------------------
getRequirements(): Requirement[] {
return this.getState().requirements
}
getRequirementsByRegulation(regulation: RegulationCode): Requirement[] {
return this.getState().requirements.filter(r => r.regulationCode === regulation)
}
getRequirementComplianceRate(regulation?: RegulationCode): number {
let requirements = this.getRequirements()
if (regulation) {
requirements = requirements.filter(r => r.regulationCode === regulation)
}
if (requirements.length === 0) return 0
const implemented = requirements.filter(
r => r.status === 'IMPLEMENTED' || r.status === 'VERIFIED'
).length
return Math.round((implemented / requirements.length) * 100)
}
// ---------------------------------------------------------------------------
// Obligations
// ---------------------------------------------------------------------------
getObligations(): Obligation[] {
return this.getState().obligations
}
getUpcomingObligations(days: number = 30): Obligation[] {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() + days)
return this.getState().obligations.filter(o => {
if (!o.deadline || o.status === 'COMPLETED') return false
const deadline = new Date(o.deadline)
return deadline <= cutoff
})
}
getOverdueObligations(): Obligation[] {
const now = new Date()
return this.getState().obligations.filter(o => {
if (!o.deadline || o.status === 'COMPLETED') return false
const deadline = new Date(o.deadline)
return deadline < now
})
}
// ---------------------------------------------------------------------------
// AI Act
// ---------------------------------------------------------------------------
getAIActClassification(): AIActResult | null {
return this.getState().aiActClassification
}
getAIActRiskCategory(): AIActRiskCategory | null {
return this.getState().aiActClassification?.riskCategory ?? null
}
isHighRiskAI(): boolean {
const category = this.getAIActRiskCategory()
return category === 'HIGH' || category === 'UNACCEPTABLE'
}
// ---------------------------------------------------------------------------
// Compliance Score
// ---------------------------------------------------------------------------
calculateComplianceScore(): ComplianceScore {
const state = this.getState()
// Calculate overall score based on controls, requirements, and evidence
const controlScore = this.getControlComplianceRate()
const requirementScore = this.getRequirementComplianceRate()
const evidenceCoverage = this.calculateEvidenceCoverage()
const overall = Math.round((controlScore + requirementScore + evidenceCoverage) / 3)
// Calculate scores by regulation
const byRegulation: Record<string, number> = {}
const regulations = new Set(state.requirements.map(r => r.regulationCode))
regulations.forEach(reg => {
byRegulation[reg] = this.getRequirementComplianceRate(reg as RegulationCode)
})
// Calculate scores by domain
const byDomain: Record<string, number> = {}
const domains = new Set(state.controls.map(c => c.domain))
domains.forEach(domain => {
const domainControls = state.controls.filter(c => c.domain === domain)
const implemented = domainControls.filter(c => c.implementationStatus === 'IMPLEMENTED').length
byDomain[domain] = domainControls.length > 0
? Math.round((implemented / domainControls.length) * 100)
: 0
})
return {
overall,
byRegulation: byRegulation as Record<RegulationCode, number>,
byDomain: byDomain as Record<string, number>,
trend: 'STABLE', // Would need historical data to calculate
lastCalculated: new Date(),
}
}
private calculateEvidenceCoverage(): number {
const controls = this.getControls()
const implementedControls = controls.filter(c => c.implementationStatus === 'IMPLEMENTED')
if (implementedControls.length === 0) return 0
const controlsWithEvidence = implementedControls.filter(c => {
const evidence = this.getEvidenceByControlId(c.id)
return evidence.some(e => e.status === 'ACTIVE')
})
return Math.round((controlsWithEvidence.length / implementedControls.length) * 100)
}
// ---------------------------------------------------------------------------
// Risks
// ---------------------------------------------------------------------------
getRisks() {
return this.getState().risks
}
getRisksByStatus(status: string) {
return this.getRisks().filter(r => r.status === status)
}
getRisksBySeverity(severity: RiskSeverity) {
return this.getRisks().filter(r => r.severity === severity)
}
getCriticalRisks() {
return this.getRisks().filter(r => r.severity === 'CRITICAL' || r.severity === 'HIGH')
}
getAverageRiskScore(): number {
const risks = this.getRisks()
if (risks.length === 0) return 0
const totalScore = risks.reduce((sum, r) => sum + r.residualRiskScore, 0)
return Math.round(totalScore / risks.length)
}
}
export function createComplianceModule(
client: ComplianceClient,
getState: () => SDKState
): ComplianceModule {
return new ComplianceModule(client, getState)
}

View File

@@ -0,0 +1,155 @@
/**
* DSGVO Module
*
* GDPR compliance functionality
*/
import type {
DSRRequest,
DSRRequestType,
ConsentRecord,
ConsentPurpose,
ProcessingActivity,
DSFA,
TOM,
RetentionPolicy,
CookieBannerConfig,
SDKState,
} from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from '../client'
export class DSGVOModule {
private client: ComplianceClient
private getState: () => SDKState
constructor(client: ComplianceClient, getState: () => SDKState) {
this.client = client
this.getState = getState
}
// ---------------------------------------------------------------------------
// DSR (Data Subject Requests)
// ---------------------------------------------------------------------------
async submitDSR(type: DSRRequestType, requesterEmail: string, requesterName: string): Promise<DSRRequest> {
const response = await fetch(`${this.client.getTenantId()}/dsgvo/dsr`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, requesterEmail, requesterName }),
})
return response.json()
}
getDSRRequests(): DSRRequest[] {
return this.getState().dsrRequests
}
getDSRById(id: string): DSRRequest | undefined {
return this.getState().dsrRequests.find(r => r.id === id)
}
// ---------------------------------------------------------------------------
// Consent Management
// ---------------------------------------------------------------------------
getConsents(): ConsentRecord[] {
return this.getState().consents
}
getConsentsByUserId(userId: string): ConsentRecord[] {
return this.getState().consents.filter(c => c.userId === userId)
}
hasConsent(userId: string, purpose: ConsentPurpose): boolean {
const consents = this.getConsentsByUserId(userId)
const latestConsent = consents
.filter(c => c.consentType === purpose)
.sort((a, b) => new Date(b.grantedAt).getTime() - new Date(a.grantedAt).getTime())[0]
return latestConsent?.granted && !latestConsent.revokedAt
}
// ---------------------------------------------------------------------------
// VVT (Processing Register)
// ---------------------------------------------------------------------------
getProcessingActivities(): ProcessingActivity[] {
return this.getState().vvt
}
getProcessingActivityById(id: string): ProcessingActivity | undefined {
return this.getState().vvt.find(p => p.id === id)
}
// ---------------------------------------------------------------------------
// DSFA
// ---------------------------------------------------------------------------
getDSFA(): DSFA | null {
return this.getState().dsfa
}
isDSFARequired(): boolean {
const state = this.getState()
const activeUseCase = state.useCases.find(uc => uc.id === state.activeUseCase)
return activeUseCase?.assessmentResult?.dsfaRequired ?? false
}
// ---------------------------------------------------------------------------
// TOMs
// ---------------------------------------------------------------------------
getTOMs(): TOM[] {
return this.getState().toms
}
getTOMsByCategory(category: string): TOM[] {
return this.getState().toms.filter(t => t.category === category)
}
getTOMScore(): number {
const toms = this.getTOMs()
if (toms.length === 0) return 0
const implemented = toms.filter(t => t.implementationStatus === 'IMPLEMENTED').length
return Math.round((implemented / toms.length) * 100)
}
// ---------------------------------------------------------------------------
// Retention Policies
// ---------------------------------------------------------------------------
getRetentionPolicies(): RetentionPolicy[] {
return this.getState().retentionPolicies
}
getUpcomingDeletions(days: number = 30): RetentionPolicy[] {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() + days)
return this.getState().retentionPolicies.filter(p => {
const nextReview = new Date(p.nextReviewDate)
return nextReview <= cutoff
})
}
// ---------------------------------------------------------------------------
// Cookie Banner
// ---------------------------------------------------------------------------
getCookieBannerConfig(): CookieBannerConfig | null {
return this.getState().cookieBanner
}
async generateCookieBannerCode(): Promise<{ html: string; css: string; js: string } | null> {
const config = this.getCookieBannerConfig()
if (!config) return null
const response = await this.client.generateDocument('cookie_banner', { config })
return response.content ? JSON.parse(response.content) : null
}
}
export function createDSGVOModule(client: ComplianceClient, getState: () => SDKState): DSGVOModule {
return new DSGVOModule(client, getState)
}

View File

@@ -0,0 +1,206 @@
/**
* RAG Module
*
* Legal RAG system for semantic search and AI-powered legal assistance
*/
import type {
SearchQuery,
SearchResponse,
AssistantQuery,
AssistantResponse,
LegalDocument,
ChatSession,
ChatMessage,
} from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from '../client'
export class RAGModule {
private client: ComplianceClient
private chatHistory: ChatMessage[] = []
private sessionId: string | null = null
constructor(client: ComplianceClient) {
this.client = client
}
// ---------------------------------------------------------------------------
// Search
// ---------------------------------------------------------------------------
async search(query: string, options?: Partial<SearchQuery>): Promise<SearchResponse> {
const searchRequest: SearchQuery = {
query,
limit: options?.limit ?? 10,
offset: options?.offset ?? 0,
filters: options?.filters,
includeMetadata: options?.includeMetadata ?? true,
scoreThreshold: options?.scoreThreshold ?? 0.5,
}
return this.client.searchRAG({
query: searchRequest.query,
filters: searchRequest.filters,
limit: searchRequest.limit,
offset: searchRequest.offset,
})
}
async searchByRegulation(regulation: string, query: string): Promise<SearchResponse> {
return this.search(query, {
filters: {
documentCodes: [regulation],
},
})
}
async searchByArticle(regulation: string, article: string): Promise<SearchResponse> {
return this.search(`${regulation} Artikel ${article}`, {
filters: {
documentCodes: [regulation],
articles: [article],
},
})
}
// ---------------------------------------------------------------------------
// Legal Assistant
// ---------------------------------------------------------------------------
async ask(question: string, options?: Partial<AssistantQuery>): Promise<AssistantResponse> {
const request: AssistantQuery = {
question,
context: options?.context,
documents: options?.documents,
maxSources: options?.maxSources ?? 5,
language: options?.language ?? 'de',
responseFormat: options?.responseFormat ?? 'detailed',
}
const response = await this.client.askRAG({
question: request.question,
context: request.context,
documents: request.documents,
maxSources: request.maxSources,
language: request.language,
})
// Add to chat history
this.chatHistory.push({
id: `user-${Date.now()}`,
role: 'user',
content: question,
timestamp: new Date(),
})
this.chatHistory.push({
id: `assistant-${Date.now()}`,
role: 'assistant',
content: response.answer,
timestamp: new Date(),
sources: response.sources,
})
return response
}
async askAboutRegulation(regulation: string, question: string): Promise<AssistantResponse> {
return this.ask(question, {
documents: [regulation],
context: `Kontext: Frage bezieht sich auf ${regulation}`,
})
}
async explainArticle(regulation: string, article: string): Promise<AssistantResponse> {
return this.ask(`Erkläre ${regulation} Artikel ${article} einfach und verständlich.`, {
documents: [regulation],
})
}
async checkCompliance(
regulation: string,
scenario: string
): Promise<AssistantResponse> {
return this.ask(
`Prüfe folgendes Szenario auf Compliance mit ${regulation}: ${scenario}`,
{
documents: [regulation],
responseFormat: 'detailed',
}
)
}
// ---------------------------------------------------------------------------
// Chat Session
// ---------------------------------------------------------------------------
startNewSession(): void {
this.sessionId = `session-${Date.now()}`
this.chatHistory = []
}
getChatHistory(): ChatMessage[] {
return [...this.chatHistory]
}
clearChatHistory(): void {
this.chatHistory = []
}
getSessionId(): string | null {
return this.sessionId
}
// ---------------------------------------------------------------------------
// Document Info
// ---------------------------------------------------------------------------
getAvailableRegulations(): readonly string[] {
return [
'DSGVO',
'AI_ACT',
'NIS2',
'EPRIVACY',
'TDDDG',
'SCC',
'DPF',
'CRA',
'EUCSA',
'DATA_ACT',
'DGA',
'DSA',
'EAA',
'BDSG',
'ISO_27001',
'BSI_GRUNDSCHUTZ',
'KRITIS',
'BAIT',
'VAIT',
'SOC2',
'PCI_DSS',
] as const
}
// ---------------------------------------------------------------------------
// Quick Actions
// ---------------------------------------------------------------------------
async getQuickAnswer(question: string): Promise<string> {
const response = await this.ask(question, {
responseFormat: 'concise',
maxSources: 3,
})
return response.answer
}
async findRelevantArticles(topic: string): Promise<SearchResponse> {
return this.search(topic, {
limit: 5,
scoreThreshold: 0.7,
})
}
}
export function createRAGModule(client: ComplianceClient): RAGModule {
return new RAGModule(client)
}

View File

@@ -0,0 +1,241 @@
/**
* Security Module
*
* Security scanning and SBOM management
*/
import type {
SBOM,
SecurityIssue,
SecurityScanResult,
BacklogItem,
SecurityIssueSeverity,
SecurityTool,
FindingsSummary,
SDKState,
} from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from '../client'
export class SecurityModule {
private client: ComplianceClient
private getState: () => SDKState
constructor(client: ComplianceClient, getState: () => SDKState) {
this.client = client
this.getState = getState
}
// ---------------------------------------------------------------------------
// Security Scanning
// ---------------------------------------------------------------------------
async startScan(options?: {
tools?: SecurityTool[]
targetPath?: string
severityThreshold?: SecurityIssueSeverity
generateSBOM?: boolean
}): Promise<{ id: string; status: string }> {
return this.client.startSecurityScan({
tools: options?.tools,
targetPath: options?.targetPath,
severityThreshold: options?.severityThreshold,
generateSBOM: options?.generateSBOM ?? true,
})
}
async getScanResult(scanId: string): Promise<SecurityScanResult | null> {
const result = await this.client.getSecurityScanResult(scanId)
return result as SecurityScanResult | null
}
getLastScanResult(): SecurityScanResult | null {
const screening = this.getState().screening
return screening?.securityScan ?? null
}
// ---------------------------------------------------------------------------
// SBOM
// ---------------------------------------------------------------------------
getSBOM(): SBOM | null {
return this.getState().sbom
}
getComponents() {
return this.getSBOM()?.components ?? []
}
getComponentsByLicense(license: string) {
return this.getComponents().filter(c => c.licenses.includes(license as never))
}
getVulnerableComponents() {
return this.getComponents().filter(c => c.vulnerabilities.length > 0)
}
getLicenseSummary(): Record<string, number> {
const components = this.getComponents()
const summary: Record<string, number> = {}
components.forEach(c => {
c.licenses.forEach(license => {
summary[license] = (summary[license] || 0) + 1
})
})
return summary
}
// ---------------------------------------------------------------------------
// Security Issues
// ---------------------------------------------------------------------------
getSecurityIssues(): SecurityIssue[] {
return this.getState().securityIssues
}
getIssueById(id: string): SecurityIssue | undefined {
return this.getSecurityIssues().find(i => i.id === id)
}
getIssuesBySeverity(severity: SecurityIssueSeverity): SecurityIssue[] {
return this.getSecurityIssues().filter(i => i.severity === severity)
}
getIssuesByStatus(status: string): SecurityIssue[] {
return this.getSecurityIssues().filter(i => i.status === status)
}
getIssuesByTool(tool: SecurityTool): SecurityIssue[] {
return this.getSecurityIssues().filter(i => i.tool === tool)
}
getOpenIssues(): SecurityIssue[] {
return this.getSecurityIssues().filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
}
getCriticalIssues(): SecurityIssue[] {
return this.getSecurityIssues().filter(
i => (i.severity === 'CRITICAL' || i.severity === 'HIGH') && i.status === 'OPEN'
)
}
// ---------------------------------------------------------------------------
// Backlog
// ---------------------------------------------------------------------------
getBacklog(): BacklogItem[] {
return this.getState().securityBacklog
}
getBacklogByStatus(status: 'OPEN' | 'IN_PROGRESS' | 'DONE'): BacklogItem[] {
return this.getBacklog().filter(i => i.status === status)
}
getOverdueBacklogItems(): BacklogItem[] {
const now = new Date()
return this.getBacklog().filter(i => {
if (!i.dueDate || i.status === 'DONE') return false
return new Date(i.dueDate) < now
})
}
// ---------------------------------------------------------------------------
// Summary & Statistics
// ---------------------------------------------------------------------------
getSecuritySummary(): FindingsSummary {
const issues = this.getSecurityIssues()
const bySeverity: Record<SecurityIssueSeverity, number> = {
CRITICAL: 0,
HIGH: 0,
MEDIUM: 0,
LOW: 0,
INFO: 0,
}
const byStatus: Record<string, number> = {
OPEN: 0,
IN_PROGRESS: 0,
RESOLVED: 0,
ACCEPTED: 0,
FALSE_POSITIVE: 0,
}
const byTool: Record<string, number> = {}
issues.forEach(issue => {
bySeverity[issue.severity]++
byStatus[issue.status]++
byTool[issue.tool] = (byTool[issue.tool] || 0) + 1
})
// Calculate average resolution time for resolved issues
const resolvedIssues = issues.filter(i => i.status === 'RESOLVED' && i.resolvedAt)
let averageResolutionDays = 0
if (resolvedIssues.length > 0) {
const totalDays = resolvedIssues.reduce((sum, issue) => {
// Would need createdAt field to calculate properly
return sum + 7 // Placeholder
}, 0)
averageResolutionDays = Math.round(totalDays / resolvedIssues.length)
}
// Find oldest unresolved issue
const openIssues = issues.filter(i => i.status === 'OPEN')
let oldestUnresolvedDays = 0
// Would need createdAt field to calculate properly
return {
totalFindings: issues.length,
bySeverity,
byStatus: byStatus as Record<string, number>,
byTool: byTool as Record<SecurityTool, number>,
averageResolutionDays,
oldestUnresolvedDays,
}
}
getSecurityScore(): number {
const issues = this.getSecurityIssues()
const openIssues = issues.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
if (issues.length === 0) return 100
// Weight by severity
const severityWeights = {
CRITICAL: 10,
HIGH: 5,
MEDIUM: 2,
LOW: 1,
INFO: 0,
}
const totalWeight = issues.reduce((sum, i) => sum + severityWeights[i.severity], 0)
const openWeight = openIssues.reduce((sum, i) => sum + severityWeights[i.severity], 0)
if (totalWeight === 0) return 100
return Math.round(((totalWeight - openWeight) / totalWeight) * 100)
}
// ---------------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------------
getAvailableTools(): SecurityTool[] {
return ['gitleaks', 'semgrep', 'bandit', 'trivy', 'grype', 'syft']
}
async exportSBOM(format: 'CycloneDX' | 'SPDX'): Promise<Blob> {
return this.client.exportState(format === 'CycloneDX' ? 'json' : 'json')
}
}
export function createSecurityModule(
client: ComplianceClient,
getState: () => SDKState
): SecurityModule {
return new SecurityModule(client, getState)
}

View File

@@ -0,0 +1,414 @@
/**
* SDK State Management
*
* Reducer-based state management with persistence support
*/
import type {
SDKState,
SDKAction,
UserPreferences,
SDKPhase,
SubscriptionTier,
getStepById,
} from '@breakpilot/compliance-sdk-types'
// =============================================================================
// INITIAL STATE
// =============================================================================
const initialPreferences: UserPreferences = {
language: 'de',
theme: 'light',
compactMode: false,
showHints: true,
autoSave: true,
autoValidate: true,
}
export const initialState: SDKState = {
// Metadata
version: '1.0.0',
lastModified: new Date(),
// Tenant & User
tenantId: '',
userId: '',
subscription: 'PROFESSIONAL' as SubscriptionTier,
// Progress
currentPhase: 1 as SDKPhase,
currentStep: 'use-case-workshop',
completedSteps: [],
checkpoints: {},
// Phase 1 Data
useCases: [],
activeUseCase: null,
screening: null,
modules: [],
requirements: [],
controls: [],
evidence: [],
checklist: [],
risks: [],
// Phase 2 Data
aiActClassification: null,
obligations: [],
dsfa: null,
toms: [],
retentionPolicies: [],
vvt: [],
documents: [],
cookieBanner: null,
consents: [],
dsrConfig: null,
dsrRequests: [],
escalationWorkflows: [],
// Security
sbom: null,
securityIssues: [],
securityBacklog: [],
// UI State
commandBarHistory: [],
recentSearches: [],
preferences: initialPreferences,
}
// =============================================================================
// REDUCER
// =============================================================================
export function sdkReducer(state: SDKState, action: SDKAction): SDKState {
const updateState = (updates: Partial<SDKState>): SDKState => ({
...state,
...updates,
lastModified: new Date(),
})
switch (action.type) {
case 'SET_STATE':
return updateState(action.payload)
case 'SET_CURRENT_STEP': {
// Import dynamically to avoid circular dependencies
const { getStepById } = require('@breakpilot/compliance-sdk-types')
const step = getStepById(action.payload)
return updateState({
currentStep: action.payload,
currentPhase: step?.phase || state.currentPhase,
})
}
case 'COMPLETE_STEP':
if (state.completedSteps.includes(action.payload)) {
return state
}
return updateState({
completedSteps: [...state.completedSteps, action.payload],
})
case 'SET_CHECKPOINT_STATUS':
return updateState({
checkpoints: {
...state.checkpoints,
[action.payload.id]: action.payload.status,
},
})
// Use Cases
case 'ADD_USE_CASE':
return updateState({
useCases: [...state.useCases, action.payload],
})
case 'UPDATE_USE_CASE':
return updateState({
useCases: state.useCases.map(uc =>
uc.id === action.payload.id ? { ...uc, ...action.payload.data } : uc
),
})
case 'DELETE_USE_CASE':
return updateState({
useCases: state.useCases.filter(uc => uc.id !== action.payload),
activeUseCase: state.activeUseCase === action.payload ? null : state.activeUseCase,
})
case 'SET_ACTIVE_USE_CASE':
return updateState({ activeUseCase: action.payload })
// Screening
case 'SET_SCREENING':
return updateState({ screening: action.payload })
// Modules
case 'ADD_MODULE':
return updateState({
modules: [...state.modules, action.payload],
})
case 'UPDATE_MODULE':
return updateState({
modules: state.modules.map(m =>
m.id === action.payload.id ? { ...m, ...action.payload.data } : m
),
})
// Requirements
case 'ADD_REQUIREMENT':
return updateState({
requirements: [...state.requirements, action.payload],
})
case 'UPDATE_REQUIREMENT':
return updateState({
requirements: state.requirements.map(r =>
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
),
})
// Controls
case 'ADD_CONTROL':
return updateState({
controls: [...state.controls, action.payload],
})
case 'UPDATE_CONTROL':
return updateState({
controls: state.controls.map(c =>
c.id === action.payload.id ? { ...c, ...action.payload.data } : c
),
})
// Evidence
case 'ADD_EVIDENCE':
return updateState({
evidence: [...state.evidence, action.payload],
})
case 'UPDATE_EVIDENCE':
return updateState({
evidence: state.evidence.map(e =>
e.id === action.payload.id ? { ...e, ...action.payload.data } : e
),
})
case 'DELETE_EVIDENCE':
return updateState({
evidence: state.evidence.filter(e => e.id !== action.payload),
})
// Risks
case 'ADD_RISK':
return updateState({
risks: [...state.risks, action.payload],
})
case 'UPDATE_RISK':
return updateState({
risks: state.risks.map(r =>
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
),
})
case 'DELETE_RISK':
return updateState({
risks: state.risks.filter(r => r.id !== action.payload),
})
// AI Act
case 'SET_AI_ACT_RESULT':
return updateState({ aiActClassification: action.payload })
// Obligations
case 'ADD_OBLIGATION':
return updateState({
obligations: [...state.obligations, action.payload],
})
case 'UPDATE_OBLIGATION':
return updateState({
obligations: state.obligations.map(o =>
o.id === action.payload.id ? { ...o, ...action.payload.data } : o
),
})
// DSFA
case 'SET_DSFA':
return updateState({ dsfa: action.payload })
// TOMs
case 'ADD_TOM':
return updateState({
toms: [...state.toms, action.payload],
})
case 'UPDATE_TOM':
return updateState({
toms: state.toms.map(t =>
t.id === action.payload.id ? { ...t, ...action.payload.data } : t
),
})
// Retention Policies
case 'ADD_RETENTION_POLICY':
return updateState({
retentionPolicies: [...state.retentionPolicies, action.payload],
})
case 'UPDATE_RETENTION_POLICY':
return updateState({
retentionPolicies: state.retentionPolicies.map(p =>
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
),
})
// Processing Activities (VVT)
case 'ADD_PROCESSING_ACTIVITY':
return updateState({
vvt: [...state.vvt, action.payload],
})
case 'UPDATE_PROCESSING_ACTIVITY':
return updateState({
vvt: state.vvt.map(p =>
p.id === action.payload.id ? { ...p, ...action.payload.data } : p
),
})
// Documents
case 'ADD_DOCUMENT':
return updateState({
documents: [...state.documents, action.payload],
})
case 'UPDATE_DOCUMENT':
return updateState({
documents: state.documents.map(d =>
d.id === action.payload.id ? { ...d, ...action.payload.data } : d
),
})
// Cookie Banner
case 'SET_COOKIE_BANNER':
return updateState({ cookieBanner: action.payload })
// DSR
case 'SET_DSR_CONFIG':
return updateState({ dsrConfig: action.payload })
case 'ADD_DSR_REQUEST':
return updateState({
dsrRequests: [...state.dsrRequests, action.payload],
})
case 'UPDATE_DSR_REQUEST':
return updateState({
dsrRequests: state.dsrRequests.map(r =>
r.id === action.payload.id ? { ...r, ...action.payload.data } : r
),
})
// Escalation Workflows
case 'ADD_ESCALATION_WORKFLOW':
return updateState({
escalationWorkflows: [...state.escalationWorkflows, action.payload],
})
case 'UPDATE_ESCALATION_WORKFLOW':
return updateState({
escalationWorkflows: state.escalationWorkflows.map(w =>
w.id === action.payload.id ? { ...w, ...action.payload.data } : w
),
})
// Security
case 'ADD_SECURITY_ISSUE':
return updateState({
securityIssues: [...state.securityIssues, action.payload],
})
case 'UPDATE_SECURITY_ISSUE':
return updateState({
securityIssues: state.securityIssues.map(i =>
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
),
})
case 'ADD_BACKLOG_ITEM':
return updateState({
securityBacklog: [...state.securityBacklog, action.payload],
})
case 'UPDATE_BACKLOG_ITEM':
return updateState({
securityBacklog: state.securityBacklog.map(i =>
i.id === action.payload.id ? { ...i, ...action.payload.data } : i
),
})
// UI State
case 'ADD_COMMAND_HISTORY':
return updateState({
commandBarHistory: [action.payload, ...state.commandBarHistory].slice(0, 50),
})
case 'SET_PREFERENCES':
return updateState({
preferences: { ...state.preferences, ...action.payload },
})
case 'RESET_STATE':
return { ...initialState, lastModified: new Date() }
default:
return state
}
}
// =============================================================================
// STORE
// =============================================================================
export interface SDKStoreOptions {
tenantId: string
userId: string
initialState?: Partial<SDKState>
onChange?: (state: SDKState) => void
}
export interface SDKStore {
getState: () => SDKState
dispatch: (action: SDKAction) => void
subscribe: (listener: (state: SDKState) => void) => () => void
}
export function createStore(options: SDKStoreOptions): SDKStore {
let state: SDKState = {
...initialState,
tenantId: options.tenantId,
userId: options.userId,
...options.initialState,
}
const listeners = new Set<(state: SDKState) => void>()
const getState = () => state
const dispatch = (action: SDKAction) => {
state = sdkReducer(state, action)
listeners.forEach(listener => listener(state))
options.onChange?.(state)
}
const subscribe = (listener: (state: SDKState) => void) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
return { getState, dispatch, subscribe }
}

View File

@@ -0,0 +1,435 @@
/**
* SDK State Synchronization
*
* Handles offline/online sync, multi-tab coordination,
* and conflict resolution for SDK state.
*/
import type { SDKState, SyncState, SyncStatus, ConflictResolution } from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from './client'
// =============================================================================
// TYPES
// =============================================================================
export interface SyncOptions {
debounceMs?: number
maxRetries?: number
conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
}
export interface SyncCallbacks {
onSyncStart?: () => void
onSyncComplete?: (state: SDKState) => void
onSyncError?: (error: Error) => void
onConflict?: (local: SDKState, server: SDKState) => void
onOffline?: () => void
onOnline?: () => void
}
// =============================================================================
// CONSTANTS
// =============================================================================
const STORAGE_KEY_PREFIX = 'breakpilot-compliance-sdk-state'
const SYNC_CHANNEL = 'breakpilot-sdk-state-sync'
const DEFAULT_DEBOUNCE_MS = 2000
const DEFAULT_MAX_RETRIES = 3
// =============================================================================
// STATE SYNC MANAGER
// =============================================================================
export class StateSyncManager {
private client: ComplianceClient
private tenantId: string
private options: Required<SyncOptions>
private callbacks: SyncCallbacks
private syncState: SyncState
private broadcastChannel: BroadcastChannel | null = null
private debounceTimeout: ReturnType<typeof setTimeout> | null = null
private pendingState: SDKState | null = null
private isOnline = true
constructor(
client: ComplianceClient,
tenantId: string,
options: SyncOptions = {},
callbacks: SyncCallbacks = {}
) {
this.client = client
this.tenantId = tenantId
this.callbacks = callbacks
this.options = {
debounceMs: options.debounceMs ?? DEFAULT_DEBOUNCE_MS,
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
conflictHandler: options.conflictHandler ?? this.defaultConflictHandler.bind(this),
}
this.syncState = {
status: 'idle' as SyncStatus,
lastSyncedAt: null,
localVersion: 0,
serverVersion: 0,
pendingChanges: 0,
error: null,
}
this.setupBroadcastChannel()
this.setupOnlineListener()
}
// ---------------------------------------------------------------------------
// Setup Methods
// ---------------------------------------------------------------------------
private setupBroadcastChannel(): void {
if (typeof window === 'undefined' || !('BroadcastChannel' in window)) {
return
}
try {
this.broadcastChannel = new BroadcastChannel(`${SYNC_CHANNEL}-${this.tenantId}`)
this.broadcastChannel.onmessage = this.handleBroadcastMessage.bind(this)
} catch (error) {
console.warn('BroadcastChannel not available:', error)
}
}
private setupOnlineListener(): void {
if (typeof window === 'undefined') {
return
}
window.addEventListener('online', () => {
this.isOnline = true
this.syncState.status = 'idle'
this.callbacks.onOnline?.()
if (this.pendingState) {
this.syncToServer(this.pendingState)
}
})
window.addEventListener('offline', () => {
this.isOnline = false
this.syncState.status = 'offline'
this.callbacks.onOffline?.()
})
this.isOnline = navigator.onLine
if (!this.isOnline) {
this.syncState.status = 'offline'
}
}
// ---------------------------------------------------------------------------
// Broadcast Channel Methods
// ---------------------------------------------------------------------------
private handleBroadcastMessage(event: MessageEvent): void {
const { type, state, version } = event.data
switch (type) {
case 'STATE_UPDATED':
if (version > this.syncState.localVersion) {
this.syncState.localVersion = version
this.saveToLocalStorage(state)
this.callbacks.onSyncComplete?.(state)
}
break
case 'SYNC_COMPLETE':
this.syncState.serverVersion = version
break
case 'REQUEST_STATE':
this.broadcastState()
break
}
}
private broadcastState(): void {
if (!this.broadcastChannel) return
const state = this.loadFromLocalStorage()
if (state) {
this.broadcastChannel.postMessage({
type: 'STATE_UPDATED',
state,
version: this.syncState.localVersion,
tabId: this.getTabId(),
})
}
}
private broadcastSyncComplete(version: number): void {
if (!this.broadcastChannel) return
this.broadcastChannel.postMessage({
type: 'SYNC_COMPLETE',
version,
tabId: this.getTabId(),
})
}
private getTabId(): string {
if (typeof window === 'undefined') return 'server'
let tabId = sessionStorage.getItem('breakpilot-sdk-tab-id')
if (!tabId) {
tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
sessionStorage.setItem('breakpilot-sdk-tab-id', tabId)
}
return tabId
}
// ---------------------------------------------------------------------------
// Local Storage Methods
// ---------------------------------------------------------------------------
private getStorageKey(): string {
return `${STORAGE_KEY_PREFIX}-${this.tenantId}`
}
saveToLocalStorage(state: SDKState): void {
if (typeof window === 'undefined') return
try {
const data = {
state,
version: this.syncState.localVersion,
savedAt: new Date().toISOString(),
}
localStorage.setItem(this.getStorageKey(), JSON.stringify(data))
} catch (error) {
console.error('Failed to save to localStorage:', error)
}
}
loadFromLocalStorage(): SDKState | null {
if (typeof window === 'undefined') return null
try {
const stored = localStorage.getItem(this.getStorageKey())
if (stored) {
const data = JSON.parse(stored)
this.syncState.localVersion = data.version || 0
return data.state
}
} catch (error) {
console.error('Failed to load from localStorage:', error)
}
return null
}
clearLocalStorage(): void {
if (typeof window === 'undefined') return
try {
localStorage.removeItem(this.getStorageKey())
} catch (error) {
console.error('Failed to clear localStorage:', error)
}
}
// ---------------------------------------------------------------------------
// Sync Methods
// ---------------------------------------------------------------------------
queueSync(state: SDKState): void {
this.pendingState = state
this.syncState.pendingChanges++
this.syncState.localVersion++
this.saveToLocalStorage(state)
this.broadcastState()
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
}
this.debounceTimeout = setTimeout(() => {
this.syncToServer(state)
}, this.options.debounceMs)
}
async forceSync(state: SDKState): Promise<void> {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
this.debounceTimeout = null
}
await this.syncToServer(state)
}
private async syncToServer(state: SDKState): Promise<void> {
if (!this.isOnline) {
this.syncState.status = 'offline'
return
}
this.syncState.status = 'syncing'
this.callbacks.onSyncStart?.()
try {
const response = await this.client.saveState(state, this.syncState.serverVersion)
this.syncState = {
...this.syncState,
status: 'idle',
lastSyncedAt: new Date(),
serverVersion: response.version,
pendingChanges: 0,
error: null,
}
this.pendingState = null
this.broadcastSyncComplete(response.version)
this.callbacks.onSyncComplete?.(state)
} catch (error) {
if ((error as { status?: number }).status === 409) {
await this.handleConflict(state)
} else {
this.syncState.status = 'error'
this.syncState.error = (error as Error).message
this.callbacks.onSyncError?.(error as Error)
}
}
}
async loadFromServer(): Promise<SDKState | null> {
if (!this.isOnline) {
return this.loadFromLocalStorage()
}
try {
const response = await this.client.getState()
if (response) {
this.syncState.serverVersion = response.version
this.syncState.localVersion = response.version
this.saveToLocalStorage(response.state)
return response.state
}
return this.loadFromLocalStorage()
} catch (error) {
console.error('Failed to load from server:', error)
return this.loadFromLocalStorage()
}
}
// ---------------------------------------------------------------------------
// Conflict Resolution
// ---------------------------------------------------------------------------
private async handleConflict(localState: SDKState): Promise<void> {
this.syncState.status = 'conflict'
try {
const serverResponse = await this.client.getState()
if (!serverResponse) {
await this.client.saveState(localState)
return
}
const serverState = serverResponse.state
this.callbacks.onConflict?.(localState, serverState)
const resolution = await this.options.conflictHandler(localState, serverState)
let resolvedState: SDKState
switch (resolution.strategy) {
case 'local':
resolvedState = localState
break
case 'server':
resolvedState = serverState
break
case 'merge':
resolvedState = resolution.mergedState || localState
break
}
const response = await this.client.saveState(resolvedState)
this.syncState.serverVersion = response.version
this.syncState.localVersion = response.version
this.saveToLocalStorage(resolvedState)
this.syncState.status = 'idle'
this.callbacks.onSyncComplete?.(resolvedState)
} catch (error) {
this.syncState.status = 'error'
this.syncState.error = (error as Error).message
this.callbacks.onSyncError?.(error as Error)
}
}
private async defaultConflictHandler(
local: SDKState,
server: SDKState
): Promise<ConflictResolution> {
const localTime = new Date(local.lastModified).getTime()
const serverTime = new Date(server.lastModified).getTime()
if (localTime > serverTime) {
return { strategy: 'local' }
}
const mergedState: SDKState = {
...server,
preferences: local.preferences,
commandBarHistory: [
...local.commandBarHistory,
...server.commandBarHistory.filter(
h => !local.commandBarHistory.some(lh => lh.id === h.id)
),
].slice(0, 50),
recentSearches: [...new Set([...local.recentSearches, ...server.recentSearches])].slice(
0,
20
),
}
return { strategy: 'merge', mergedState }
}
// ---------------------------------------------------------------------------
// Getters & Cleanup
// ---------------------------------------------------------------------------
getSyncState(): SyncState {
return { ...this.syncState }
}
isOnlineStatus(): boolean {
return this.isOnline
}
hasPendingChanges(): boolean {
return this.syncState.pendingChanges > 0 || this.pendingState !== null
}
destroy(): void {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
}
if (this.broadcastChannel) {
this.broadcastChannel.close()
}
}
}
// =============================================================================
// FACTORY
// =============================================================================
export function createStateSyncManager(
client: ComplianceClient,
tenantId: string,
options?: SyncOptions,
callbacks?: SyncCallbacks
): StateSyncManager {
return new StateSyncManager(client, tenantId, options, callbacks)
}

View File

@@ -0,0 +1,262 @@
/**
* Utility Functions
*/
// =============================================================================
// ID GENERATION
// =============================================================================
export function generateId(prefix?: string): string {
const timestamp = Date.now().toString(36)
const random = Math.random().toString(36).substring(2, 9)
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`
}
export function generateUUID(): string {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
// Fallback for older environments
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}
// =============================================================================
// DATE UTILITIES
// =============================================================================
export function formatDate(date: Date | string, locale = 'de-DE'): string {
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString(locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
}
export function formatDateTime(date: Date | string, locale = 'de-DE'): string {
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleString(locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
}
export function isDateExpired(date: Date | string): boolean {
const d = typeof date === 'string' ? new Date(date) : date
return d < new Date()
}
export function addDays(date: Date, days: number): Date {
const result = new Date(date)
result.setDate(result.getDate() + days)
return result
}
export function daysBetween(date1: Date, date2: Date): number {
const oneDay = 24 * 60 * 60 * 1000
return Math.round(Math.abs((date1.getTime() - date2.getTime()) / oneDay))
}
// =============================================================================
// STRING UTILITIES
// =============================================================================
export function truncate(str: string, maxLength: number, suffix = '...'): string {
if (str.length <= maxLength) return str
return str.substring(0, maxLength - suffix.length) + suffix
}
export function slugify(str: string): string {
return str
.toLowerCase()
.replace(/[äöü]/g, c => ({ ä: 'ae', ö: 'oe', ü: 'ue' })[c] || c)
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}
// =============================================================================
// ARRAY UTILITIES
// =============================================================================
export function groupBy<T, K extends string | number>(
array: T[],
keyFn: (item: T) => K
): Record<K, T[]> {
return array.reduce(
(groups, item) => {
const key = keyFn(item)
if (!groups[key]) {
groups[key] = []
}
groups[key].push(item)
return groups
},
{} as Record<K, T[]>
)
}
export function uniqueBy<T>(array: T[], keyFn: (item: T) => unknown): T[] {
const seen = new Set()
return array.filter(item => {
const key = keyFn(item)
if (seen.has(key)) return false
seen.add(key)
return true
})
}
export function sortBy<T>(array: T[], keyFn: (item: T) => number | string, desc = false): T[] {
return [...array].sort((a, b) => {
const aKey = keyFn(a)
const bKey = keyFn(b)
if (aKey < bKey) return desc ? 1 : -1
if (aKey > bKey) return desc ? -1 : 1
return 0
})
}
// =============================================================================
// OBJECT UTILITIES
// =============================================================================
export function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') return obj
if (obj instanceof Date) return new Date(obj.getTime()) as T
if (Array.isArray(obj)) return obj.map(item => deepClone(item)) as T
return Object.fromEntries(
Object.entries(obj as object).map(([key, value]) => [key, deepClone(value)])
) as T
}
export function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
const result = { ...target }
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const sourceValue = source[key]
const targetValue = target[key]
if (
typeof sourceValue === 'object' &&
sourceValue !== null &&
!Array.isArray(sourceValue) &&
typeof targetValue === 'object' &&
targetValue !== null &&
!Array.isArray(targetValue)
) {
result[key] = deepMerge(
targetValue as Record<string, unknown>,
sourceValue as Record<string, unknown>
) as T[Extract<keyof T, string>]
} else {
result[key] = sourceValue as T[Extract<keyof T, string>]
}
}
}
return result
}
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
return keys.reduce(
(result, key) => {
if (key in obj) {
result[key] = obj[key]
}
return result
},
{} as Pick<T, K>
)
}
export function omit<T extends object, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
const result = { ...obj }
keys.forEach(key => delete result[key])
return result
}
// =============================================================================
// VALIDATION UTILITIES
// =============================================================================
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
export function isValidUrl(url: string): boolean {
try {
new URL(url)
return true
} catch {
return false
}
}
export function isEmpty(value: unknown): boolean {
if (value === null || value === undefined) return true
if (typeof value === 'string') return value.trim().length === 0
if (Array.isArray(value)) return value.length === 0
if (typeof value === 'object') return Object.keys(value).length === 0
return false
}
// =============================================================================
// ASYNC UTILITIES
// =============================================================================
export function debounce<T extends (...args: unknown[]) => unknown>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout>
return (...args: Parameters<T>) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}
export function throttle<T extends (...args: unknown[]) => unknown>(
fn: T,
limit: number
): (...args: Parameters<T>) => void {
let lastCall = 0
return (...args: Parameters<T>) => {
const now = Date.now()
if (now - lastCall >= limit) {
lastCall = now
fn(...args)
}
}
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
export async function retry<T>(
fn: () => Promise<T>,
maxRetries: number,
delay: number
): Promise<T> {
let lastError: Error | null = null
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn()
} catch (error) {
lastError = error as Error
if (i < maxRetries) {
await sleep(delay * Math.pow(2, i))
}
}
}
throw lastError
}

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts', 'src/client.ts', 'src/state.ts', 'src/sync.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
external: ['@breakpilot/compliance-sdk-types'],
})

View File

@@ -0,0 +1,55 @@
{
"name": "@breakpilot/compliance-sdk-react",
"version": "1.0.0",
"description": "React components and hooks for BreakPilot Compliance SDK",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./hooks": {
"import": "./dist/hooks.mjs",
"require": "./dist/hooks.js",
"types": "./dist/hooks.d.ts"
},
"./components": {
"import": "./dist/components.mjs",
"require": "./dist/components.js",
"types": "./dist/components.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"clean": "rm -rf dist"
},
"dependencies": {
"@breakpilot/compliance-sdk-core": "workspace:*",
"@breakpilot/compliance-sdk-types": "workspace:*"
},
"devDependencies": {
"@testing-library/react": "^14.1.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsup": "^8.0.1",
"typescript": "^5.3.3",
"vitest": "^1.2.0"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
},
"publishConfig": {
"access": "restricted"
}
}

View File

@@ -0,0 +1,12 @@
/**
* React Components
*
* Pre-built UI components for common compliance functionality
*/
// Re-export all components
export { ConsentBanner, type ConsentBannerProps } from './components/ConsentBanner'
export { DSRPortal, type DSRPortalProps } from './components/DSRPortal'
export { ComplianceDashboard, type ComplianceDashboardProps } from './components/ComplianceDashboard'
export { ComplianceScore, type ComplianceScoreProps } from './components/ComplianceScore'
export { RiskMatrix, type RiskMatrixProps } from './components/RiskMatrix'

View File

@@ -0,0 +1,264 @@
'use client'
import React from 'react'
import { useCompliance } from '../provider'
import { ComplianceScore } from './ComplianceScore'
import { RiskMatrix } from './RiskMatrix'
export interface ComplianceDashboardProps {
showScore?: boolean
showRisks?: boolean
showProgress?: boolean
showObligations?: boolean
className?: string
style?: React.CSSProperties
}
export function ComplianceDashboard({
showScore = true,
showRisks = true,
showProgress = true,
showObligations = true,
className,
style,
}: ComplianceDashboardProps) {
const { state, compliance, completionPercentage, phase1Completion, phase2Completion } =
useCompliance()
const score = compliance.calculateComplianceScore()
const criticalRisks = compliance.getCriticalRisks()
const upcomingObligations = compliance.getUpcomingObligations()
const overdueObligations = compliance.getOverdueObligations()
const containerStyle: React.CSSProperties = {
fontFamily: 'system-ui, -apple-system, sans-serif',
...style,
}
const cardStyle: React.CSSProperties = {
backgroundColor: '#fff',
borderRadius: '8px',
padding: '20px',
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
marginBottom: '20px',
}
const gridStyle: React.CSSProperties = {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
gap: '20px',
marginBottom: '20px',
}
const statStyle: React.CSSProperties = {
...cardStyle,
textAlign: 'center',
}
const statValueStyle: React.CSSProperties = {
fontSize: '36px',
fontWeight: 700,
margin: '10px 0',
}
const statLabelStyle: React.CSSProperties = {
fontSize: '14px',
color: '#666',
}
return (
<div style={containerStyle} className={className}>
<h1 style={{ margin: '0 0 20px' }}>Compliance Dashboard</h1>
{/* Stats Grid */}
<div style={gridStyle}>
{showScore && (
<div style={statStyle}>
<div style={statLabelStyle}>Compliance Score</div>
<div style={{ ...statValueStyle, color: score.overall >= 70 ? '#16a34a' : '#dc2626' }}>
{score.overall}%
</div>
<div style={statLabelStyle}>
Trend:{' '}
{score.trend === 'UP' ? '↑ Steigend' : score.trend === 'DOWN' ? '↓ Fallend' : '→ Stabil'}
</div>
</div>
)}
{showProgress && (
<>
<div style={statStyle}>
<div style={statLabelStyle}>Gesamtfortschritt</div>
<div style={statValueStyle}>{completionPercentage}%</div>
<div style={statLabelStyle}>
{state.completedSteps.length} von 19 Schritten abgeschlossen
</div>
</div>
<div style={statStyle}>
<div style={statLabelStyle}>Phase 1: Assessment</div>
<div style={statValueStyle}>{phase1Completion}%</div>
<div style={{ height: '8px', backgroundColor: '#e5e5e5', borderRadius: '4px' }}>
<div
style={{
height: '100%',
width: `${phase1Completion}%`,
backgroundColor: '#3b82f6',
borderRadius: '4px',
}}
/>
</div>
</div>
<div style={statStyle}>
<div style={statLabelStyle}>Phase 2: Dokumentation</div>
<div style={statValueStyle}>{phase2Completion}%</div>
<div style={{ height: '8px', backgroundColor: '#e5e5e5', borderRadius: '4px' }}>
<div
style={{
height: '100%',
width: `${phase2Completion}%`,
backgroundColor: '#8b5cf6',
borderRadius: '4px',
}}
/>
</div>
</div>
</>
)}
</div>
{/* Main Content Grid */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
{/* Score Breakdown */}
{showScore && (
<div style={cardStyle}>
<h3 style={{ margin: '0 0 15px' }}>Score nach Regulierung</h3>
{Object.entries(score.byRegulation).map(([reg, value]) => (
<div
key={reg}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '10px 0',
borderBottom: '1px solid #eee',
}}
>
<span>{reg}</span>
<span style={{ fontWeight: 600, color: value >= 70 ? '#16a34a' : '#dc2626' }}>
{value}%
</span>
</div>
))}
</div>
)}
{/* Obligations */}
{showObligations && (
<div style={cardStyle}>
<h3 style={{ margin: '0 0 15px' }}>Anstehende Pflichten</h3>
{overdueObligations.length > 0 && (
<div
style={{
padding: '10px',
backgroundColor: '#fef2f2',
borderRadius: '4px',
marginBottom: '10px',
}}
>
<strong style={{ color: '#dc2626' }}>
{overdueObligations.length} überfällige Pflichten!
</strong>
</div>
)}
{upcomingObligations.length === 0 && overdueObligations.length === 0 ? (
<p style={{ color: '#666' }}>Keine anstehenden Pflichten in den nächsten 30 Tagen.</p>
) : (
<div>
{[...overdueObligations, ...upcomingObligations].slice(0, 5).map(o => (
<div
key={o.id}
style={{
padding: '10px',
borderBottom: '1px solid #eee',
}}
>
<div style={{ fontWeight: 500 }}>{o.title}</div>
<div style={{ fontSize: '12px', color: '#666' }}>
{o.regulationCode} Art. {o.article}
</div>
{o.deadline && (
<div
style={{
fontSize: '12px',
color: new Date(o.deadline) < new Date() ? '#dc2626' : '#666',
}}
>
Frist: {new Date(o.deadline).toLocaleDateString('de-DE')}
</div>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
{/* Risk Matrix */}
{showRisks && state.risks.length > 0 && (
<div style={cardStyle}>
<h3 style={{ margin: '0 0 15px' }}>Risikomatrix</h3>
<RiskMatrix risks={state.risks} />
{criticalRisks.length > 0 && (
<div
style={{
marginTop: '15px',
padding: '10px',
backgroundColor: '#fef2f2',
borderRadius: '4px',
}}
>
<strong style={{ color: '#dc2626' }}>
{criticalRisks.length} kritische/hohe Risiken ohne Mitigation
</strong>
</div>
)}
</div>
)}
{/* Controls Summary */}
<div style={cardStyle}>
<h3 style={{ margin: '0 0 15px' }}>Controls Übersicht</h3>
<div style={gridStyle}>
<div>
<div style={{ fontSize: '24px', fontWeight: 700 }}>{state.controls.length}</div>
<div style={{ color: '#666' }}>Gesamt</div>
</div>
<div>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#16a34a' }}>
{state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length}
</div>
<div style={{ color: '#666' }}>Implementiert</div>
</div>
<div>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#f59e0b' }}>
{state.controls.filter(c => c.implementationStatus === 'PARTIAL').length}
</div>
<div style={{ color: '#666' }}>Teilweise</div>
</div>
<div>
<div style={{ fontSize: '24px', fontWeight: 700, color: '#dc2626' }}>
{state.controls.filter(c => c.implementationStatus === 'NOT_IMPLEMENTED').length}
</div>
<div style={{ color: '#666' }}>Offen</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,111 @@
'use client'
import React from 'react'
export interface ComplianceScoreProps {
score: number
size?: 'small' | 'medium' | 'large'
showLabel?: boolean
className?: string
style?: React.CSSProperties
}
export function ComplianceScore({
score,
size = 'medium',
showLabel = true,
className,
style,
}: ComplianceScoreProps) {
const sizes = {
small: { width: 80, strokeWidth: 6, fontSize: 18 },
medium: { width: 120, strokeWidth: 8, fontSize: 24 },
large: { width: 160, strokeWidth: 10, fontSize: 32 },
}
const { width, strokeWidth, fontSize } = sizes[size]
const radius = (width - strokeWidth) / 2
const circumference = radius * 2 * Math.PI
const offset = circumference - (score / 100) * circumference
const getColor = (score: number): string => {
if (score >= 80) return '#16a34a' // Green
if (score >= 60) return '#f59e0b' // Yellow
if (score >= 40) return '#f97316' // Orange
return '#dc2626' // Red
}
const color = getColor(score)
return (
<div
style={{
display: 'inline-flex',
flexDirection: 'column',
alignItems: 'center',
...style,
}}
className={className}
>
<svg
width={width}
height={width}
style={{ transform: 'rotate(-90deg)' }}
>
{/* Background circle */}
<circle
cx={width / 2}
cy={width / 2}
r={radius}
fill="none"
stroke="#e5e5e5"
strokeWidth={strokeWidth}
/>
{/* Progress circle */}
<circle
cx={width / 2}
cy={width / 2}
r={radius}
fill="none"
stroke={color}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
style={{
transition: 'stroke-dashoffset 0.5s ease-in-out',
}}
/>
{/* Score text */}
<text
x="50%"
y="50%"
dominantBaseline="middle"
textAnchor="middle"
style={{
transform: 'rotate(90deg)',
transformOrigin: 'center',
fontSize: `${fontSize}px`,
fontWeight: 700,
fill: color,
}}
>
{score}%
</text>
</svg>
{showLabel && (
<div
style={{
marginTop: '8px',
fontSize: '14px',
color: '#666',
textAlign: 'center',
}}
>
Compliance Score
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,213 @@
'use client'
import React, { useState, useCallback } from 'react'
import { useCompliance } from '../provider'
import type { ConsentPurpose, CookieBannerPosition, CookieBannerTheme } from '@breakpilot/compliance-sdk-types'
export interface ConsentBannerProps {
position?: CookieBannerPosition
theme?: CookieBannerTheme
onConsentChange?: (consents: Record<ConsentPurpose, boolean>) => void
privacyPolicyUrl?: string
imprintUrl?: string
className?: string
style?: React.CSSProperties
}
export function ConsentBanner({
position = 'BOTTOM',
theme = 'LIGHT',
onConsentChange,
privacyPolicyUrl = '/privacy',
imprintUrl = '/imprint',
className,
style,
}: ConsentBannerProps) {
const { state, dispatch } = useCompliance()
const [showSettings, setShowSettings] = useState(false)
const [consents, setConsents] = useState<Record<ConsentPurpose, boolean>>({
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
})
const config = state.cookieBanner
const handleAcceptAll = useCallback(() => {
const allConsents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: true,
ANALYTICS: true,
MARKETING: true,
PERSONALIZATION: true,
THIRD_PARTY: true,
}
setConsents(allConsents)
onConsentChange?.(allConsents)
// Would save to backend here
}, [onConsentChange])
const handleRejectAll = useCallback(() => {
const minimalConsents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
setConsents(minimalConsents)
onConsentChange?.(minimalConsents)
}, [onConsentChange])
const handleSaveSettings = useCallback(() => {
onConsentChange?.(consents)
setShowSettings(false)
}, [consents, onConsentChange])
const handleConsentToggle = useCallback((purpose: ConsentPurpose) => {
if (purpose === 'ESSENTIAL') return // Cannot disable essential
setConsents(prev => ({ ...prev, [purpose]: !prev[purpose] }))
}, [])
const positionStyles: Record<CookieBannerPosition, React.CSSProperties> = {
TOP: { top: 0, left: 0, right: 0 },
BOTTOM: { bottom: 0, left: 0, right: 0 },
CENTER: { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' },
}
const themeStyles: Record<CookieBannerTheme, React.CSSProperties> = {
LIGHT: { backgroundColor: '#ffffff', color: '#1a1a1a' },
DARK: { backgroundColor: '#1a1a1a', color: '#ffffff' },
CUSTOM: config?.customColors
? {
backgroundColor: config.customColors.background,
color: config.customColors.text,
}
: {},
}
const bannerStyle: React.CSSProperties = {
position: 'fixed',
zIndex: 9999,
padding: '20px',
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
fontFamily: 'system-ui, -apple-system, sans-serif',
...positionStyles[position],
...themeStyles[theme],
...style,
}
const buttonBaseStyle: React.CSSProperties = {
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: 500,
marginRight: '10px',
}
const primaryButtonStyle: React.CSSProperties = {
...buttonBaseStyle,
backgroundColor: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
color: theme === 'DARK' ? '#1a1a1a' : '#ffffff',
}
const secondaryButtonStyle: React.CSSProperties = {
...buttonBaseStyle,
backgroundColor: 'transparent',
border: `1px solid ${theme === 'DARK' ? '#ffffff' : '#1a1a1a'}`,
color: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
}
if (showSettings) {
return (
<div style={bannerStyle} className={className}>
<h3 style={{ margin: '0 0 15px', fontSize: '18px' }}>
{config?.texts?.settings || 'Cookie-Einstellungen'}
</h3>
<div style={{ marginBottom: '20px' }}>
{Object.entries({
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
}).map(([key, { name, description }]) => (
<div
key={key}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 0',
borderBottom: `1px solid ${theme === 'DARK' ? '#333' : '#eee'}`,
}}
>
<div>
<div style={{ fontWeight: 500 }}>{name}</div>
<div style={{ fontSize: '12px', opacity: 0.7 }}>{description}</div>
</div>
<label style={{ cursor: key === 'ESSENTIAL' ? 'not-allowed' : 'pointer' }}>
<input
type="checkbox"
checked={consents[key as ConsentPurpose]}
onChange={() => handleConsentToggle(key as ConsentPurpose)}
disabled={key === 'ESSENTIAL'}
style={{ width: '20px', height: '20px' }}
/>
</label>
</div>
))}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
<button style={primaryButtonStyle} onClick={handleSaveSettings}>
{config?.texts?.save || 'Einstellungen speichern'}
</button>
<button style={secondaryButtonStyle} onClick={() => setShowSettings(false)}>
Zurück
</button>
</div>
</div>
)
}
return (
<div style={bannerStyle} className={className}>
<div style={{ marginBottom: '15px' }}>
<h3 style={{ margin: '0 0 10px', fontSize: '18px' }}>
{config?.texts?.title || 'Cookie-Einwilligung'}
</h3>
<p style={{ margin: 0, fontSize: '14px', opacity: 0.8 }}>
{config?.texts?.description ||
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.'}
</p>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
<button style={primaryButtonStyle} onClick={handleAcceptAll}>
{config?.texts?.acceptAll || 'Alle akzeptieren'}
</button>
<button style={secondaryButtonStyle} onClick={handleRejectAll}>
{config?.texts?.rejectAll || 'Nur notwendige'}
</button>
<button style={secondaryButtonStyle} onClick={() => setShowSettings(true)}>
{config?.texts?.settings || 'Einstellungen'}
</button>
<div style={{ marginLeft: 'auto', fontSize: '12px' }}>
<a href={privacyPolicyUrl} style={{ marginRight: '15px', color: 'inherit' }}>
Datenschutz
</a>
<a href={imprintUrl} style={{ color: 'inherit' }}>
Impressum
</a>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,240 @@
'use client'
import React, { useState, useCallback } from 'react'
import { useCompliance } from '../provider'
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
export interface DSRPortalProps {
onSubmit?: (type: DSRRequestType, email: string, name: string) => void
className?: string
style?: React.CSSProperties
}
const DSR_TYPES: Record<DSRRequestType, { name: string; description: string }> = {
ACCESS: {
name: 'Auskunft (Art. 15)',
description: 'Welche Daten haben Sie über mich gespeichert?',
},
RECTIFICATION: {
name: 'Berichtigung (Art. 16)',
description: 'Korrigieren Sie falsche Daten über mich.',
},
ERASURE: {
name: 'Löschung (Art. 17)',
description: 'Löschen Sie alle meine personenbezogenen Daten.',
},
PORTABILITY: {
name: 'Datenübertragbarkeit (Art. 20)',
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
},
RESTRICTION: {
name: 'Einschränkung (Art. 18)',
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
},
OBJECTION: {
name: 'Widerspruch (Art. 21)',
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
},
}
export function DSRPortal({ onSubmit, className, style }: DSRPortalProps) {
const { dsgvo } = useCompliance()
const [selectedType, setSelectedType] = useState<DSRRequestType | null>(null)
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const [additionalInfo, setAdditionalInfo] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitted, setSubmitted] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault()
if (!selectedType || !email || !name) return
setIsSubmitting(true)
setError(null)
try {
await dsgvo.submitDSR(selectedType, email, name)
onSubmit?.(selectedType, email, name)
setSubmitted(true)
} catch (err) {
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten')
} finally {
setIsSubmitting(false)
}
},
[selectedType, email, name, dsgvo, onSubmit]
)
const containerStyle: React.CSSProperties = {
fontFamily: 'system-ui, -apple-system, sans-serif',
maxWidth: '600px',
margin: '0 auto',
padding: '20px',
...style,
}
const inputStyle: React.CSSProperties = {
width: '100%',
padding: '12px',
fontSize: '14px',
border: '1px solid #ddd',
borderRadius: '4px',
marginBottom: '15px',
boxSizing: 'border-box',
}
const buttonStyle: React.CSSProperties = {
padding: '12px 24px',
fontSize: '16px',
fontWeight: 500,
color: '#fff',
backgroundColor: '#1a1a1a',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
width: '100%',
}
if (submitted) {
return (
<div style={containerStyle} className={className}>
<div
style={{
textAlign: 'center',
padding: '40px 20px',
backgroundColor: '#f0fdf4',
borderRadius: '8px',
}}
>
<div style={{ fontSize: '48px', marginBottom: '20px' }}></div>
<h2 style={{ margin: '0 0 10px', color: '#166534' }}>Anfrage eingereicht</h2>
<p style={{ margin: 0, color: '#166534' }}>
Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine
Bestätigung per E-Mail an {email}.
</p>
</div>
</div>
)
}
return (
<div style={containerStyle} className={className}>
<h2 style={{ margin: '0 0 10px' }}>Betroffenenrechte-Portal</h2>
<p style={{ margin: '0 0 20px', color: '#666' }}>
Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und
füllen Sie das Formular aus.
</p>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', fontWeight: 500, marginBottom: '10px' }}>
Art der Anfrage *
</label>
<div style={{ display: 'grid', gap: '10px' }}>
{Object.entries(DSR_TYPES).map(([type, { name, description }]) => (
<label
key={type}
style={{
display: 'flex',
padding: '15px',
border: `2px solid ${selectedType === type ? '#1a1a1a' : '#ddd'}`,
borderRadius: '8px',
cursor: 'pointer',
backgroundColor: selectedType === type ? '#f5f5f5' : '#fff',
}}
>
<input
type="radio"
name="dsrType"
value={type}
checked={selectedType === type}
onChange={() => setSelectedType(type as DSRRequestType)}
style={{ marginRight: '15px' }}
/>
<div>
<div style={{ fontWeight: 500 }}>{name}</div>
<div style={{ fontSize: '13px', color: '#666' }}>{description}</div>
</div>
</label>
))}
</div>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
Ihr Name *
</label>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
required
placeholder="Max Mustermann"
style={inputStyle}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
E-Mail-Adresse *
</label>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
placeholder="max@example.com"
style={inputStyle}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', fontWeight: 500, marginBottom: '5px' }}>
Zusätzliche Informationen (optional)
</label>
<textarea
value={additionalInfo}
onChange={e => setAdditionalInfo(e.target.value)}
placeholder="Weitere Details zu Ihrer Anfrage..."
rows={4}
style={{ ...inputStyle, resize: 'vertical' }}
/>
</div>
{error && (
<div
style={{
padding: '12px',
backgroundColor: '#fef2f2',
color: '#dc2626',
borderRadius: '4px',
marginBottom: '15px',
}}
>
{error}
</div>
)}
<button
type="submit"
disabled={!selectedType || !email || !name || isSubmitting}
style={{
...buttonStyle,
opacity: !selectedType || !email || !name || isSubmitting ? 0.5 : 1,
cursor: !selectedType || !email || !name || isSubmitting ? 'not-allowed' : 'pointer',
}}
>
{isSubmitting ? 'Wird gesendet...' : 'Anfrage einreichen'}
</button>
</form>
<p style={{ marginTop: '20px', fontSize: '12px', color: '#666' }}>
Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen
Fällen kann diese Frist um weitere zwei Monate verlängert werden.
</p>
</div>
)
}

View File

@@ -0,0 +1,193 @@
'use client'
import React from 'react'
import type { Risk, RiskLikelihood, RiskImpact } from '@breakpilot/compliance-sdk-types'
export interface RiskMatrixProps {
risks: Risk[]
onRiskClick?: (risk: Risk) => void
className?: string
style?: React.CSSProperties
}
export function RiskMatrix({ risks, onRiskClick, className, style }: RiskMatrixProps) {
const getColor = (likelihood: number, impact: number): string => {
const score = likelihood * impact
if (score >= 20) return '#dc2626' // Critical - Red
if (score >= 12) return '#f97316' // High - Orange
if (score >= 6) return '#f59e0b' // Medium - Yellow
return '#16a34a' // Low - Green
}
const getCellRisks = (likelihood: RiskLikelihood, impact: RiskImpact): Risk[] => {
return risks.filter(r => r.likelihood === likelihood && r.impact === impact)
}
const containerStyle: React.CSSProperties = {
fontFamily: 'system-ui, -apple-system, sans-serif',
...style,
}
const gridStyle: React.CSSProperties = {
display: 'grid',
gridTemplateColumns: '60px repeat(5, 1fr)',
gridTemplateRows: '30px repeat(5, 60px)',
gap: '2px',
backgroundColor: '#e5e5e5',
padding: '2px',
borderRadius: '8px',
}
const cellStyle: React.CSSProperties = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#fff',
position: 'relative',
}
const headerStyle: React.CSSProperties = {
...cellStyle,
fontWeight: 600,
fontSize: '12px',
color: '#666',
}
const likelihoodLabels = ['Sehr niedrig', 'Niedrig', 'Mittel', 'Hoch', 'Sehr hoch']
const impactLabels = ['1', '2', '3', '4', '5']
return (
<div style={containerStyle} className={className}>
<div style={{ display: 'flex', marginBottom: '10px' }}>
<div style={{ flex: 1 }}>
<span style={{ fontWeight: 600 }}>Risikomatrix</span>
<span style={{ marginLeft: '15px', color: '#666', fontSize: '14px' }}>
{risks.length} Risiken
</span>
</div>
<div style={{ display: 'flex', gap: '10px', fontSize: '12px' }}>
<span>
<span
style={{
display: 'inline-block',
width: '12px',
height: '12px',
backgroundColor: '#16a34a',
borderRadius: '2px',
marginRight: '4px',
}}
/>
Niedrig
</span>
<span>
<span
style={{
display: 'inline-block',
width: '12px',
height: '12px',
backgroundColor: '#f59e0b',
borderRadius: '2px',
marginRight: '4px',
}}
/>
Mittel
</span>
<span>
<span
style={{
display: 'inline-block',
width: '12px',
height: '12px',
backgroundColor: '#f97316',
borderRadius: '2px',
marginRight: '4px',
}}
/>
Hoch
</span>
<span>
<span
style={{
display: 'inline-block',
width: '12px',
height: '12px',
backgroundColor: '#dc2626',
borderRadius: '2px',
marginRight: '4px',
}}
/>
Kritisch
</span>
</div>
</div>
<div style={gridStyle}>
{/* Header row */}
<div style={headerStyle} />
{impactLabels.map((label, i) => (
<div key={`impact-${i}`} style={headerStyle}>
Auswirkung {label}
</div>
))}
{/* Data rows (reversed so high likelihood is at top) */}
{[5, 4, 3, 2, 1].map(likelihood => (
<React.Fragment key={`row-${likelihood}`}>
<div style={headerStyle}>{likelihoodLabels[likelihood - 1]}</div>
{[1, 2, 3, 4, 5].map(impact => {
const cellRisks = getCellRisks(likelihood as RiskLikelihood, impact as RiskImpact)
return (
<div
key={`cell-${likelihood}-${impact}`}
style={{
...cellStyle,
backgroundColor: getColor(likelihood, impact),
opacity: cellRisks.length > 0 ? 1 : 0.3,
cursor: cellRisks.length > 0 ? 'pointer' : 'default',
}}
onClick={() => {
if (cellRisks.length > 0 && onRiskClick) {
onRiskClick(cellRisks[0])
}
}}
>
{cellRisks.length > 0 && (
<div
style={{
width: '24px',
height: '24px',
borderRadius: '50%',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 600,
fontSize: '12px',
color: '#1a1a1a',
}}
>
{cellRisks.length}
</div>
)}
</div>
)
})}
</React.Fragment>
))}
</div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
marginTop: '10px',
fontSize: '12px',
color: '#666',
}}
>
<span>Wahrscheinlichkeit </span>
<span>Auswirkung </span>
</div>
</div>
)
}

View File

@@ -0,0 +1,474 @@
'use client'
import { useContext, useMemo, useCallback } from 'react'
import { ComplianceContext, type ComplianceContextValue } from './provider'
import type {
SDKState,
SDKAction,
Control,
Evidence,
Risk,
Requirement,
Obligation,
TOM,
ProcessingActivity,
ConsentPurpose,
} from '@breakpilot/compliance-sdk-types'
// =============================================================================
// MAIN HOOK
// =============================================================================
export function useCompliance(): ComplianceContextValue {
const context = useContext(ComplianceContext)
if (!context) {
throw new Error('useCompliance must be used within ComplianceProvider')
}
return context
}
// =============================================================================
// STATE HOOK
// =============================================================================
export function useComplianceState(): SDKState {
const { state } = useCompliance()
return state
}
export function useComplianceDispatch(): React.Dispatch<SDKAction> {
const { dispatch } = useCompliance()
return dispatch
}
// =============================================================================
// DSGVO HOOKS
// =============================================================================
export function useDSGVO() {
const { dsgvo, state } = useCompliance()
return useMemo(
() => ({
// DSR
dsrRequests: state.dsrRequests,
dsrConfig: state.dsrConfig,
submitDSR: dsgvo.submitDSR.bind(dsgvo),
// Consent
consents: state.consents,
hasConsent: dsgvo.hasConsent.bind(dsgvo),
getConsentsByUserId: dsgvo.getConsentsByUserId.bind(dsgvo),
// VVT
processingActivities: state.vvt,
getProcessingActivityById: dsgvo.getProcessingActivityById.bind(dsgvo),
// DSFA
dsfa: state.dsfa,
isDSFARequired: dsgvo.isDSFARequired.bind(dsgvo),
// TOMs
toms: state.toms,
getTOMsByCategory: dsgvo.getTOMsByCategory.bind(dsgvo),
getTOMScore: dsgvo.getTOMScore.bind(dsgvo),
// Retention
retentionPolicies: state.retentionPolicies,
getUpcomingDeletions: dsgvo.getUpcomingDeletions.bind(dsgvo),
// Cookie Banner
cookieBanner: state.cookieBanner,
generateCookieBannerCode: dsgvo.generateCookieBannerCode.bind(dsgvo),
}),
[dsgvo, state]
)
}
export function useConsent(userId: string) {
const { dsgvo, state } = useCompliance()
return useMemo(() => {
const userConsents = state.consents.filter(c => c.userId === userId)
return {
consents: userConsents,
hasConsent: (purpose: ConsentPurpose) => dsgvo.hasConsent(userId, purpose),
hasAnalyticsConsent: dsgvo.hasConsent(userId, 'ANALYTICS'),
hasMarketingConsent: dsgvo.hasConsent(userId, 'MARKETING'),
hasFunctionalConsent: dsgvo.hasConsent(userId, 'FUNCTIONAL'),
}
}, [dsgvo, state.consents, userId])
}
export function useDSR() {
const { dsgvo, state, dispatch } = useCompliance()
return useMemo(
() => ({
requests: state.dsrRequests,
config: state.dsrConfig,
submitRequest: dsgvo.submitDSR.bind(dsgvo),
pendingRequests: state.dsrRequests.filter(r => r.status !== 'COMPLETED' && r.status !== 'REJECTED'),
overdueRequests: state.dsrRequests.filter(r => {
if (r.status === 'COMPLETED' || r.status === 'REJECTED') return false
return new Date(r.dueDate) < new Date()
}),
}),
[dsgvo, state.dsrRequests, state.dsrConfig]
)
}
// =============================================================================
// COMPLIANCE HOOKS
// =============================================================================
export function useComplianceModule() {
const { compliance, state } = useCompliance()
return useMemo(
() => ({
// Controls
controls: state.controls,
getControlById: compliance.getControlById.bind(compliance),
getControlsByDomain: compliance.getControlsByDomain.bind(compliance),
getControlsByStatus: compliance.getControlsByStatus.bind(compliance),
controlComplianceRate: compliance.getControlComplianceRate(),
// Evidence
evidence: state.evidence,
getEvidenceByControlId: compliance.getEvidenceByControlId.bind(compliance),
expiringEvidence: compliance.getExpiringEvidence(),
// Requirements
requirements: state.requirements,
getRequirementsByRegulation: compliance.getRequirementsByRegulation.bind(compliance),
requirementComplianceRate: compliance.getRequirementComplianceRate(),
// Obligations
obligations: state.obligations,
upcomingObligations: compliance.getUpcomingObligations(),
overdueObligations: compliance.getOverdueObligations(),
// AI Act
aiActClassification: state.aiActClassification,
aiActRiskCategory: compliance.getAIActRiskCategory(),
isHighRiskAI: compliance.isHighRiskAI(),
// Score
complianceScore: compliance.calculateComplianceScore(),
// Risks
risks: state.risks,
criticalRisks: compliance.getCriticalRisks(),
averageRiskScore: compliance.getAverageRiskScore(),
}),
[compliance, state]
)
}
export function useControls() {
const { compliance, state, dispatch } = useCompliance()
const addControl = useCallback(
(control: Control) => {
dispatch({ type: 'ADD_CONTROL', payload: control })
},
[dispatch]
)
const updateControl = useCallback(
(id: string, data: Partial<Control>) => {
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
},
[dispatch]
)
return useMemo(
() => ({
controls: state.controls,
addControl,
updateControl,
getById: compliance.getControlById.bind(compliance),
getByDomain: compliance.getControlsByDomain.bind(compliance),
getByStatus: compliance.getControlsByStatus.bind(compliance),
implementedCount: state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED').length,
totalCount: state.controls.length,
complianceRate: compliance.getControlComplianceRate(),
}),
[state.controls, compliance, addControl, updateControl]
)
}
export function useEvidence() {
const { compliance, state, dispatch } = useCompliance()
const addEvidence = useCallback(
(evidence: Evidence) => {
dispatch({ type: 'ADD_EVIDENCE', payload: evidence })
},
[dispatch]
)
const updateEvidence = useCallback(
(id: string, data: Partial<Evidence>) => {
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, data } })
},
[dispatch]
)
const deleteEvidence = useCallback(
(id: string) => {
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
},
[dispatch]
)
return useMemo(
() => ({
evidence: state.evidence,
addEvidence,
updateEvidence,
deleteEvidence,
getByControlId: compliance.getEvidenceByControlId.bind(compliance),
expiringEvidence: compliance.getExpiringEvidence(),
activeCount: state.evidence.filter(e => e.status === 'ACTIVE').length,
totalCount: state.evidence.length,
}),
[state.evidence, compliance, addEvidence, updateEvidence, deleteEvidence]
)
}
export function useRisks() {
const { compliance, state, dispatch } = useCompliance()
const addRisk = useCallback(
(risk: Risk) => {
dispatch({ type: 'ADD_RISK', payload: risk })
},
[dispatch]
)
const updateRisk = useCallback(
(id: string, data: Partial<Risk>) => {
dispatch({ type: 'UPDATE_RISK', payload: { id, data } })
},
[dispatch]
)
const deleteRisk = useCallback(
(id: string) => {
dispatch({ type: 'DELETE_RISK', payload: id })
},
[dispatch]
)
return useMemo(
() => ({
risks: state.risks,
addRisk,
updateRisk,
deleteRisk,
criticalRisks: compliance.getCriticalRisks(),
getByStatus: compliance.getRisksByStatus.bind(compliance),
getBySeverity: compliance.getRisksBySeverity.bind(compliance),
averageScore: compliance.getAverageRiskScore(),
}),
[state.risks, compliance, addRisk, updateRisk, deleteRisk]
)
}
// =============================================================================
// RAG HOOKS
// =============================================================================
export function useRAG() {
const { rag } = useCompliance()
return useMemo(
() => ({
search: rag.search.bind(rag),
searchByRegulation: rag.searchByRegulation.bind(rag),
searchByArticle: rag.searchByArticle.bind(rag),
ask: rag.ask.bind(rag),
askAboutRegulation: rag.askAboutRegulation.bind(rag),
explainArticle: rag.explainArticle.bind(rag),
checkCompliance: rag.checkCompliance.bind(rag),
getQuickAnswer: rag.getQuickAnswer.bind(rag),
findRelevantArticles: rag.findRelevantArticles.bind(rag),
availableRegulations: rag.getAvailableRegulations(),
chatHistory: rag.getChatHistory(),
clearChatHistory: rag.clearChatHistory.bind(rag),
startNewSession: rag.startNewSession.bind(rag),
}),
[rag]
)
}
// =============================================================================
// SECURITY HOOKS
// =============================================================================
export function useSecurity() {
const { security, state } = useCompliance()
return useMemo(
() => ({
// SBOM
sbom: state.sbom,
components: security.getComponents(),
vulnerableComponents: security.getVulnerableComponents(),
licenseSummary: security.getLicenseSummary(),
// Issues
issues: state.securityIssues,
openIssues: security.getOpenIssues(),
criticalIssues: security.getCriticalIssues(),
getIssuesBySeverity: security.getIssuesBySeverity.bind(security),
getIssuesByTool: security.getIssuesByTool.bind(security),
// Backlog
backlog: state.securityBacklog,
overdueBacklogItems: security.getOverdueBacklogItems(),
// Scanning
startScan: security.startScan.bind(security),
getScanResult: security.getScanResult.bind(security),
lastScanResult: security.getLastScanResult(),
// Summary
summary: security.getSecuritySummary(),
securityScore: security.getSecurityScore(),
availableTools: security.getAvailableTools(),
}),
[security, state]
)
}
// =============================================================================
// NAVIGATION HOOKS
// =============================================================================
export function useSDKNavigation() {
const {
currentStep,
goToStep,
goToNextStep,
goToPreviousStep,
canGoNext,
canGoPrevious,
completionPercentage,
phase1Completion,
phase2Completion,
state,
} = useCompliance()
return useMemo(
() => ({
currentStep,
currentPhase: state.currentPhase,
completedSteps: state.completedSteps,
goToStep,
goToNextStep,
goToPreviousStep,
canGoNext,
canGoPrevious,
completionPercentage,
phase1Completion,
phase2Completion,
}),
[
currentStep,
state.currentPhase,
state.completedSteps,
goToStep,
goToNextStep,
goToPreviousStep,
canGoNext,
canGoPrevious,
completionPercentage,
phase1Completion,
phase2Completion,
]
)
}
// =============================================================================
// SYNC HOOKS
// =============================================================================
export function useSync() {
const { syncState, forceSyncToServer, isOnline, saveState, loadState } = useCompliance()
return useMemo(
() => ({
status: syncState.status,
lastSyncedAt: syncState.lastSyncedAt,
pendingChanges: syncState.pendingChanges,
error: syncState.error,
isOnline,
isSyncing: syncState.status === 'syncing',
hasConflict: syncState.status === 'conflict',
forceSyncToServer,
saveState,
loadState,
}),
[syncState, isOnline, forceSyncToServer, saveState, loadState]
)
}
// =============================================================================
// CHECKPOINT HOOKS
// =============================================================================
export function useCheckpoints() {
const { validateCheckpoint, overrideCheckpoint, getCheckpointStatus, state } = useCompliance()
return useMemo(
() => ({
checkpoints: state.checkpoints,
validateCheckpoint,
overrideCheckpoint,
getCheckpointStatus,
passedCheckpoints: Object.values(state.checkpoints).filter(c => c.passed).length,
totalCheckpoints: Object.keys(state.checkpoints).length,
}),
[state.checkpoints, validateCheckpoint, overrideCheckpoint, getCheckpointStatus]
)
}
// =============================================================================
// EXPORT HOOKS
// =============================================================================
export function useExport() {
const { exportState } = useCompliance()
return useMemo(
() => ({
exportJSON: () => exportState('json'),
exportPDF: () => exportState('pdf'),
exportZIP: () => exportState('zip'),
exportState,
}),
[exportState]
)
}
// =============================================================================
// COMMAND BAR HOOK
// =============================================================================
export function useCommandBar() {
const { isCommandBarOpen, setCommandBarOpen } = useCompliance()
return useMemo(
() => ({
isOpen: isCommandBarOpen,
open: () => setCommandBarOpen(true),
close: () => setCommandBarOpen(false),
toggle: () => setCommandBarOpen(!isCommandBarOpen),
}),
[isCommandBarOpen, setCommandBarOpen]
)
}

View File

@@ -0,0 +1,14 @@
/**
* @breakpilot/compliance-sdk-react
*
* React components and hooks for BreakPilot Compliance SDK
*/
// Provider
export { ComplianceProvider, ComplianceContext, type ComplianceProviderProps } from './provider'
// Hooks
export * from './hooks'
// Components
export * from './components'

View File

@@ -0,0 +1,539 @@
'use client'
import React, {
createContext,
useContext,
useReducer,
useEffect,
useCallback,
useMemo,
useRef,
useState,
} from 'react'
import {
ComplianceClient,
sdkReducer,
initialState,
StateSyncManager,
createStateSyncManager,
createDSGVOModule,
createComplianceModule,
createRAGModule,
createSecurityModule,
type DSGVOModule,
type ComplianceModule,
type RAGModule,
type SecurityModule,
} from '@breakpilot/compliance-sdk-core'
import type {
SDKState,
SDKAction,
SDKStep,
CheckpointStatus,
SyncState,
UseCaseAssessment,
Risk,
Control,
UserPreferences,
} from '@breakpilot/compliance-sdk-types'
import {
getStepById,
getNextStep,
getPreviousStep,
getCompletionPercentage,
getPhaseCompletionPercentage,
} from '@breakpilot/compliance-sdk-types'
// =============================================================================
// CONTEXT TYPES
// =============================================================================
export interface ComplianceContextValue {
// State
state: SDKState
dispatch: React.Dispatch<SDKAction>
// Client
client: ComplianceClient
// Modules
dsgvo: DSGVOModule
compliance: ComplianceModule
rag: RAGModule
security: SecurityModule
// Navigation
currentStep: SDKStep | undefined
goToStep: (stepId: string) => void
goToNextStep: () => void
goToPreviousStep: () => void
canGoNext: boolean
canGoPrevious: boolean
// Progress
completionPercentage: number
phase1Completion: number
phase2Completion: number
// Checkpoints
validateCheckpoint: (checkpointId: string) => Promise<CheckpointStatus>
overrideCheckpoint: (checkpointId: string, reason: string) => Promise<void>
getCheckpointStatus: (checkpointId: string) => CheckpointStatus | undefined
// State Updates
updateUseCase: (id: string, data: Partial<UseCaseAssessment>) => void
addRisk: (risk: Risk) => void
updateControl: (id: string, data: Partial<Control>) => void
// Persistence
saveState: () => Promise<void>
loadState: () => Promise<void>
resetState: () => void
// Sync
syncState: SyncState
forceSyncToServer: () => Promise<void>
isOnline: boolean
// Export
exportState: (format: 'json' | 'pdf' | 'zip') => Promise<Blob>
// Command Bar
isCommandBarOpen: boolean
setCommandBarOpen: (open: boolean) => void
// Status
isInitialized: boolean
isLoading: boolean
error: Error | null
}
export const ComplianceContext = createContext<ComplianceContextValue | null>(null)
// =============================================================================
// PROVIDER PROPS
// =============================================================================
export interface ComplianceProviderProps {
children: React.ReactNode
apiEndpoint: string
apiKey?: string
tenantId: string
userId?: string
enableBackendSync?: boolean
onNavigate?: (url: string) => void
onError?: (error: Error) => void
}
// =============================================================================
// PROVIDER
// =============================================================================
const SDK_STORAGE_KEY = 'breakpilot-compliance-sdk-state'
export function ComplianceProvider({
children,
apiEndpoint,
apiKey,
tenantId,
userId = 'default',
enableBackendSync = true,
onNavigate,
onError,
}: ComplianceProviderProps) {
const [state, dispatch] = useReducer(sdkReducer, {
...initialState,
tenantId,
userId,
})
const [isCommandBarOpen, setCommandBarOpen] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const [syncState, setSyncState] = useState<SyncState>({
status: 'idle',
lastSyncedAt: null,
localVersion: 0,
serverVersion: 0,
pendingChanges: 0,
error: null,
})
const [isOnline, setIsOnline] = useState(true)
// Refs
const clientRef = useRef<ComplianceClient | null>(null)
const syncManagerRef = useRef<StateSyncManager | null>(null)
// Initialize client
if (!clientRef.current) {
clientRef.current = new ComplianceClient({
apiEndpoint,
apiKey,
tenantId,
onError: err => {
setError(err)
onError?.(err)
},
})
}
const client = clientRef.current
// Modules
const dsgvo = useMemo(
() => createDSGVOModule(client, () => state),
[client, state]
)
const compliance = useMemo(
() => createComplianceModule(client, () => state),
[client, state]
)
const rag = useMemo(() => createRAGModule(client), [client])
const security = useMemo(
() => createSecurityModule(client, () => state),
[client, state]
)
// Initialize sync manager
useEffect(() => {
if (enableBackendSync && typeof window !== 'undefined') {
syncManagerRef.current = createStateSyncManager(
client,
tenantId,
{ debounceMs: 2000, maxRetries: 3 },
{
onSyncStart: () => {
setSyncState(prev => ({ ...prev, status: 'syncing' }))
},
onSyncComplete: syncedState => {
setSyncState(prev => ({
...prev,
status: 'idle',
lastSyncedAt: new Date(),
pendingChanges: 0,
}))
if (new Date(syncedState.lastModified) > new Date(state.lastModified)) {
dispatch({ type: 'SET_STATE', payload: syncedState })
}
},
onSyncError: err => {
setSyncState(prev => ({
...prev,
status: 'error',
error: err.message,
}))
setError(err)
},
onConflict: () => {
setSyncState(prev => ({ ...prev, status: 'conflict' }))
},
onOffline: () => {
setIsOnline(false)
setSyncState(prev => ({ ...prev, status: 'offline' }))
},
onOnline: () => {
setIsOnline(true)
setSyncState(prev => ({ ...prev, status: 'idle' }))
},
}
)
}
return () => {
syncManagerRef.current?.destroy()
}
}, [enableBackendSync, tenantId, client])
// Load initial state
useEffect(() => {
const loadInitialState = async () => {
setIsLoading(true)
try {
// Load from localStorage first
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
if (stored) {
const parsed = JSON.parse(stored)
if (parsed.lastModified) {
parsed.lastModified = new Date(parsed.lastModified)
}
dispatch({ type: 'SET_STATE', payload: parsed })
}
}
// Then load from server if enabled
if (enableBackendSync && syncManagerRef.current) {
const serverState = await syncManagerRef.current.loadFromServer()
if (serverState) {
dispatch({ type: 'SET_STATE', payload: serverState })
}
}
} catch (err) {
setError(err as Error)
onError?.(err as Error)
} finally {
setIsLoading(false)
setIsInitialized(true)
}
}
loadInitialState()
}, [tenantId, enableBackendSync])
// Auto-save
useEffect(() => {
if (!isInitialized || !state.preferences.autoSave) return
const saveTimeout = setTimeout(() => {
try {
if (typeof window !== 'undefined') {
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
}
if (enableBackendSync && syncManagerRef.current) {
syncManagerRef.current.queueSync(state)
}
} catch (err) {
console.error('Failed to save state:', err)
}
}, 1000)
return () => clearTimeout(saveTimeout)
}, [state, tenantId, isInitialized, enableBackendSync])
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault()
setCommandBarOpen(prev => !prev)
}
if (e.key === 'Escape' && isCommandBarOpen) {
setCommandBarOpen(false)
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isCommandBarOpen])
// Navigation
const currentStep = useMemo(() => getStepById(state.currentStep), [state.currentStep])
const goToStep = useCallback(
(stepId: string) => {
const step = getStepById(stepId)
if (step) {
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
onNavigate?.(step.url)
}
},
[onNavigate]
)
const goToNextStep = useCallback(() => {
const nextStep = getNextStep(state.currentStep)
if (nextStep) {
goToStep(nextStep.id)
}
}, [state.currentStep, goToStep])
const goToPreviousStep = useCallback(() => {
const prevStep = getPreviousStep(state.currentStep)
if (prevStep) {
goToStep(prevStep.id)
}
}, [state.currentStep, goToStep])
const canGoNext = useMemo(() => getNextStep(state.currentStep) !== undefined, [state.currentStep])
const canGoPrevious = useMemo(
() => getPreviousStep(state.currentStep) !== undefined,
[state.currentStep]
)
// Progress
const completionPercentage = useMemo(() => getCompletionPercentage(state), [state])
const phase1Completion = useMemo(() => getPhaseCompletionPercentage(state, 1), [state])
const phase2Completion = useMemo(() => getPhaseCompletionPercentage(state, 2), [state])
// Checkpoints
const validateCheckpoint = useCallback(
async (checkpointId: string): Promise<CheckpointStatus> => {
if (enableBackendSync) {
try {
const result = await client.validateCheckpoint(checkpointId, state)
const status: CheckpointStatus = {
checkpointId: result.checkpointId,
passed: result.passed,
validatedAt: new Date(result.validatedAt),
validatedBy: result.validatedBy,
errors: result.errors,
warnings: result.warnings,
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
return status
} catch {
// Fall through to local validation
}
}
// Local validation
const status: CheckpointStatus = {
checkpointId,
passed: true,
validatedAt: new Date(),
validatedBy: 'SYSTEM',
errors: [],
warnings: [],
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
return status
},
[state, enableBackendSync, client]
)
const overrideCheckpoint = useCallback(
async (checkpointId: string, reason: string): Promise<void> => {
const existingStatus = state.checkpoints[checkpointId]
const overriddenStatus: CheckpointStatus = {
...existingStatus,
checkpointId,
passed: true,
overrideReason: reason,
overriddenBy: state.userId,
overriddenAt: new Date(),
errors: [],
warnings: existingStatus?.warnings || [],
}
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } })
},
[state.checkpoints, state.userId]
)
const getCheckpointStatus = useCallback(
(checkpointId: string): CheckpointStatus | undefined => {
return state.checkpoints[checkpointId]
},
[state.checkpoints]
)
// State Updates
const updateUseCase = useCallback((id: string, data: Partial<UseCaseAssessment>) => {
dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } })
}, [])
const addRisk = useCallback((risk: Risk) => {
dispatch({ type: 'ADD_RISK', payload: risk })
}, [])
const updateControl = useCallback((id: string, data: Partial<Control>) => {
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
}, [])
// Persistence
const saveState = useCallback(async (): Promise<void> => {
try {
if (typeof window !== 'undefined') {
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
}
if (enableBackendSync && syncManagerRef.current) {
await syncManagerRef.current.forceSync(state)
}
} catch (err) {
setError(err as Error)
throw err
}
}, [state, tenantId, enableBackendSync])
const loadState = useCallback(async (): Promise<void> => {
setIsLoading(true)
try {
if (enableBackendSync && syncManagerRef.current) {
const serverState = await syncManagerRef.current.loadFromServer()
if (serverState) {
dispatch({ type: 'SET_STATE', payload: serverState })
return
}
}
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
if (stored) {
dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) })
}
}
} catch (err) {
setError(err as Error)
throw err
} finally {
setIsLoading(false)
}
}, [tenantId, enableBackendSync])
const resetState = useCallback(() => {
dispatch({ type: 'RESET_STATE' })
if (typeof window !== 'undefined') {
localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`)
}
}, [tenantId])
// Sync
const forceSyncToServer = useCallback(async (): Promise<void> => {
if (enableBackendSync && syncManagerRef.current) {
await syncManagerRef.current.forceSync(state)
}
}, [state, enableBackendSync])
// Export
const exportState = useCallback(
async (format: 'json' | 'pdf' | 'zip'): Promise<Blob> => {
if (format === 'json') {
return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
}
return client.exportState(format)
},
[state, client]
)
const value: ComplianceContextValue = {
state,
dispatch,
client,
dsgvo,
compliance,
rag,
security,
currentStep,
goToStep,
goToNextStep,
goToPreviousStep,
canGoNext,
canGoPrevious,
completionPercentage,
phase1Completion,
phase2Completion,
validateCheckpoint,
overrideCheckpoint,
getCheckpointStatus,
updateUseCase,
addRisk,
updateControl,
saveState,
loadState,
resetState,
syncState,
forceSyncToServer,
isOnline,
exportState,
isCommandBarOpen,
setCommandBarOpen,
isInitialized,
isLoading,
error,
}
return <ComplianceContext.Provider value={value}>{children}</ComplianceContext.Provider>
}

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts', 'src/hooks.ts', 'src/components.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
external: ['react', 'react-dom', '@breakpilot/compliance-sdk-core', '@breakpilot/compliance-sdk-types'],
esbuildOptions(options) {
options.jsx = 'automatic'
},
})

View File

@@ -0,0 +1,49 @@
{
"name": "@breakpilot/compliance-sdk-types",
"version": "1.0.0",
"description": "TypeScript type definitions for BreakPilot Compliance SDK",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./dsgvo": {
"import": "./dist/dsgvo.mjs",
"require": "./dist/dsgvo.js",
"types": "./dist/dsgvo.d.ts"
},
"./compliance": {
"import": "./dist/compliance.mjs",
"require": "./dist/compliance.js",
"types": "./dist/compliance.d.ts"
},
"./rag": {
"import": "./dist/rag.mjs",
"require": "./dist/rag.js",
"types": "./dist/rag.d.ts"
},
"./security": {
"import": "./dist/security.mjs",
"require": "./dist/security.js",
"types": "./dist/security.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist"
},
"devDependencies": {
"tsup": "^8.0.1",
"typescript": "^5.3.3"
},
"publishConfig": {
"access": "restricted"
}
}

View File

@@ -0,0 +1,318 @@
/**
* API Types
*
* Type definitions for API communication
*/
import type { SDKState } from './state'
import type { CheckpointStatus, ValidationError } from './base'
// =============================================================================
// API RESPONSE TYPES
// =============================================================================
export interface APIResponse<T = unknown> {
success: boolean
data?: T
error?: string
message?: string
version?: number
timestamp?: string
}
export interface PaginatedResponse<T> {
data: T[]
total: number
page: number
pageSize: number
totalPages: number
}
export interface APIError {
code: string
message: string
details?: Record<string, unknown>
status: number
retryable: boolean
}
// =============================================================================
// STATE MANAGEMENT
// =============================================================================
export interface StateResponse {
tenantId: string
state: SDKState
version: number
lastModified: string
}
export interface SaveStateRequest {
tenantId: string
state: SDKState
version?: number // For optimistic locking
}
// =============================================================================
// CHECKPOINT VALIDATION
// =============================================================================
export interface CheckpointValidationRequest {
tenantId: string
checkpointId: string
data?: unknown
}
export interface CheckpointValidationResult {
checkpointId: string
passed: boolean
errors: ValidationError[]
warnings: ValidationError[]
validatedAt: string
validatedBy: string
}
// =============================================================================
// AUTHENTICATION
// =============================================================================
export interface AuthTokenRequest {
grantType: 'client_credentials' | 'authorization_code' | 'refresh_token'
clientId: string
clientSecret?: string
code?: string
refreshToken?: string
redirectUri?: string
}
export interface AuthTokenResponse {
accessToken: string
tokenType: 'Bearer'
expiresIn: number
refreshToken?: string
scope?: string
}
export interface APIKeyInfo {
id: string
name: string
prefix: string
scopes: string[]
createdAt: Date
lastUsedAt?: Date
expiresAt?: Date
}
// =============================================================================
// SYNC
// =============================================================================
export type SyncStatus = 'idle' | 'syncing' | 'error' | 'offline' | 'conflict'
export interface SyncState {
status: SyncStatus
lastSyncedAt: Date | null
localVersion: number
serverVersion: number
pendingChanges: number
error: string | null
}
export interface ConflictResolution {
strategy: 'local' | 'server' | 'merge'
mergedState?: SDKState
}
export interface SyncOptions {
debounceMs?: number
maxRetries?: number
conflictHandler?: (local: SDKState, server: SDKState) => Promise<ConflictResolution>
}
// =============================================================================
// FLOW NAVIGATION
// =============================================================================
export interface FlowStateResponse {
currentStep: string
currentPhase: 1 | 2
completedSteps: string[]
suggestions: Array<{ stepId: string; reason: string }>
}
export interface FlowNavigationRequest {
tenantId: string
direction: 'next' | 'previous'
}
export interface FlowNavigationResponse {
stepId: string
phase: 1 | 2
}
// =============================================================================
// EXPORT
// =============================================================================
export type ExportFormat = 'json' | 'pdf' | 'docx' | 'zip' | 'csv'
export interface ExportRequest {
tenantId: string
format: ExportFormat
sections?: string[]
includeEvidence?: boolean
language?: 'de' | 'en'
}
export interface ExportResponse {
id: string
status: 'pending' | 'processing' | 'completed' | 'failed'
downloadUrl?: string
expiresAt?: Date
error?: string
}
// =============================================================================
// RAG API
// =============================================================================
export interface RAGSearchRequest {
query: string
filters?: {
documentCodes?: string[]
jurisdictions?: string[]
languages?: string[]
articles?: string[]
}
limit?: number
offset?: number
}
export interface RAGSearchResponse {
results: Array<{
id: string
documentCode: string
documentName: string
content: string
article?: string
score: number
highlights: string[]
}>
total: number
processingTimeMs: number
}
export interface RAGAskRequest {
question: string
context?: string
documents?: string[]
maxSources?: number
language?: 'de' | 'en'
}
export interface RAGAskResponse {
answer: string
sources: Array<{
documentCode: string
documentName: string
article?: string
content: string
relevanceScore: number
}>
confidence: number
processingTimeMs: number
}
// =============================================================================
// DOCUMENT GENERATION
// =============================================================================
export interface GenerateDocumentRequest {
tenantId: string
documentType: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner'
options?: Record<string, unknown>
}
export interface GenerateDocumentResponse {
id: string
status: 'pending' | 'processing' | 'completed' | 'failed'
content?: string
documentUrl?: string
error?: string
}
// =============================================================================
// SECURITY SCAN
// =============================================================================
export interface SecurityScanRequest {
tenantId: string
tools?: string[]
targetPath?: string
excludePaths?: string[]
severityThreshold?: string
generateSBOM?: boolean
}
export interface SecurityScanResponse {
id: string
status: 'queued' | 'running' | 'completed' | 'failed'
totalIssues?: number
critical?: number
high?: number
medium?: number
low?: number
sbomId?: string
error?: string
}
// =============================================================================
// WEBHOOKS
// =============================================================================
export type WebhookEventType =
| 'state.updated'
| 'checkpoint.passed'
| 'checkpoint.failed'
| 'document.generated'
| 'security.scan_completed'
| 'dsr.received'
| 'breach.detected'
export interface WebhookConfig {
id: string
url: string
events: WebhookEventType[]
secret?: string
enabled: boolean
createdAt: Date
updatedAt: Date
}
export interface WebhookPayload {
id: string
event: WebhookEventType
timestamp: string
tenantId: string
data: Record<string, unknown>
}
// =============================================================================
// HEALTH & STATUS
// =============================================================================
export interface HealthCheckResponse {
status: 'healthy' | 'degraded' | 'unhealthy'
version: string
uptime: number
services: Record<string, {
status: 'up' | 'down'
latencyMs?: number
}>
}
export interface RateLimitInfo {
limit: number
remaining: number
resetAt: Date
}

View File

@@ -0,0 +1,220 @@
/**
* Base Types and Enums
*
* Core type definitions used across all SDK modules
*/
// =============================================================================
// SUBSCRIPTION & PLANS
// =============================================================================
export type SubscriptionTier = 'FREE' | 'STARTER' | 'PROFESSIONAL' | 'ENTERPRISE'
export type DeploymentMode = 'CLOUD' | 'MAC_MINI' | 'MAC_STUDIO' | 'ON_PREMISE'
// =============================================================================
// STATUS ENUMS
// =============================================================================
export type ValidationSeverity = 'ERROR' | 'WARNING' | 'INFO'
export type RiskSeverity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
export type RiskLikelihood = 1 | 2 | 3 | 4 | 5
export type RiskImpact = 1 | 2 | 3 | 4 | 5
export type ImplementationStatus = 'NOT_IMPLEMENTED' | 'PARTIAL' | 'IMPLEMENTED'
export type RequirementStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'IMPLEMENTED' | 'VERIFIED'
export type ReviewerType = 'NONE' | 'TEAM_LEAD' | 'DSB' | 'LEGAL'
// =============================================================================
// TENANT & USER
// =============================================================================
export interface Tenant {
id: string
name: string
subscription: SubscriptionTier
deploymentMode: DeploymentMode
createdAt: Date
settings: TenantSettings
}
export interface TenantSettings {
defaultLanguage: 'de' | 'en'
timezone: string
dataResidency: 'EU' | 'DE' | 'CUSTOM'
features: EnabledFeatures
}
export interface EnabledFeatures {
dsgvo: boolean
nis2: boolean
aiAct: boolean
sbom: boolean
security: boolean
rag: boolean
}
export interface User {
id: string
tenantId: string
email: string
name: string
role: UserRole
permissions: Permission[]
preferences: UserPreferences
}
export type UserRole = 'ADMIN' | 'DSB' | 'COMPLIANCE_MANAGER' | 'AUDITOR' | 'VIEWER'
export type Permission =
| 'READ_STATE'
| 'WRITE_STATE'
| 'MANAGE_CONTROLS'
| 'MANAGE_EVIDENCE'
| 'APPROVE_DSFA'
| 'EXPORT_DATA'
| 'MANAGE_USERS'
| 'MANAGE_SETTINGS'
export interface UserPreferences {
language: 'de' | 'en'
theme: 'light' | 'dark' | 'system'
compactMode: boolean
showHints: boolean
autoSave: boolean
autoValidate: boolean
}
// =============================================================================
// VALIDATION & CHECKPOINTS
// =============================================================================
export type CheckpointType = 'REQUIRED' | 'RECOMMENDED' | 'OPTIONAL'
export interface ValidationRule {
id: string
field: string
condition: 'NOT_EMPTY' | 'MIN_COUNT' | 'MIN_VALUE' | 'CUSTOM' | 'REGEX'
value?: number | string
message: string
severity: ValidationSeverity
}
export interface ValidationError {
ruleId: string
field: string
message: string
severity: ValidationSeverity
}
export interface Checkpoint {
id: string
step: string
name: string
type: CheckpointType
validation: ValidationRule[]
blocksProgress: boolean
requiresReview: ReviewerType
autoValidate: boolean
}
export interface CheckpointStatus {
checkpointId: string
passed: boolean
validatedAt: Date | null
validatedBy: string | null
errors: ValidationError[]
warnings: ValidationError[]
overrideReason?: string
overriddenBy?: string
overriddenAt?: Date
}
// =============================================================================
// RISK MANAGEMENT
// =============================================================================
export type RiskStatus = 'IDENTIFIED' | 'ASSESSED' | 'MITIGATED' | 'ACCEPTED' | 'CLOSED'
export type MitigationType = 'AVOID' | 'TRANSFER' | 'MITIGATE' | 'ACCEPT'
export interface RiskMitigation {
id: string
description: string
type: MitigationType
status: 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED'
effectiveness: number // 0-100
controlId: string | null
}
export interface Risk {
id: string
title: string
description: string
category: string
likelihood: RiskLikelihood
impact: RiskImpact
severity: RiskSeverity
inherentRiskScore: number
residualRiskScore: number
status: RiskStatus
mitigation: RiskMitigation[]
owner: string | null
relatedControls: string[]
relatedRequirements: string[]
}
// =============================================================================
// COMMAND BAR
// =============================================================================
export type CommandType = 'ACTION' | 'NAVIGATION' | 'SEARCH' | 'GENERATE' | 'HELP'
export interface CommandSuggestion {
id: string
type: CommandType
label: string
description: string
shortcut?: string
icon?: string
action: () => void | Promise<void>
relevanceScore: number
}
export interface CommandHistory {
id: string
query: string
type: CommandType
timestamp: Date
success: boolean
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
export function calculateRiskScore(likelihood: RiskLikelihood, impact: RiskImpact): number {
return likelihood * impact
}
export function getRiskSeverityFromScore(score: number): RiskSeverity {
if (score >= 20) return 'CRITICAL'
if (score >= 12) return 'HIGH'
if (score >= 6) return 'MEDIUM'
return 'LOW'
}
export function calculateResidualRisk(risk: Risk): number {
const inherentScore = calculateRiskScore(risk.likelihood, risk.impact)
const totalEffectiveness = risk.mitigation
.filter(m => m.status === 'COMPLETED')
.reduce((sum, m) => sum + m.effectiveness, 0)
const effectivenessMultiplier = Math.min(totalEffectiveness, 100) / 100
return Math.max(1, Math.round(inherentScore * (1 - effectivenessMultiplier)))
}

View File

@@ -0,0 +1,396 @@
/**
* Compliance Module Types
*
* Type definitions for general compliance features:
* - Controls & Control Catalog
* - Evidence Management
* - Obligations & Requirements
* - AI Act Classification
* - NIS2 Compliance
* - Audit & Assessment
*/
import type {
RiskSeverity,
ImplementationStatus,
RequirementStatus,
ValidationError,
} from './base'
// =============================================================================
// REGULATIONS
// =============================================================================
export type RegulationCode =
| 'DSGVO'
| 'AI_ACT'
| 'NIS2'
| 'EPRIVACY'
| 'TDDDG'
| 'SCC'
| 'DPF'
| 'CRA'
| 'EUCSA'
| 'DATA_ACT'
| 'DGA'
| 'DSA'
| 'EAA'
| 'ISO_27001'
| 'SOC2'
| 'BSI_GRUNDSCHUTZ'
export interface Regulation {
id: string
code: RegulationCode
name: string
fullName: string
description: string
jurisdiction: 'EU' | 'DE' | 'INT'
effectiveDate: Date
requirements: number
controlMappings: number
documentUrl?: string
}
// =============================================================================
// CONTROLS
// =============================================================================
export type ControlType = 'TECHNICAL' | 'ORGANIZATIONAL' | 'PHYSICAL'
export type ControlDomain =
| 'ACCESS_MANAGEMENT'
| 'DATA_PROTECTION'
| 'NETWORK_SECURITY'
| 'INCIDENT_RESPONSE'
| 'BUSINESS_CONTINUITY'
| 'VENDOR_MANAGEMENT'
| 'ASSET_MANAGEMENT'
| 'CRYPTOGRAPHY'
| 'PHYSICAL_SECURITY'
export interface Control {
id: string
code: string // e.g., "AC-01", "DP-03"
name: string
description: string
type: ControlType
domain: ControlDomain
implementationStatus: ImplementationStatus
effectiveness: RiskSeverity
evidence: string[]
owner: string | null
dueDate: Date | null
mappings: ControlMapping[]
testingFrequency: 'ANNUAL' | 'SEMI_ANNUAL' | 'QUARTERLY' | 'MONTHLY'
lastTestDate?: Date
nextTestDate?: Date
}
export interface ControlMapping {
regulationCode: RegulationCode
requirementId: string
article: string
}
export interface ControlCatalog {
id: string
name: string
version: string
controls: Control[]
domains: ControlDomain[]
lastUpdated: Date
}
// =============================================================================
// REQUIREMENTS
// =============================================================================
export interface Requirement {
id: string
regulationCode: RegulationCode
article: string
title: string
description: string
criticality: RiskSeverity
applicableModules: string[]
status: RequirementStatus
controls: string[] // Control IDs
deadline?: Date
notes?: string
}
// =============================================================================
// EVIDENCE
// =============================================================================
export type EvidenceType =
| 'DOCUMENT'
| 'SCREENSHOT'
| 'LOG'
| 'CERTIFICATE'
| 'AUDIT_REPORT'
| 'POLICY'
| 'TRAINING_RECORD'
export type EvidenceStatus = 'DRAFT' | 'ACTIVE' | 'EXPIRED' | 'ARCHIVED'
export interface Evidence {
id: string
controlId: string
type: EvidenceType
name: string
description: string
fileUrl: string | null
mimeType?: string
fileSize?: number
validFrom: Date
validUntil: Date | null
status: EvidenceStatus
uploadedBy: string
uploadedAt: Date
reviewedBy?: string
reviewedAt?: Date
tags: string[]
}
export interface EvidenceUploadRequest {
controlId: string
type: EvidenceType
name: string
description?: string
validFrom?: Date
validUntil?: Date
tags?: string[]
file: File | Blob
}
// =============================================================================
// AI ACT
// =============================================================================
export type AIActRiskCategory =
| 'MINIMAL' // Minimal or no risk
| 'LIMITED' // Limited risk - transparency
| 'HIGH' // High-risk - conformity assessment
| 'UNACCEPTABLE' // Prohibited
export type AISystemType =
| 'GENERAL_PURPOSE'
| 'BIOMETRIC'
| 'CRITICAL_INFRASTRUCTURE'
| 'EDUCATION'
| 'EMPLOYMENT'
| 'ESSENTIAL_SERVICES'
| 'LAW_ENFORCEMENT'
| 'BORDER_CONTROL'
| 'JUSTICE'
| 'RECOMMENDATION'
| 'CONTENT_GENERATION'
export interface AIActObligation {
id: string
article: string
title: string
description: string
riskCategory: AIActRiskCategory
deadline: Date | null
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'
evidence?: string[]
}
export interface AIActResult {
riskCategory: AIActRiskCategory
systemType: AISystemType
obligations: AIActObligation[]
assessmentDate: Date
assessedBy: string
justification: string
mitigationRequired: boolean
conformityAssessmentRequired: boolean
transparencyRequired: boolean
}
export interface AIActAssessmentInput {
systemDescription: string
intendedUse: string
dataCategories: string[]
affectedPersons: string[]
automatedDecisions: boolean
humanOversight: boolean
riskMitigations: string[]
}
// =============================================================================
// NIS2
// =============================================================================
export type NIS2Sector =
| 'ENERGY'
| 'TRANSPORT'
| 'BANKING'
| 'FINANCIAL_MARKET'
| 'HEALTH'
| 'DRINKING_WATER'
| 'WASTE_WATER'
| 'DIGITAL_INFRASTRUCTURE'
| 'ICT_SERVICE_MANAGEMENT'
| 'PUBLIC_ADMINISTRATION'
| 'SPACE'
| 'POSTAL'
| 'WASTE_MANAGEMENT'
| 'CHEMICAL'
| 'FOOD'
| 'MANUFACTURING'
| 'DIGITAL_PROVIDERS'
| 'RESEARCH'
export type NIS2EntityType = 'ESSENTIAL' | 'IMPORTANT'
export interface NIS2Assessment {
sector: NIS2Sector
entityType: NIS2EntityType
employeeCount: number
annualTurnover: number // in EUR
applicableRequirements: NIS2Requirement[]
complianceScore: number // 0-100
gaps: string[]
}
export interface NIS2Requirement {
id: string
article: string
title: string
description: string
controls: string[]
status: RequirementStatus
deadline?: Date
}
// =============================================================================
// OBLIGATIONS
// =============================================================================
export interface Obligation {
id: string
regulationCode: RegulationCode
article: string
title: string
description: string
deadline: Date | null
penalty: string | null
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED'
responsible: string | null
priority: RiskSeverity
linkedControls: string[]
linkedEvidence: string[]
}
// =============================================================================
// AUDIT & ASSESSMENT
// =============================================================================
export type AuditStatus = 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'
export type AuditType = 'INTERNAL' | 'EXTERNAL' | 'CERTIFICATION' | 'REGULATORY'
export interface Audit {
id: string
name: string
type: AuditType
status: AuditStatus
scope: string[]
regulations: RegulationCode[]
auditor: string
startDate: Date
endDate: Date | null
findings: AuditFinding[]
report?: string
}
export interface AuditFinding {
id: string
title: string
description: string
severity: RiskSeverity
controlId?: string
recommendation: string
status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'ACCEPTED'
dueDate?: Date
responsiblePerson?: string
}
export interface ChecklistItem {
id: string
requirementId: string
title: string
description: string
status: 'PENDING' | 'PASSED' | 'FAILED' | 'NOT_APPLICABLE'
notes: string
verifiedBy: string | null
verifiedAt: Date | null
evidence?: string[]
}
// =============================================================================
// COMPLIANCE SCORE
// =============================================================================
export interface ComplianceScore {
overall: number // 0-100
byRegulation: Record<RegulationCode, number>
byDomain: Record<ControlDomain, number>
trend: 'UP' | 'DOWN' | 'STABLE'
lastCalculated: Date
}
export interface ComplianceGap {
id: string
regulationCode: RegulationCode
requirementId: string
controlId?: string
description: string
severity: RiskSeverity
recommendation: string
estimatedEffort: 'LOW' | 'MEDIUM' | 'HIGH'
}
// =============================================================================
// MODULES / SERVICE MODULES
// =============================================================================
export interface ServiceModule {
id: string
name: string
description: string
regulations: RegulationCode[]
criticality: RiskSeverity
processesPersonalData: boolean
hasAIComponents: boolean
dataCategories: string[]
owner: string
}
// =============================================================================
// EXPORT
// =============================================================================
export interface ExportOptions {
format: 'PDF' | 'DOCX' | 'JSON' | 'CSV' | 'ZIP'
sections: ExportSection[]
includeEvidence: boolean
language: 'de' | 'en'
template?: string
}
export type ExportSection =
| 'SUMMARY'
| 'CONTROLS'
| 'EVIDENCE'
| 'RISKS'
| 'REQUIREMENTS'
| 'OBLIGATIONS'
| 'AUDIT_FINDINGS'
| 'VVT'
| 'DSFA'
| 'TOM'

View File

@@ -0,0 +1,394 @@
/**
* DSGVO Module Types
*
* Type definitions for GDPR/DSGVO compliance features:
* - DSR (Data Subject Requests) - Art. 15-21
* - Consent Management - Art. 6, 7
* - VVT (Processing Register) - Art. 30
* - DSFA (Impact Assessment) - Art. 35, 36
* - TOM (Technical & Organizational Measures) - Art. 32
* - Retention Policies - Art. 5, 17
*/
import type { RiskSeverity, ImplementationStatus } from './base'
// =============================================================================
// DSR (Data Subject Requests) - Art. 15-21 DSGVO
// =============================================================================
export type DSRRequestType =
| 'ACCESS' // Art. 15 - Auskunftsrecht
| 'RECTIFICATION' // Art. 16 - Berichtigungsrecht
| 'ERASURE' // Art. 17 - Löschungsrecht
| 'PORTABILITY' // Art. 20 - Datenübertragbarkeit
| 'RESTRICTION' // Art. 18 - Einschränkungsrecht
| 'OBJECTION' // Art. 21 - Widerspruchsrecht
export type DSRStatus =
| 'RECEIVED'
| 'VERIFIED'
| 'PROCESSING'
| 'COMPLETED'
| 'REJECTED'
export interface DSRRequest {
id: string
type: DSRRequestType
status: DSRStatus
requesterEmail: string
requesterName: string
requestedAt: Date
dueDate: Date
completedAt: Date | null
notes: string
verificationMethod?: string
responseDocument?: string
}
export interface DSRConfig {
id: string
enabled: boolean
portalUrl: string
emailTemplates: Record<DSRRequestType, string>
automatedResponses: boolean
verificationRequired: boolean
deadlineDays: number // Default 30 days per GDPR
}
// =============================================================================
// CONSENT MANAGEMENT - Art. 6, 7 DSGVO
// =============================================================================
export type ConsentPurpose =
| 'ESSENTIAL'
| 'FUNCTIONAL'
| 'ANALYTICS'
| 'MARKETING'
| 'PERSONALIZATION'
| 'THIRD_PARTY'
export type ConsentChannel =
| 'WEB'
| 'MOBILE_APP'
| 'EMAIL'
| 'PAPER'
| 'VERBAL'
| 'API'
export interface ConsentRecord {
id: string
userId: string
documentId: string
documentVersion: string
consentType: ConsentPurpose
channel: ConsentChannel
granted: boolean
grantedAt: Date
revokedAt: Date | null
ipAddress: string | null
userAgent: string | null
metadata?: Record<string, unknown>
}
export interface ConsentPreference {
purpose: ConsentPurpose
granted: boolean
lastUpdated: Date
}
export interface ConsentState {
userId: string
preferences: ConsentPreference[]
lastConsentDate: Date
consentVersion: string
}
// =============================================================================
// COOKIE BANNER - Art. 7, 13/14 DSGVO
// =============================================================================
export type CookieBannerStyle = 'BANNER' | 'MODAL' | 'FLOATING'
export type CookieBannerPosition = 'TOP' | 'BOTTOM' | 'CENTER'
export type CookieBannerTheme = 'LIGHT' | 'DARK' | 'CUSTOM'
export type CookieType = 'NECESSARY' | 'FUNCTIONAL' | 'ANALYTICS' | 'MARKETING'
export interface Cookie {
id: string
name: string
provider: string
purpose: string
expiry: string
type: CookieType
}
export interface CookieCategory {
id: string
name: string
description: string
required: boolean
cookies: Cookie[]
}
export interface CookieBannerTexts {
title: string
description: string
acceptAll: string
rejectAll: string
settings: string
save: string
necessary: string
functional: string
analytics: string
marketing: string
}
export interface CookieBannerGeneratedCode {
html: string
css: string
js: string
}
export interface CookieBannerConfig {
id: string
style: CookieBannerStyle
position: CookieBannerPosition
theme: CookieBannerTheme
customColors?: {
primary: string
secondary: string
background: string
text: string
}
texts: CookieBannerTexts
categories: CookieCategory[]
generatedCode: CookieBannerGeneratedCode | null
logoUrl?: string
privacyPolicyUrl: string
imprintUrl: string
}
// =============================================================================
// VVT (Verarbeitungsverzeichnis) - Art. 30 DSGVO
// =============================================================================
export type LegalBasis =
| 'CONSENT' // Art. 6(1)(a) - Einwilligung
| 'CONTRACT' // Art. 6(1)(b) - Vertragserfüllung
| 'LEGAL_OBLIGATION' // Art. 6(1)(c) - Rechtliche Verpflichtung
| 'VITAL_INTERESTS' // Art. 6(1)(d) - Lebenswichtige Interessen
| 'PUBLIC_INTEREST' // Art. 6(1)(e) - Öffentliches Interesse
| 'LEGITIMATE_INTEREST' // Art. 6(1)(f) - Berechtigtes Interesse
export interface ProcessingActivity {
id: string
name: string
purpose: string
legalBasis: LegalBasis
legalBasisJustification?: string
dataCategories: string[]
specialDataCategories?: string[] // Art. 9 Daten
dataSubjects: string[]
recipients: string[]
thirdCountryTransfers: boolean
transferSafeguards?: string // SCCs, BCRs, etc.
retentionPeriod: string
technicalMeasures: string[]
organizationalMeasures: string[]
responsiblePerson: string
lastReviewDate: Date
nextReviewDate: Date
}
// =============================================================================
// DSFA (Datenschutz-Folgenabschätzung) - Art. 35, 36 DSGVO
// =============================================================================
export type DSFAStatus = 'DRAFT' | 'IN_REVIEW' | 'APPROVED' | 'REJECTED'
export interface DSFASection {
id: string
title: string
content: string
status: 'DRAFT' | 'COMPLETED'
order: number
}
export interface DSFAApproval {
id: string
approver: string
role: string
status: 'PENDING' | 'APPROVED' | 'REJECTED'
comment: string | null
approvedAt: Date | null
}
export interface DSFARisk {
id: string
description: string
likelihood: number
severity: number
riskScore: number
mitigation: string
residualRisk: number
}
export interface DSFA {
id: string
name: string
processingActivityId: string
status: DSFAStatus
version: number
sections: DSFASection[]
risks: DSFARisk[]
approvals: DSFAApproval[]
consultationRequired: boolean // Art. 36 consultation
consultationDate?: Date
createdAt: Date
updatedAt: Date
}
// =============================================================================
// TOM (Technische und Organisatorische Maßnahmen) - Art. 32 DSGVO
// =============================================================================
export type TOMType = 'TECHNICAL' | 'ORGANIZATIONAL'
export type TOMCategory =
| 'ACCESS_CONTROL' // Zutrittskontrolle
| 'DATA_ACCESS_CONTROL' // Zugangskontrolle
| 'USER_ACCESS_CONTROL' // Zugriffskontrolle
| 'TRANSFER_CONTROL' // Weitergabekontrolle
| 'INPUT_CONTROL' // Eingabekontrolle
| 'JOB_CONTROL' // Auftragskontrolle
| 'AVAILABILITY_CONTROL' // Verfügbarkeitskontrolle
| 'SEPARATION_CONTROL' // Trennungskontrolle
export interface TOM {
id: string
category: TOMCategory
name: string
description: string
type: TOMType
implementationStatus: ImplementationStatus
priority: RiskSeverity
responsiblePerson: string | null
implementationDate: Date | null
reviewDate: Date | null
evidence: string[]
controls: string[] // Related control IDs
}
export interface TOMGeneratorConfig {
companySize: 'SMALL' | 'MEDIUM' | 'LARGE' | 'ENTERPRISE'
industry: string
dataCategories: string[]
riskLevel: RiskSeverity
existingMeasures: string[]
}
export interface TOMGeneratorResult {
recommendedTOMs: TOM[]
score: number // 0-100%
gaps: string[]
recommendations: string[]
}
// =============================================================================
// RETENTION POLICIES - Art. 5, 17 DSGVO
// =============================================================================
export interface RetentionPolicy {
id: string
dataCategory: string
description: string
legalBasis: string
retentionPeriod: string // ISO 8601 Duration (e.g., "P3Y" for 3 years)
retentionPeriodDays: number
deletionMethod: 'ANONYMIZATION' | 'PSEUDONYMIZATION' | 'HARD_DELETE' | 'SOFT_DELETE'
exceptions: string[]
automatedDeletion: boolean
lastReviewDate: Date
nextReviewDate: Date
}
export interface RetentionSchedule {
id: string
policyId: string
nextExecutionDate: Date
lastExecutionDate: Date | null
status: 'SCHEDULED' | 'RUNNING' | 'COMPLETED' | 'FAILED'
itemsProcessed?: number
errors?: string[]
}
// =============================================================================
// ESCALATIONS - Art. 33/34 DSGVO (Data Breach)
// =============================================================================
export type EscalationLevel = 'E0' | 'E1' | 'E2' | 'E3'
export interface EscalationStep {
id: string
order: number
action: string
assignee: string
timeLimit: string // ISO 8601 Duration
escalateOnTimeout: boolean
notificationChannels: ('EMAIL' | 'SMS' | 'SLACK' | 'WEBHOOK')[]
}
export interface EscalationWorkflow {
id: string
name: string
description: string
level: EscalationLevel
triggerConditions: string[]
steps: EscalationStep[]
enabled: boolean
createdAt: Date
updatedAt: Date
}
export interface DataBreach {
id: string
title: string
description: string
discoveredAt: Date
reportedToAuthorityAt?: Date // Art. 33 - 72 hours
reportedToDataSubjectsAt?: Date // Art. 34
affectedDataSubjects: number
dataCategories: string[]
riskLevel: RiskSeverity
mitigationMeasures: string[]
status: 'DETECTED' | 'INVESTIGATING' | 'REPORTED' | 'RESOLVED'
workflowId: string
}
// =============================================================================
// LEGAL DOCUMENTS
// =============================================================================
export type LegalDocumentType =
| 'AGB'
| 'PRIVACY_POLICY'
| 'TERMS_OF_USE'
| 'IMPRINT'
| 'COOKIE_POLICY'
| 'DATA_PROCESSING_AGREEMENT'
export interface LegalDocument {
id: string
type: LegalDocumentType
title: string
content: string
version: string
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'
language: 'de' | 'en'
publishedAt: Date | null
createdAt: Date
updatedAt: Date
changelog?: string
}

View File

@@ -0,0 +1,14 @@
/**
* @breakpilot/compliance-sdk-types
*
* Comprehensive TypeScript type definitions for BreakPilot Compliance SDK
*/
// Re-export all modules
export * from './base'
export * from './dsgvo'
export * from './compliance'
export * from './rag'
export * from './security'
export * from './state'
export * from './api'

View File

@@ -0,0 +1,305 @@
/**
* RAG (Retrieval-Augmented Generation) Module Types
*
* Type definitions for the Legal RAG System:
* - 21 pre-indexed legal documents
* - Semantic search with Qdrant
* - LLM-powered legal assistant
* - Customer document upload
*/
import type { RegulationCode } from './compliance'
// =============================================================================
// LEGAL CORPUS
// =============================================================================
export interface LegalDocument {
id: string
code: RegulationCode | string
name: string
fullName: string
jurisdiction: 'EU' | 'DE' | 'INT'
language: 'de' | 'en'
effectiveDate: Date
sourceUrl?: string
chunks: number
lastIndexed: Date
}
export interface LegalChunk {
id: string
documentId: string
documentCode: string
chunkIndex: number
content: string
article?: string
section?: string
metadata: ChunkMetadata
embedding?: number[]
}
export interface ChunkMetadata {
articleNumber?: string
sectionTitle?: string
paragraph?: number
keywords: string[]
references: string[]
effectiveDate?: Date
}
// Pre-indexed documents in the Legal Corpus
export const LEGAL_CORPUS_DOCUMENTS: readonly string[] = [
'DSGVO', // 99 Chunks
'AI_ACT', // 85 Chunks
'NIS2', // 46 Chunks
'EPRIVACY',
'TDDDG',
'SCC', // Standard Contractual Clauses
'DPF', // Data Privacy Framework
'CRA', // Cyber Resilience Act
'EUCSA', // EU Cybersecurity Act
'DATA_ACT',
'DGA', // Data Governance Act
'DSA', // Digital Services Act
'EAA', // European Accessibility Act
'BDSG', // German BDSG
'ISO_27001',
'BSI_GRUNDSCHUTZ',
'KRITIS',
'BAIT', // Banking IT requirements
'VAIT', // Insurance IT requirements
'SOC2',
'PCI_DSS',
] as const
// =============================================================================
// SEARCH & RETRIEVAL
// =============================================================================
export interface SearchQuery {
query: string
filters?: SearchFilters
limit?: number
offset?: number
includeMetadata?: boolean
scoreThreshold?: number
}
export interface SearchFilters {
documentCodes?: string[]
jurisdictions?: ('EU' | 'DE' | 'INT')[]
languages?: ('de' | 'en')[]
articles?: string[]
dateFrom?: Date
dateTo?: Date
}
export interface SearchResult {
id: string
documentCode: string
documentName: string
content: string
article?: string
section?: string
score: number
highlights: string[]
metadata: ChunkMetadata
}
export interface SearchResponse {
results: SearchResult[]
total: number
query: string
processingTimeMs: number
filters?: SearchFilters
}
// =============================================================================
// LEGAL ASSISTANT
// =============================================================================
export interface AssistantQuery {
question: string
context?: string
documents?: string[]
maxSources?: number
language?: 'de' | 'en'
responseFormat?: 'detailed' | 'concise' | 'bullet_points'
}
export interface AssistantResponse {
answer: string
sources: SourceReference[]
confidence: number // 0-1
processingTimeMs: number
suggestedFollowUps?: string[]
}
export interface SourceReference {
documentCode: string
documentName: string
article?: string
content: string
relevanceScore: number
url?: string
}
// =============================================================================
// CUSTOMER DOCUMENTS
// =============================================================================
export type CustomerDocumentType =
| 'POLICY'
| 'GUIDELINE'
| 'CONTRACT'
| 'SLA'
| 'PROCEDURE'
| 'RISK_ASSESSMENT'
| 'AUDIT_REPORT'
| 'OTHER'
export type IndexingStatus =
| 'PENDING'
| 'PROCESSING'
| 'INDEXED'
| 'FAILED'
| 'OUTDATED'
export interface CustomerDocument {
id: string
tenantId: string
name: string
fileName: string
type: CustomerDocumentType
mimeType: string
fileSize: number
uploadedBy: string
uploadedAt: Date
indexingStatus: IndexingStatus
indexedAt?: Date
chunkCount?: number
error?: string
tags: string[]
metadata?: Record<string, unknown>
}
export interface DocumentUploadRequest {
name: string
type: CustomerDocumentType
tags?: string[]
metadata?: Record<string, unknown>
file: File | Blob
}
export interface DocumentUploadResponse {
id: string
status: IndexingStatus
message: string
estimatedProcessingTimeMs?: number
}
// =============================================================================
// VECTOR DATABASE
// =============================================================================
export interface VectorDBConfig {
provider: 'qdrant' | 'pinecone' | 'weaviate'
url: string
apiKey?: string
collection: string
embeddingModel: string
embeddingDimensions: number
}
export interface EmbeddingRequest {
texts: string[]
model?: string
}
export interface EmbeddingResponse {
embeddings: number[][]
model: string
usage: {
promptTokens: number
totalTokens: number
}
}
// =============================================================================
// RAG PIPELINE
// =============================================================================
export interface RAGPipelineConfig {
retriever: {
topK: number
scoreThreshold: number
reranking: boolean
hybridSearch: boolean
}
generator: {
model: string
temperature: number
maxTokens: number
systemPrompt?: string
}
postProcessing: {
citationFormat: 'inline' | 'footnote' | 'none'
sourceVerification: boolean
hallucizationCheck: boolean
}
}
export interface RAGContext {
query: string
retrievedChunks: LegalChunk[]
rerankedChunks?: LegalChunk[]
generatedResponse?: string
citations: Citation[]
}
export interface Citation {
id: string
chunkId: string
documentCode: string
article?: string
quotedText: string
confidence: number
}
// =============================================================================
// CHAT HISTORY
// =============================================================================
export interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'system'
content: string
timestamp: Date
sources?: SourceReference[]
metadata?: Record<string, unknown>
}
export interface ChatSession {
id: string
tenantId: string
userId: string
messages: ChatMessage[]
createdAt: Date
updatedAt: Date
title?: string
tags?: string[]
}
// =============================================================================
// ANALYTICS
// =============================================================================
export interface RAGAnalytics {
totalQueries: number
averageResponseTimeMs: number
topDocuments: Array<{ code: string; queryCount: number }>
topQuestions: Array<{ question: string; count: number }>
satisfactionScore?: number
period: 'day' | 'week' | 'month'
}

View File

@@ -0,0 +1,381 @@
/**
* Security Module Types
*
* Type definitions for security scanning:
* - SBOM (Software Bill of Materials)
* - Vulnerability scanning with 6 integrated tools
* - Findings management
* - CVE tracking
*/
// =============================================================================
// SBOM (Software Bill of Materials)
// =============================================================================
export type SBOMFormat = 'CycloneDX' | 'SPDX'
export type ComponentType =
| 'library'
| 'framework'
| 'application'
| 'container'
| 'operating-system'
| 'device'
| 'file'
export type LicenseType =
| 'MIT'
| 'Apache-2.0'
| 'GPL-2.0'
| 'GPL-3.0'
| 'BSD-2-Clause'
| 'BSD-3-Clause'
| 'ISC'
| 'MPL-2.0'
| 'LGPL-2.1'
| 'LGPL-3.0'
| 'AGPL-3.0'
| 'Proprietary'
| 'Unknown'
export interface SBOMComponent {
name: string
version: string
type: ComponentType
purl: string // Package URL
licenses: LicenseType[]
supplier?: string
author?: string
description?: string
hashes?: ComponentHash[]
vulnerabilities: Vulnerability[]
dependencies?: string[] // PURLs of dependencies
}
export interface ComponentHash {
algorithm: 'SHA-256' | 'SHA-384' | 'SHA-512' | 'MD5'
value: string
}
export interface SBOMDependency {
from: string // PURL
to: string // PURL
}
export interface SBOM {
format: SBOMFormat
specVersion: string
serialNumber?: string
version: number
metadata: SBOMMetadata
components: SBOMComponent[]
dependencies: SBOMDependency[]
generatedAt: Date
}
export interface SBOMMetadata {
timestamp: Date
tools: SBOMTool[]
component?: SBOMComponent // The component this SBOM describes
authors?: string[]
}
export interface SBOMTool {
vendor: string
name: string
version: string
}
// =============================================================================
// VULNERABILITIES
// =============================================================================
export type SecurityIssueSeverity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'INFO'
export type SecurityIssueStatus = 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'ACCEPTED' | 'FALSE_POSITIVE'
export interface Vulnerability {
id: string
cve: string
severity: SecurityIssueSeverity
title: string
description: string
cvss: CVSSScore | null
cwe?: string
fixedIn: string | null
publishedAt?: Date
references: string[]
}
export interface CVSSScore {
version: '2.0' | '3.0' | '3.1'
score: number
vector: string
severity: SecurityIssueSeverity
}
// =============================================================================
// SECURITY SCANNING
// =============================================================================
export type SecurityTool =
| 'gitleaks' // Secrets detection
| 'semgrep' // SAST
| 'bandit' // Python security
| 'trivy' // Container/filesystem scanning
| 'grype' // Vulnerability scanner
| 'syft' // SBOM generation
export type ScreeningStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
export interface SecurityScanConfig {
tools: SecurityTool[]
targetPath: string
excludePaths?: string[]
severityThreshold?: SecurityIssueSeverity
failOnFindings?: boolean
generateSBOM?: boolean
}
export interface SecurityScanResult {
id: string
status: ScreeningStatus
startedAt: Date
completedAt: Date | null
duration?: number // ms
totalIssues: number
critical: number
high: number
medium: number
low: number
info: number
issues: SecurityIssue[]
toolResults: ToolResult[]
sbom?: SBOM
error?: string
}
export interface ToolResult {
tool: SecurityTool
status: 'SUCCESS' | 'FAILED' | 'SKIPPED'
issuesFound: number
executionTimeMs: number
version: string
rawOutput?: string
error?: string
}
export interface SecurityIssue {
id: string
tool: SecurityTool
severity: SecurityIssueSeverity
title: string
description: string
cve: string | null
cvss: CVSSScore | null
cwe?: string
affectedComponent: string
affectedFile?: string
affectedLine?: number
remediation: string
status: SecurityIssueStatus
assignee?: string
dueDate?: Date
resolvedAt?: Date
resolvedBy?: string
notes?: string
}
// =============================================================================
// FINDINGS MANAGEMENT
// =============================================================================
export interface FindingsFilter {
tools?: SecurityTool[]
severities?: SecurityIssueSeverity[]
statuses?: SecurityIssueStatus[]
cveIds?: string[]
components?: string[]
dateFrom?: Date
dateTo?: Date
}
export interface FindingsSummary {
totalFindings: number
bySeverity: Record<SecurityIssueSeverity, number>
byStatus: Record<SecurityIssueStatus, number>
byTool: Record<SecurityTool, number>
averageResolutionDays: number
oldestUnresolvedDays: number
}
export interface FindingsReport {
generatedAt: Date
period: {
from: Date
to: Date
}
summary: FindingsSummary
newFindings: SecurityIssue[]
resolvedFindings: SecurityIssue[]
trends: FindingsTrend[]
}
export interface FindingsTrend {
date: Date
totalOpen: number
totalResolved: number
newFindings: number
}
// =============================================================================
// SECURITY BACKLOG
// =============================================================================
export interface BacklogItem {
id: string
title: string
description: string
severity: SecurityIssueSeverity
securityIssueId: string
status: 'OPEN' | 'IN_PROGRESS' | 'DONE'
priority: number // 1-5
assignee: string | null
dueDate: Date | null
estimatedHours?: number
createdAt: Date
updatedAt: Date
labels: string[]
}
// =============================================================================
// SECRETS DETECTION (Gitleaks)
// =============================================================================
export interface SecretFinding {
id: string
ruleId: string
description: string
match: string
secret: string // Masked
file: string
startLine: number
endLine: number
commit?: string
author?: string
date?: Date
entropy?: number
}
// =============================================================================
// SAST (Semgrep)
// =============================================================================
export interface SASTFinding {
id: string
ruleId: string
message: string
severity: SecurityIssueSeverity
file: string
startLine: number
endLine: number
startCol: number
endCol: number
codeSnippet: string
fix?: SASTFix
metadata: {
category: string
technology: string[]
cwe?: string
owasp?: string
confidence: 'HIGH' | 'MEDIUM' | 'LOW'
}
}
export interface SASTFix {
fix: string
regex_replace?: {
regex: string
replacement: string
}
}
// =============================================================================
// CONTAINER SCANNING (Trivy)
// =============================================================================
export interface ContainerScanResult {
target: string
type: 'image' | 'filesystem' | 'repository'
vulnerabilities: ContainerVulnerability[]
misconfigurations?: ContainerMisconfiguration[]
secrets?: SecretFinding[]
}
export interface ContainerVulnerability extends Vulnerability {
pkgName: string
installedVersion: string
fixedVersion?: string
layer?: {
digest: string
diffId: string
createdBy?: string
}
}
export interface ContainerMisconfiguration {
id: string
title: string
description: string
severity: SecurityIssueSeverity
resolution: string
type: string
resource: string
cause: {
provider: string
service: string
startLine: number
endLine: number
}
}
// =============================================================================
// CI/CD INTEGRATION
// =============================================================================
export interface CICDIntegrationConfig {
provider: 'github' | 'gitlab' | 'jenkins' | 'azure_devops' | 'circleci'
webhookUrl?: string
apiToken?: string
scanOnPush: boolean
scanOnPR: boolean
failPipelineOnCritical: boolean
failPipelineOnHigh: boolean
notifyOnNewFindings: boolean
notificationChannels: string[]
}
export interface CICDScanTrigger {
id: string
provider: string
repository: string
branch: string
commit: string
triggeredBy: 'push' | 'pull_request' | 'schedule' | 'manual'
triggeredAt: Date
scanResultId?: string
status: 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED'
}
// =============================================================================
// COMPLIANCE MAPPING
// =============================================================================
export interface SecurityComplianceMapping {
issueId: string
regulation: string
requirement: string
article: string
impact: string
remediation: string
}

View File

@@ -0,0 +1,505 @@
/**
* SDK State Types
*
* Type definitions for the SDK's state management system
*/
import type {
SubscriptionTier,
UserPreferences,
Risk,
CommandHistory,
CheckpointStatus,
} from './base'
import type {
ConsentRecord,
DSRConfig,
DSRRequest,
CookieBannerConfig,
ProcessingActivity,
DSFA,
TOM,
RetentionPolicy,
EscalationWorkflow,
LegalDocument,
} from './dsgvo'
import type {
Control,
Evidence,
Requirement,
ServiceModule,
Obligation,
AIActResult,
ChecklistItem,
} from './compliance'
import type { SBOM, SecurityIssue, BacklogItem, SecurityScanResult } from './security'
// =============================================================================
// SDK FLOW & NAVIGATION
// =============================================================================
export type SDKPhase = 1 | 2
export interface SDKStep {
id: string
phase: SDKPhase
order: number
name: string
nameShort: string
description: string
url: string
checkpointId: string
prerequisiteSteps: string[]
isOptional: boolean
}
export const SDK_STEPS: SDKStep[] = [
// Phase 1: Automatisches Compliance Assessment
{
id: 'use-case-workshop',
phase: 1,
order: 1,
name: 'Use Case Workshop',
nameShort: 'Use Case',
description: '5-Schritte-Wizard für Use Case Erfassung',
url: '/sdk/advisory-board',
checkpointId: 'CP-UC',
prerequisiteSteps: [],
isOptional: false,
},
{
id: 'screening',
phase: 1,
order: 2,
name: 'System Screening',
nameShort: 'Screening',
description: 'SBOM + Security Check',
url: '/sdk/screening',
checkpointId: 'CP-SCAN',
prerequisiteSteps: ['use-case-workshop'],
isOptional: false,
},
{
id: 'modules',
phase: 1,
order: 3,
name: 'Compliance Modules',
nameShort: 'Module',
description: 'Abgleich welche Regulierungen gelten',
url: '/sdk/modules',
checkpointId: 'CP-MOD',
prerequisiteSteps: ['screening'],
isOptional: false,
},
{
id: 'requirements',
phase: 1,
order: 4,
name: 'Requirements',
nameShort: 'Anforderungen',
description: 'Prüfaspekte aus Regulierungen ableiten',
url: '/sdk/requirements',
checkpointId: 'CP-REQ',
prerequisiteSteps: ['modules'],
isOptional: false,
},
{
id: 'controls',
phase: 1,
order: 5,
name: 'Controls',
nameShort: 'Controls',
description: 'Erforderliche Maßnahmen ermitteln',
url: '/sdk/controls',
checkpointId: 'CP-CTRL',
prerequisiteSteps: ['requirements'],
isOptional: false,
},
{
id: 'evidence',
phase: 1,
order: 6,
name: 'Evidence',
nameShort: 'Nachweise',
description: 'Nachweise dokumentieren',
url: '/sdk/evidence',
checkpointId: 'CP-EVI',
prerequisiteSteps: ['controls'],
isOptional: false,
},
{
id: 'audit-checklist',
phase: 1,
order: 7,
name: 'Audit Checklist',
nameShort: 'Checklist',
description: 'Prüfliste generieren',
url: '/sdk/audit-checklist',
checkpointId: 'CP-CHK',
prerequisiteSteps: ['evidence'],
isOptional: false,
},
{
id: 'risks',
phase: 1,
order: 8,
name: 'Risk Matrix',
nameShort: 'Risiken',
description: 'Risikobewertung & Residual Risk',
url: '/sdk/risks',
checkpointId: 'CP-RISK',
prerequisiteSteps: ['audit-checklist'],
isOptional: false,
},
// Phase 2: Dokumentengenerierung
{
id: 'ai-act',
phase: 2,
order: 1,
name: 'AI Act Klassifizierung',
nameShort: 'AI Act',
description: 'Risikostufe nach EU AI Act',
url: '/sdk/ai-act',
checkpointId: 'CP-AI',
prerequisiteSteps: ['risks'],
isOptional: false,
},
{
id: 'obligations',
phase: 2,
order: 2,
name: 'Pflichtenübersicht',
nameShort: 'Pflichten',
description: 'NIS2, DSGVO, AI Act Pflichten',
url: '/sdk/obligations',
checkpointId: 'CP-OBL',
prerequisiteSteps: ['ai-act'],
isOptional: false,
},
{
id: 'dsfa',
phase: 2,
order: 3,
name: 'DSFA',
nameShort: 'DSFA',
description: 'Datenschutz-Folgenabschätzung',
url: '/sdk/dsfa',
checkpointId: 'CP-DSFA',
prerequisiteSteps: ['obligations'],
isOptional: true,
},
{
id: 'tom',
phase: 2,
order: 4,
name: 'TOMs',
nameShort: 'TOMs',
description: 'Technische & Org. Maßnahmen',
url: '/sdk/tom',
checkpointId: 'CP-TOM',
prerequisiteSteps: ['dsfa'],
isOptional: false,
},
{
id: 'loeschfristen',
phase: 2,
order: 5,
name: 'Löschfristen',
nameShort: 'Löschfristen',
description: 'Aufbewahrungsrichtlinien',
url: '/sdk/loeschfristen',
checkpointId: 'CP-RET',
prerequisiteSteps: ['tom'],
isOptional: false,
},
{
id: 'vvt',
phase: 2,
order: 6,
name: 'Verarbeitungsverzeichnis',
nameShort: 'VVT',
description: 'Art. 30 DSGVO Dokumentation',
url: '/sdk/vvt',
checkpointId: 'CP-VVT',
prerequisiteSteps: ['loeschfristen'],
isOptional: false,
},
{
id: 'consent',
phase: 2,
order: 7,
name: 'Rechtliche Vorlagen',
nameShort: 'Vorlagen',
description: 'AGB, Datenschutz, Nutzungsbedingungen',
url: '/sdk/consent',
checkpointId: 'CP-DOC',
prerequisiteSteps: ['vvt'],
isOptional: false,
},
{
id: 'cookie-banner',
phase: 2,
order: 8,
name: 'Cookie Banner',
nameShort: 'Cookies',
description: 'Cookie-Consent Generator',
url: '/sdk/cookie-banner',
checkpointId: 'CP-COOK',
prerequisiteSteps: ['consent'],
isOptional: false,
},
{
id: 'einwilligungen',
phase: 2,
order: 9,
name: 'Einwilligungen',
nameShort: 'Consents',
description: 'Consent-Tracking Setup',
url: '/sdk/einwilligungen',
checkpointId: 'CP-CONS',
prerequisiteSteps: ['cookie-banner'],
isOptional: false,
},
{
id: 'dsr',
phase: 2,
order: 10,
name: 'DSR Portal',
nameShort: 'DSR',
description: 'Betroffenenrechte-Portal',
url: '/sdk/dsr',
checkpointId: 'CP-DSR',
prerequisiteSteps: ['einwilligungen'],
isOptional: false,
},
{
id: 'escalations',
phase: 2,
order: 11,
name: 'Escalations',
nameShort: 'Eskalationen',
description: 'Management-Workflows',
url: '/sdk/escalations',
checkpointId: 'CP-ESC',
prerequisiteSteps: ['dsr'],
isOptional: false,
},
]
// =============================================================================
// USE CASE ASSESSMENT
// =============================================================================
export interface UseCaseStep {
id: string
name: string
completed: boolean
data: Record<string, unknown>
}
export interface AssessmentResult {
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'
applicableRegulations: string[]
recommendedControls: string[]
dsfaRequired: boolean
aiActClassification: string
}
export interface UseCaseAssessment {
id: string
name: string
description: string
category: string
stepsCompleted: number
steps: UseCaseStep[]
assessmentResult: AssessmentResult | null
createdAt: Date
updatedAt: Date
}
// =============================================================================
// SCREENING RESULT
// =============================================================================
export interface ScreeningResult {
id: string
status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
startedAt: Date
completedAt: Date | null
sbom: SBOM | null
securityScan: SecurityScanResult | null
error: string | null
}
// =============================================================================
// SDK STATE
// =============================================================================
export interface SDKState {
// Metadata
version: string
lastModified: Date
// Tenant & User
tenantId: string
userId: string
subscription: SubscriptionTier
// Progress
currentPhase: SDKPhase
currentStep: string
completedSteps: string[]
checkpoints: Record<string, CheckpointStatus>
// Phase 1 Data
useCases: UseCaseAssessment[]
activeUseCase: string | null
screening: ScreeningResult | null
modules: ServiceModule[]
requirements: Requirement[]
controls: Control[]
evidence: Evidence[]
checklist: ChecklistItem[]
risks: Risk[]
// Phase 2 Data
aiActClassification: AIActResult | null
obligations: Obligation[]
dsfa: DSFA | null
toms: TOM[]
retentionPolicies: RetentionPolicy[]
vvt: ProcessingActivity[]
documents: LegalDocument[]
cookieBanner: CookieBannerConfig | null
consents: ConsentRecord[]
dsrConfig: DSRConfig | null
dsrRequests: DSRRequest[]
escalationWorkflows: EscalationWorkflow[]
// Security
sbom: SBOM | null
securityIssues: SecurityIssue[]
securityBacklog: BacklogItem[]
// UI State
commandBarHistory: CommandHistory[]
recentSearches: string[]
preferences: UserPreferences
}
// =============================================================================
// SDK ACTIONS
// =============================================================================
export type SDKAction =
| { type: 'SET_STATE'; payload: Partial<SDKState> }
| { type: 'SET_CURRENT_STEP'; payload: string }
| { type: 'COMPLETE_STEP'; payload: string }
| { type: 'SET_CHECKPOINT_STATUS'; payload: { id: string; status: CheckpointStatus } }
| { type: 'ADD_USE_CASE'; payload: UseCaseAssessment }
| { type: 'UPDATE_USE_CASE'; payload: { id: string; data: Partial<UseCaseAssessment> } }
| { type: 'DELETE_USE_CASE'; payload: string }
| { type: 'SET_ACTIVE_USE_CASE'; payload: string | null }
| { type: 'SET_SCREENING'; payload: ScreeningResult }
| { type: 'ADD_MODULE'; payload: ServiceModule }
| { type: 'UPDATE_MODULE'; payload: { id: string; data: Partial<ServiceModule> } }
| { type: 'ADD_REQUIREMENT'; payload: Requirement }
| { type: 'UPDATE_REQUIREMENT'; payload: { id: string; data: Partial<Requirement> } }
| { type: 'ADD_CONTROL'; payload: Control }
| { type: 'UPDATE_CONTROL'; payload: { id: string; data: Partial<Control> } }
| { type: 'ADD_EVIDENCE'; payload: Evidence }
| { type: 'UPDATE_EVIDENCE'; payload: { id: string; data: Partial<Evidence> } }
| { type: 'DELETE_EVIDENCE'; payload: string }
| { type: 'ADD_RISK'; payload: Risk }
| { type: 'UPDATE_RISK'; payload: { id: string; data: Partial<Risk> } }
| { type: 'DELETE_RISK'; payload: string }
| { type: 'SET_AI_ACT_RESULT'; payload: AIActResult }
| { type: 'ADD_OBLIGATION'; payload: Obligation }
| { type: 'UPDATE_OBLIGATION'; payload: { id: string; data: Partial<Obligation> } }
| { type: 'SET_DSFA'; payload: DSFA }
| { type: 'ADD_TOM'; payload: TOM }
| { type: 'UPDATE_TOM'; payload: { id: string; data: Partial<TOM> } }
| { type: 'ADD_RETENTION_POLICY'; payload: RetentionPolicy }
| { type: 'UPDATE_RETENTION_POLICY'; payload: { id: string; data: Partial<RetentionPolicy> } }
| { type: 'ADD_PROCESSING_ACTIVITY'; payload: ProcessingActivity }
| { type: 'UPDATE_PROCESSING_ACTIVITY'; payload: { id: string; data: Partial<ProcessingActivity> } }
| { type: 'ADD_DOCUMENT'; payload: LegalDocument }
| { type: 'UPDATE_DOCUMENT'; payload: { id: string; data: Partial<LegalDocument> } }
| { type: 'SET_COOKIE_BANNER'; payload: CookieBannerConfig }
| { type: 'SET_DSR_CONFIG'; payload: DSRConfig }
| { type: 'ADD_DSR_REQUEST'; payload: DSRRequest }
| { type: 'UPDATE_DSR_REQUEST'; payload: { id: string; data: Partial<DSRRequest> } }
| { type: 'ADD_ESCALATION_WORKFLOW'; payload: EscalationWorkflow }
| { type: 'UPDATE_ESCALATION_WORKFLOW'; payload: { id: string; data: Partial<EscalationWorkflow> } }
| { type: 'ADD_SECURITY_ISSUE'; payload: SecurityIssue }
| { type: 'UPDATE_SECURITY_ISSUE'; payload: { id: string; data: Partial<SecurityIssue> } }
| { type: 'ADD_BACKLOG_ITEM'; payload: BacklogItem }
| { type: 'UPDATE_BACKLOG_ITEM'; payload: { id: string; data: Partial<BacklogItem> } }
| { type: 'ADD_COMMAND_HISTORY'; payload: CommandHistory }
| { type: 'SET_PREFERENCES'; payload: Partial<UserPreferences> }
| { type: 'RESET_STATE' }
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
export function getStepById(stepId: string): SDKStep | undefined {
return SDK_STEPS.find(s => s.id === stepId)
}
export function getStepByUrl(url: string): SDKStep | undefined {
return SDK_STEPS.find(s => s.url === url)
}
export function getStepsForPhase(phase: SDKPhase): SDKStep[] {
return SDK_STEPS.filter(s => s.phase === phase).sort((a, b) => a.order - b.order)
}
export function getNextStep(currentStepId: string): SDKStep | undefined {
const currentStep = getStepById(currentStepId)
if (!currentStep) return undefined
const stepsInPhase = getStepsForPhase(currentStep.phase)
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
if (currentIndex < stepsInPhase.length - 1) {
return stepsInPhase[currentIndex + 1]
}
if (currentStep.phase === 1) {
return getStepsForPhase(2)[0]
}
return undefined
}
export function getPreviousStep(currentStepId: string): SDKStep | undefined {
const currentStep = getStepById(currentStepId)
if (!currentStep) return undefined
const stepsInPhase = getStepsForPhase(currentStep.phase)
const currentIndex = stepsInPhase.findIndex(s => s.id === currentStepId)
if (currentIndex > 0) {
return stepsInPhase[currentIndex - 1]
}
if (currentStep.phase === 2) {
const phase1Steps = getStepsForPhase(1)
return phase1Steps[phase1Steps.length - 1]
}
return undefined
}
export function getCompletionPercentage(state: SDKState): number {
const totalSteps = SDK_STEPS.length
const completedSteps = state.completedSteps.length
return Math.round((completedSteps / totalSteps) * 100)
}
export function getPhaseCompletionPercentage(state: SDKState, phase: SDKPhase): number {
const phaseSteps = getStepsForPhase(phase)
const completedPhaseSteps = phaseSteps.filter(s => state.completedSteps.includes(s.id))
return Math.round((completedPhaseSteps.length / phaseSteps.length) * 100)
}

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: [
'src/index.ts',
'src/dsgvo.ts',
'src/compliance.ts',
'src/rag.ts',
'src/security.ts',
],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
})

View File

@@ -0,0 +1,55 @@
{
"name": "@breakpilot/compliance-sdk-vanilla",
"version": "0.0.1",
"description": "BreakPilot Compliance SDK - Vanilla JS Embed Script & Web Components",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./embed": {
"import": "./dist/embed.mjs",
"require": "./dist/embed.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@breakpilot/compliance-sdk-core": "workspace:*",
"@breakpilot/compliance-sdk-types": "workspace:*"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.3.0"
},
"peerDependencies": {},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/breakpilot/compliance-sdk.git",
"directory": "packages/vanilla"
},
"keywords": [
"compliance",
"gdpr",
"dsgvo",
"consent",
"cookie-banner",
"web-components",
"embed"
]
}

View File

@@ -0,0 +1,611 @@
/**
* BreakPilot Compliance SDK - Embed Script
*
* Usage:
* <script src="https://cdn.breakpilot.app/sdk/v1/compliance.min.js"></script>
* <script>
* BreakPilotSDK.init({
* apiEndpoint: 'https://compliance.example.com/api/v1',
* apiKey: 'pk_live_xxx',
* autoInjectBanner: true,
* bannerConfig: { position: 'bottom', theme: 'dark', language: 'de' }
* });
* </script>
*/
import { ComplianceClient } from '@breakpilot/compliance-sdk-core'
import type {
ConsentPurpose,
CookieBannerPosition,
CookieBannerTheme,
DSRRequestType,
} from '@breakpilot/compliance-sdk-types'
// ============================================================================
// Types
// ============================================================================
export interface BreakPilotSDKConfig {
apiEndpoint: string
apiKey: string
tenantId?: string
autoInjectBanner?: boolean
bannerConfig?: BannerConfig
onConsentChange?: (consents: Record<ConsentPurpose, boolean>) => void
onReady?: () => void
onError?: (error: Error) => void
debug?: boolean
}
export interface BannerConfig {
position?: CookieBannerPosition
theme?: CookieBannerTheme
language?: 'de' | 'en'
privacyPolicyUrl?: string
imprintUrl?: string
texts?: {
title?: string
description?: string
acceptAll?: string
rejectAll?: string
settings?: string
save?: string
}
customColors?: {
background?: string
text?: string
primary?: string
secondary?: string
}
}
// ============================================================================
// Internal State
// ============================================================================
let _client: ComplianceClient | null = null
let _config: BreakPilotSDKConfig | null = null
let _consents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
let _bannerElement: HTMLElement | null = null
let _isInitialized = false
// ============================================================================
// Translations
// ============================================================================
const TRANSLATIONS = {
de: {
title: 'Cookie-Einwilligung',
description:
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.',
acceptAll: 'Alle akzeptieren',
rejectAll: 'Nur notwendige',
settings: 'Einstellungen',
save: 'Speichern',
privacy: 'Datenschutz',
imprint: 'Impressum',
categories: {
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
PERSONALIZATION: { name: 'Personalisierung', description: 'Angepasste Inhalte' },
THIRD_PARTY: { name: 'Drittanbieter', description: 'Externe Dienste' },
},
},
en: {
title: 'Cookie Consent',
description:
'We use cookies to improve your experience. For more information, please see our privacy policy.',
acceptAll: 'Accept All',
rejectAll: 'Reject Non-Essential',
settings: 'Settings',
save: 'Save',
privacy: 'Privacy Policy',
imprint: 'Imprint',
categories: {
ESSENTIAL: { name: 'Essential', description: 'Required for basic functionality' },
FUNCTIONAL: { name: 'Functional', description: 'Enhanced features' },
ANALYTICS: { name: 'Analytics', description: 'Usage statistics' },
MARKETING: { name: 'Marketing', description: 'Personalized advertising' },
PERSONALIZATION: { name: 'Personalization', description: 'Customized content' },
THIRD_PARTY: { name: 'Third Party', description: 'External services' },
},
},
}
// ============================================================================
// Utility Functions
// ============================================================================
function log(message: string, ...args: unknown[]): void {
if (_config?.debug) {
console.log(`[BreakPilotSDK] ${message}`, ...args)
}
}
function getStoredConsents(): Record<ConsentPurpose, boolean> | null {
try {
const stored = localStorage.getItem('breakpilot_consents')
return stored ? JSON.parse(stored) : null
} catch {
return null
}
}
function storeConsents(consents: Record<ConsentPurpose, boolean>): void {
try {
localStorage.setItem('breakpilot_consents', JSON.stringify(consents))
localStorage.setItem('breakpilot_consents_timestamp', Date.now().toString())
} catch {
log('Failed to store consents')
}
}
function createElement<K extends keyof HTMLElementTagNameMap>(
tag: K,
styles: Partial<CSSStyleDeclaration> = {},
attributes: Record<string, string> = {}
): HTMLElementTagNameMap[K] {
const el = document.createElement(tag)
Object.assign(el.style, styles)
Object.entries(attributes).forEach(([key, value]) => el.setAttribute(key, value))
return el
}
// ============================================================================
// Banner Implementation
// ============================================================================
function createBanner(): HTMLElement {
const config = _config!
const bannerConfig = config.bannerConfig || {}
const lang = bannerConfig.language || 'de'
const t = TRANSLATIONS[lang]
const position = bannerConfig.position || 'BOTTOM'
const theme = bannerConfig.theme || 'LIGHT'
const isDark = theme === 'DARK'
const bgColor = bannerConfig.customColors?.background || (isDark ? '#1a1a1a' : '#ffffff')
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
// Container
const container = createElement(
'div',
{
position: 'fixed',
zIndex: '99999',
left: position === 'CENTER' ? '50%' : '0',
right: position === 'CENTER' ? 'auto' : '0',
top: position === 'TOP' ? '0' : position === 'CENTER' ? '50%' : 'auto',
bottom: position === 'BOTTOM' ? '0' : 'auto',
transform: position === 'CENTER' ? 'translate(-50%, -50%)' : 'none',
maxWidth: position === 'CENTER' ? '500px' : 'none',
backgroundColor: bgColor,
color: textColor,
padding: '20px',
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '14px',
lineHeight: '1.5',
},
{ id: 'breakpilot-consent-banner', role: 'dialog', 'aria-label': t.title }
)
// Title
const title = createElement('h3', {
margin: '0 0 10px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = bannerConfig.texts?.title || t.title
// Description
const description = createElement('p', {
margin: '0 0 15px',
opacity: '0.8',
})
description.textContent = bannerConfig.texts?.description || t.description
// Buttons container
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
alignItems: 'center',
})
// Accept All button
const acceptBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
},
{ type: 'button' }
)
acceptBtn.textContent = bannerConfig.texts?.acceptAll || t.acceptAll
acceptBtn.onclick = () => handleAcceptAll()
// Reject button
const rejectBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
rejectBtn.textContent = bannerConfig.texts?.rejectAll || t.rejectAll
rejectBtn.onclick = () => handleRejectAll()
// Settings button
const settingsBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
settingsBtn.textContent = bannerConfig.texts?.settings || t.settings
settingsBtn.onclick = () => showSettingsPanel()
// Links container
const linksContainer = createElement('div', {
marginLeft: 'auto',
fontSize: '12px',
})
const privacyLink = createElement('a', {
marginRight: '15px',
color: textColor,
textDecoration: 'none',
})
privacyLink.href = bannerConfig.privacyPolicyUrl || '/privacy'
privacyLink.textContent = t.privacy
const imprintLink = createElement('a', {
color: textColor,
textDecoration: 'none',
})
imprintLink.href = bannerConfig.imprintUrl || '/imprint'
imprintLink.textContent = t.imprint
// Assemble
linksContainer.appendChild(privacyLink)
linksContainer.appendChild(imprintLink)
buttonsContainer.appendChild(acceptBtn)
buttonsContainer.appendChild(rejectBtn)
buttonsContainer.appendChild(settingsBtn)
buttonsContainer.appendChild(linksContainer)
container.appendChild(title)
container.appendChild(description)
container.appendChild(buttonsContainer)
return container
}
function showSettingsPanel(): void {
if (!_bannerElement) return
const config = _config!
const bannerConfig = config.bannerConfig || {}
const lang = bannerConfig.language || 'de'
const t = TRANSLATIONS[lang]
const theme = bannerConfig.theme || 'LIGHT'
const isDark = theme === 'DARK'
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
// Clear banner content
_bannerElement.innerHTML = ''
// Title
const title = createElement('h3', {
margin: '0 0 15px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = t.settings
_bannerElement.appendChild(title)
// Categories
const categories: ConsentPurpose[] = [
'ESSENTIAL',
'FUNCTIONAL',
'ANALYTICS',
'MARKETING',
]
categories.forEach(category => {
const catInfo = t.categories[category]
const row = createElement('div', {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 0',
borderBottom: `1px solid ${isDark ? '#333' : '#eee'}`,
})
const labelContainer = createElement('div', {})
const labelName = createElement('div', { fontWeight: '500' })
labelName.textContent = catInfo.name
const labelDesc = createElement('div', { fontSize: '12px', opacity: '0.7' })
labelDesc.textContent = catInfo.description
labelContainer.appendChild(labelName)
labelContainer.appendChild(labelDesc)
const checkbox = createElement(
'input',
{
width: '20px',
height: '20px',
cursor: category === 'ESSENTIAL' ? 'not-allowed' : 'pointer',
},
{
type: 'checkbox',
'data-category': category,
}
)
checkbox.checked = _consents[category]
checkbox.disabled = category === 'ESSENTIAL'
checkbox.onchange = () => {
if (category !== 'ESSENTIAL') {
_consents[category] = checkbox.checked
}
}
row.appendChild(labelContainer)
row.appendChild(checkbox)
_bannerElement!.appendChild(row)
})
// Buttons
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
marginTop: '15px',
})
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
const saveBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
},
{ type: 'button' }
)
saveBtn.textContent = bannerConfig.texts?.save || t.save
saveBtn.onclick = () => handleSaveSettings()
const backBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
backBtn.textContent = 'Zurück'
backBtn.onclick = () => showMainBanner()
buttonsContainer.appendChild(saveBtn)
buttonsContainer.appendChild(backBtn)
_bannerElement.appendChild(buttonsContainer)
}
function showMainBanner(): void {
if (_bannerElement) {
_bannerElement.remove()
}
_bannerElement = createBanner()
document.body.appendChild(_bannerElement)
}
function hideBanner(): void {
if (_bannerElement) {
_bannerElement.remove()
_bannerElement = null
}
}
// ============================================================================
// Consent Handlers
// ============================================================================
function handleAcceptAll(): void {
_consents = {
ESSENTIAL: true,
FUNCTIONAL: true,
ANALYTICS: true,
MARKETING: true,
PERSONALIZATION: true,
THIRD_PARTY: true,
}
applyConsents()
}
function handleRejectAll(): void {
_consents = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
applyConsents()
}
function handleSaveSettings(): void {
applyConsents()
}
function applyConsents(): void {
storeConsents(_consents)
hideBanner()
_config?.onConsentChange?.(_consents)
log('Consents applied:', _consents)
}
// ============================================================================
// Public API
// ============================================================================
function init(config: BreakPilotSDKConfig): void {
if (_isInitialized) {
log('SDK already initialized')
return
}
_config = config
log('Initializing with config:', config)
try {
_client = new ComplianceClient({
apiEndpoint: config.apiEndpoint,
apiKey: config.apiKey,
tenantId: config.tenantId,
})
// Check for stored consents
const storedConsents = getStoredConsents()
if (storedConsents) {
_consents = storedConsents
log('Loaded stored consents:', _consents)
config.onConsentChange?.(storedConsents)
} else if (config.autoInjectBanner !== false) {
// Show banner if no stored consents
showMainBanner()
}
_isInitialized = true
config.onReady?.()
log('SDK initialized successfully')
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
config.onError?.(err)
log('SDK initialization failed:', err)
}
}
function getConsents(): Record<ConsentPurpose, boolean> {
return { ..._consents }
}
function updateConsent(purpose: ConsentPurpose, granted: boolean): void {
if (purpose === 'ESSENTIAL') {
log('Cannot change essential consent')
return
}
_consents[purpose] = granted
storeConsents(_consents)
_config?.onConsentChange?.(_consents)
log('Consent updated:', purpose, granted)
}
function showBanner(): void {
showMainBanner()
}
function hasConsent(purpose: ConsentPurpose): boolean {
return _consents[purpose]
}
function getClient(): ComplianceClient | null {
return _client
}
async function submitDSR(
type: DSRRequestType,
email: string,
name: string
): Promise<{ success: boolean; requestId?: string; error?: string }> {
if (!_client) {
return { success: false, error: 'SDK not initialized' }
}
try {
// This would call the DSR endpoint
log('Submitting DSR:', { type, email, name })
// In a real implementation, this would use the client to submit
return { success: true, requestId: `dsr_${Date.now()}` }
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
return { success: false, error: message }
}
}
function destroy(): void {
hideBanner()
_client = null
_config = null
_isInitialized = false
log('SDK destroyed')
}
// ============================================================================
// Export for IIFE
// ============================================================================
export const BreakPilotSDK = {
init,
getConsents,
updateConsent,
showBanner,
hideBanner,
hasConsent,
getClient,
submitDSR,
destroy,
version: '0.0.1',
}
// Auto-attach to window for script tag usage
if (typeof window !== 'undefined') {
;(window as unknown as Record<string, unknown>).BreakPilotSDK = BreakPilotSDK
}
export default BreakPilotSDK

View File

@@ -0,0 +1,29 @@
/**
* @breakpilot/compliance-sdk-vanilla
*
* Vanilla JS integration for BreakPilot Compliance SDK
*
* Includes:
* - Embed Script (IIFE for <script> tag)
* - Web Components (Custom Elements)
*/
// Re-export embed script
export { BreakPilotSDK, type BreakPilotSDKConfig, type BannerConfig } from './embed'
// Re-export web components
export {
BreakPilotElement,
COMMON_STYLES,
ConsentBannerElement,
DSRPortalElement,
ComplianceScoreElement,
} from './web-components'
// Re-export types from core
export type {
ConsentPurpose,
CookieBannerPosition,
CookieBannerTheme,
DSRRequestType,
} from '@breakpilot/compliance-sdk-types'

View File

@@ -0,0 +1,95 @@
/**
* Base class for BreakPilot Web Components
*/
import { ComplianceClient } from '@breakpilot/compliance-sdk-core'
export abstract class BreakPilotElement extends HTMLElement {
protected client: ComplianceClient | null = null
protected shadow: ShadowRoot
constructor() {
super()
this.shadow = this.attachShadow({ mode: 'open' })
}
connectedCallback(): void {
this.initializeClient()
this.render()
}
disconnectedCallback(): void {
this.cleanup()
}
attributeChangedCallback(
name: string,
_oldValue: string | null,
_newValue: string | null
): void {
if (name === 'api-key' || name === 'api-endpoint') {
this.initializeClient()
}
this.render()
}
protected initializeClient(): void {
const apiKey = this.getAttribute('api-key')
const apiEndpoint =
this.getAttribute('api-endpoint') || 'https://compliance.breakpilot.app/api/v1'
const tenantId = this.getAttribute('tenant-id') || undefined
if (apiKey) {
this.client = new ComplianceClient({
apiKey,
apiEndpoint,
tenantId,
})
}
}
protected abstract render(): void
protected cleanup(): void {}
protected createStyles(css: string): HTMLStyleElement {
const style = document.createElement('style')
style.textContent = css
return style
}
protected emit<T>(eventName: string, detail: T): void {
this.dispatchEvent(
new CustomEvent(eventName, {
detail,
bubbles: true,
composed: true,
})
)
}
}
// Common styles
export const COMMON_STYLES = `
:host {
display: block;
font-family: system-ui, -apple-system, sans-serif;
font-size: 14px;
line-height: 1.5;
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
button {
font-family: inherit;
font-size: inherit;
cursor: pointer;
}
input, textarea {
font-family: inherit;
font-size: inherit;
}
`

View File

@@ -0,0 +1,136 @@
/**
* <breakpilot-compliance-score> Web Component
*
* Displays a circular compliance score indicator
*
* Usage:
* <breakpilot-compliance-score
* score="75"
* size="medium"
* show-label="true">
* </breakpilot-compliance-score>
*/
import { BreakPilotElement, COMMON_STYLES } from './base'
export class ComplianceScoreElement extends BreakPilotElement {
static get observedAttributes(): string[] {
return ['score', 'size', 'show-label', 'label']
}
private get score(): number {
const value = parseInt(this.getAttribute('score') || '0', 10)
return Math.max(0, Math.min(100, value))
}
private get size(): 'small' | 'medium' | 'large' {
return (this.getAttribute('size') as 'small' | 'medium' | 'large') || 'medium'
}
private get showLabel(): boolean {
return this.getAttribute('show-label') !== 'false'
}
private get label(): string {
return this.getAttribute('label') || 'Compliance Score'
}
private getColor(score: number): string {
if (score >= 80) return '#16a34a' // Green
if (score >= 60) return '#f59e0b' // Yellow
if (score >= 40) return '#f97316' // Orange
return '#dc2626' // Red
}
protected render(): void {
const sizes = {
small: { width: 80, strokeWidth: 6, fontSize: 18 },
medium: { width: 120, strokeWidth: 8, fontSize: 24 },
large: { width: 160, strokeWidth: 10, fontSize: 32 },
}
const { width, strokeWidth, fontSize } = sizes[this.size]
const radius = (width - strokeWidth) / 2
const circumference = radius * 2 * Math.PI
const offset = circumference - (this.score / 100) * circumference
const color = this.getColor(this.score)
const styles = `
${COMMON_STYLES}
:host {
display: inline-flex;
flex-direction: column;
align-items: center;
}
.score-container {
position: relative;
}
svg {
transform: rotate(-90deg);
}
.background-circle {
fill: none;
stroke: #e5e5e5;
}
.progress-circle {
fill: none;
stroke: ${color};
stroke-linecap: round;
transition: stroke-dashoffset 0.5s ease-in-out;
}
.score-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: ${fontSize}px;
font-weight: 700;
color: ${color};
}
.label {
margin-top: 8px;
font-size: 14px;
color: #666;
text-align: center;
}
`
this.shadow.innerHTML = `
<style>${styles}</style>
<div class="score-container">
<svg width="${width}" height="${width}">
<circle
class="background-circle"
cx="${width / 2}"
cy="${width / 2}"
r="${radius}"
stroke-width="${strokeWidth}"
/>
<circle
class="progress-circle"
cx="${width / 2}"
cy="${width / 2}"
r="${radius}"
stroke-width="${strokeWidth}"
stroke-dasharray="${circumference}"
stroke-dashoffset="${offset}"
/>
</svg>
<div class="score-text">${this.score}%</div>
</div>
${this.showLabel ? `<div class="label">${this.label}</div>` : ''}
`
}
}
// Register the custom element
if (typeof customElements !== 'undefined') {
customElements.define('breakpilot-compliance-score', ComplianceScoreElement)
}

View File

@@ -0,0 +1,379 @@
/**
* <breakpilot-consent-banner> Web Component
*
* Usage:
* <breakpilot-consent-banner
* api-key="pk_live_xxx"
* position="bottom"
* theme="light"
* language="de"
* privacy-url="/privacy"
* imprint-url="/imprint">
* </breakpilot-consent-banner>
*/
import type {
ConsentPurpose,
CookieBannerPosition,
CookieBannerTheme,
} from '@breakpilot/compliance-sdk-types'
import { BreakPilotElement, COMMON_STYLES } from './base'
const TRANSLATIONS = {
de: {
title: 'Cookie-Einwilligung',
description:
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.',
acceptAll: 'Alle akzeptieren',
rejectAll: 'Nur notwendige',
settings: 'Einstellungen',
save: 'Einstellungen speichern',
back: 'Zurück',
privacy: 'Datenschutz',
imprint: 'Impressum',
categories: {
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
},
},
en: {
title: 'Cookie Consent',
description:
'We use cookies to improve your experience. For more information, please see our privacy policy.',
acceptAll: 'Accept All',
rejectAll: 'Reject Non-Essential',
settings: 'Settings',
save: 'Save Settings',
back: 'Back',
privacy: 'Privacy Policy',
imprint: 'Imprint',
categories: {
ESSENTIAL: { name: 'Essential', description: 'Required for basic functionality' },
FUNCTIONAL: { name: 'Functional', description: 'Enhanced features' },
ANALYTICS: { name: 'Analytics', description: 'Usage statistics' },
MARKETING: { name: 'Marketing', description: 'Personalized advertising' },
},
},
}
export class ConsentBannerElement extends BreakPilotElement {
static get observedAttributes(): string[] {
return [
'api-key',
'api-endpoint',
'position',
'theme',
'language',
'privacy-url',
'imprint-url',
]
}
private consents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
private showSettings = false
connectedCallback(): void {
super.connectedCallback()
this.loadStoredConsents()
}
private get position(): CookieBannerPosition {
return (this.getAttribute('position')?.toUpperCase() as CookieBannerPosition) || 'BOTTOM'
}
private get theme(): CookieBannerTheme {
return (this.getAttribute('theme')?.toUpperCase() as CookieBannerTheme) || 'LIGHT'
}
private get language(): 'de' | 'en' {
return (this.getAttribute('language') as 'de' | 'en') || 'de'
}
private get privacyUrl(): string {
return this.getAttribute('privacy-url') || '/privacy'
}
private get imprintUrl(): string {
return this.getAttribute('imprint-url') || '/imprint'
}
private get t() {
return TRANSLATIONS[this.language]
}
private loadStoredConsents(): void {
try {
const stored = localStorage.getItem('breakpilot_consents')
if (stored) {
this.consents = JSON.parse(stored)
this.hide()
}
} catch {
// Ignore errors
}
}
private storeConsents(): void {
try {
localStorage.setItem('breakpilot_consents', JSON.stringify(this.consents))
localStorage.setItem('breakpilot_consents_timestamp', Date.now().toString())
} catch {
// Ignore errors
}
}
private handleAcceptAll = (): void => {
this.consents = {
ESSENTIAL: true,
FUNCTIONAL: true,
ANALYTICS: true,
MARKETING: true,
PERSONALIZATION: true,
THIRD_PARTY: true,
}
this.applyConsents()
}
private handleRejectAll = (): void => {
this.consents = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
this.applyConsents()
}
private handleSave = (): void => {
this.applyConsents()
}
private applyConsents(): void {
this.storeConsents()
this.emit('consent-change', { consents: this.consents })
this.hide()
}
private toggleSettings = (): void => {
this.showSettings = !this.showSettings
this.render()
}
public show(): void {
this.style.display = 'block'
this.render()
}
public hide(): void {
this.style.display = 'none'
}
public getConsents(): Record<ConsentPurpose, boolean> {
return { ...this.consents }
}
protected render(): void {
const isDark = this.theme === 'DARK'
const bgColor = isDark ? '#1a1a1a' : '#ffffff'
const textColor = isDark ? '#ffffff' : '#1a1a1a'
const borderColor = isDark ? '#333' : '#eee'
const positionStyles = {
TOP: 'top: 0; left: 0; right: 0;',
BOTTOM: 'bottom: 0; left: 0; right: 0;',
CENTER: 'top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 500px;',
}
const styles = `
${COMMON_STYLES}
:host {
position: fixed;
z-index: 99999;
${positionStyles[this.position]}
}
.banner {
background-color: ${bgColor};
color: ${textColor};
padding: 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
}
.title {
margin: 0 0 10px;
font-size: 18px;
font-weight: 600;
}
.description {
margin: 0 0 15px;
opacity: 0.8;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.btn {
padding: 10px 20px;
border-radius: 4px;
border: none;
font-weight: 500;
}
.btn-primary {
background-color: ${isDark ? '#ffffff' : '#1a1a1a'};
color: ${isDark ? '#1a1a1a' : '#ffffff'};
}
.btn-secondary {
background-color: transparent;
border: 1px solid ${textColor};
color: ${textColor};
}
.links {
margin-left: auto;
font-size: 12px;
}
.links a {
color: ${textColor};
text-decoration: none;
margin-right: 15px;
}
.links a:last-child {
margin-right: 0;
}
.category {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid ${borderColor};
}
.category-name {
font-weight: 500;
}
.category-description {
font-size: 12px;
opacity: 0.7;
}
.checkbox {
width: 20px;
height: 20px;
}
.checkbox:disabled {
cursor: not-allowed;
}
`
if (this.showSettings) {
this.renderSettings(styles)
} else {
this.renderMain(styles)
}
}
private renderMain(styles: string): void {
const t = this.t
this.shadow.innerHTML = `
<style>${styles}</style>
<div class="banner">
<h3 class="title">${t.title}</h3>
<p class="description">${t.description}</p>
<div class="buttons">
<button class="btn btn-primary" id="accept-all">${t.acceptAll}</button>
<button class="btn btn-secondary" id="reject-all">${t.rejectAll}</button>
<button class="btn btn-secondary" id="settings">${t.settings}</button>
<div class="links">
<a href="${this.privacyUrl}">${t.privacy}</a>
<a href="${this.imprintUrl}">${t.imprint}</a>
</div>
</div>
</div>
`
this.shadow.getElementById('accept-all')!.onclick = this.handleAcceptAll
this.shadow.getElementById('reject-all')!.onclick = this.handleRejectAll
this.shadow.getElementById('settings')!.onclick = this.toggleSettings
}
private renderSettings(styles: string): void {
const t = this.t
const categories: ConsentPurpose[] = ['ESSENTIAL', 'FUNCTIONAL', 'ANALYTICS', 'MARKETING']
const categoriesHtml = categories
.map(
cat => `
<div class="category">
<div>
<div class="category-name">${t.categories[cat].name}</div>
<div class="category-description">${t.categories[cat].description}</div>
</div>
<input
type="checkbox"
class="checkbox"
data-category="${cat}"
${this.consents[cat] ? 'checked' : ''}
${cat === 'ESSENTIAL' ? 'disabled' : ''}
/>
</div>
`
)
.join('')
this.shadow.innerHTML = `
<style>${styles}</style>
<div class="banner">
<h3 class="title">${t.settings}</h3>
${categoriesHtml}
<div class="buttons" style="margin-top: 15px;">
<button class="btn btn-primary" id="save">${t.save}</button>
<button class="btn btn-secondary" id="back">${t.back}</button>
</div>
</div>
`
this.shadow.getElementById('save')!.onclick = this.handleSave
this.shadow.getElementById('back')!.onclick = this.toggleSettings
// Handle checkbox changes
this.shadow.querySelectorAll<HTMLInputElement>('.checkbox').forEach(checkbox => {
checkbox.onchange = () => {
const category = checkbox.dataset.category as ConsentPurpose
if (category !== 'ESSENTIAL') {
this.consents[category] = checkbox.checked
}
}
})
}
}
// Register the custom element
if (typeof customElements !== 'undefined') {
customElements.define('breakpilot-consent-banner', ConsentBannerElement)
}

View File

@@ -0,0 +1,464 @@
/**
* <breakpilot-dsr-portal> Web Component
*
* Data Subject Request Portal for GDPR rights
*
* Usage:
* <breakpilot-dsr-portal
* api-key="pk_live_xxx"
* language="de">
* </breakpilot-dsr-portal>
*/
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
import { BreakPilotElement, COMMON_STYLES } from './base'
const TRANSLATIONS = {
de: {
title: 'Betroffenenrechte-Portal',
subtitle:
'Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und füllen Sie das Formular aus.',
requestType: 'Art der Anfrage',
name: 'Ihr Name',
namePlaceholder: 'Max Mustermann',
email: 'E-Mail-Adresse',
emailPlaceholder: 'max@example.com',
additionalInfo: 'Zusätzliche Informationen (optional)',
additionalInfoPlaceholder: 'Weitere Details zu Ihrer Anfrage...',
submit: 'Anfrage einreichen',
submitting: 'Wird gesendet...',
successTitle: 'Anfrage eingereicht',
successMessage:
'Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine Bestätigung per E-Mail an',
disclaimer:
'Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen Fällen kann diese Frist um weitere zwei Monate verlängert werden.',
types: {
ACCESS: {
name: 'Auskunft (Art. 15)',
description: 'Welche Daten haben Sie über mich gespeichert?',
},
RECTIFICATION: {
name: 'Berichtigung (Art. 16)',
description: 'Korrigieren Sie falsche Daten über mich.',
},
ERASURE: {
name: 'Löschung (Art. 17)',
description: 'Löschen Sie alle meine personenbezogenen Daten.',
},
PORTABILITY: {
name: 'Datenübertragbarkeit (Art. 20)',
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
},
RESTRICTION: {
name: 'Einschränkung (Art. 18)',
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
},
OBJECTION: {
name: 'Widerspruch (Art. 21)',
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
},
},
},
en: {
title: 'Data Subject Rights Portal',
subtitle:
'Here you can exercise your rights under GDPR. Select the type of request and fill out the form.',
requestType: 'Request Type',
name: 'Your Name',
namePlaceholder: 'John Doe',
email: 'Email Address',
emailPlaceholder: 'john@example.com',
additionalInfo: 'Additional Information (optional)',
additionalInfoPlaceholder: 'Any additional details about your request...',
submit: 'Submit Request',
submitting: 'Submitting...',
successTitle: 'Request Submitted',
successMessage:
'We will process your request within 30 days. You will receive a confirmation email at',
disclaimer:
'Your request will be processed in accordance with Article 12 GDPR within one month. In complex cases, this period may be extended by up to two additional months.',
types: {
ACCESS: {
name: 'Access (Art. 15)',
description: 'What data do you have about me?',
},
RECTIFICATION: {
name: 'Rectification (Art. 16)',
description: 'Correct inaccurate data about me.',
},
ERASURE: {
name: 'Erasure (Art. 17)',
description: 'Delete all my personal data.',
},
PORTABILITY: {
name: 'Data Portability (Art. 20)',
description: 'Export my data in a machine-readable format.',
},
RESTRICTION: {
name: 'Restriction (Art. 18)',
description: 'Restrict the processing of my data.',
},
OBJECTION: {
name: 'Objection (Art. 21)',
description: 'I object to the processing of my data.',
},
},
},
}
export class DSRPortalElement extends BreakPilotElement {
static get observedAttributes(): string[] {
return ['api-key', 'api-endpoint', 'tenant-id', 'language']
}
private selectedType: DSRRequestType | null = null
private name = ''
private email = ''
private additionalInfo = ''
private isSubmitting = false
private isSubmitted = false
private error: string | null = null
private get language(): 'de' | 'en' {
return (this.getAttribute('language') as 'de' | 'en') || 'de'
}
private get t() {
return TRANSLATIONS[this.language]
}
private handleTypeSelect = (type: DSRRequestType): void => {
this.selectedType = type
this.render()
}
private handleSubmit = async (e: Event): Promise<void> => {
e.preventDefault()
if (!this.selectedType || !this.email || !this.name) {
return
}
this.isSubmitting = true
this.error = null
this.render()
try {
// In a real implementation, this would use the client
// For now, we simulate a successful submission
await new Promise(resolve => setTimeout(resolve, 1000))
this.emit('dsr-submitted', {
type: this.selectedType,
email: this.email,
name: this.name,
additionalInfo: this.additionalInfo,
})
this.isSubmitted = true
} catch (err) {
this.error = err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'
} finally {
this.isSubmitting = false
this.render()
}
}
protected render(): void {
const styles = `
${COMMON_STYLES}
:host {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.portal {
background: #fff;
}
.title {
margin: 0 0 10px;
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
}
.subtitle {
margin: 0 0 20px;
color: #666;
}
.form-group {
margin-bottom: 20px;
}
.label {
display: block;
font-weight: 500;
margin-bottom: 10px;
}
.type-options {
display: grid;
gap: 10px;
}
.type-option {
display: flex;
padding: 15px;
border: 2px solid #ddd;
border-radius: 8px;
cursor: pointer;
background: #fff;
transition: all 0.2s;
}
.type-option:hover {
border-color: #999;
}
.type-option.selected {
border-color: #1a1a1a;
background: #f5f5f5;
}
.type-option input {
margin-right: 15px;
}
.type-name {
font-weight: 500;
}
.type-description {
font-size: 13px;
color: #666;
}
.input {
width: 100%;
padding: 12px;
font-size: 14px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.input:focus {
outline: none;
border-color: #1a1a1a;
}
.textarea {
resize: vertical;
min-height: 100px;
}
.error {
padding: 12px;
background: #fef2f2;
color: #dc2626;
border-radius: 4px;
margin-bottom: 15px;
}
.btn-submit {
width: 100%;
padding: 12px 24px;
font-size: 16px;
font-weight: 500;
color: #fff;
background: #1a1a1a;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.disclaimer {
margin-top: 20px;
font-size: 12px;
color: #666;
}
.success {
text-align: center;
padding: 40px 20px;
background: #f0fdf4;
border-radius: 8px;
}
.success-icon {
font-size: 48px;
margin-bottom: 20px;
}
.success-title {
margin: 0 0 10px;
color: #166534;
}
.success-message {
margin: 0;
color: #166534;
}
`
if (this.isSubmitted) {
this.renderSuccess(styles)
} else {
this.renderForm(styles)
}
}
private renderForm(styles: string): void {
const t = this.t
const types: DSRRequestType[] = [
'ACCESS',
'RECTIFICATION',
'ERASURE',
'PORTABILITY',
'RESTRICTION',
'OBJECTION',
]
const typesHtml = types
.map(
type => `
<label class="type-option ${this.selectedType === type ? 'selected' : ''}">
<input
type="radio"
name="dsrType"
value="${type}"
${this.selectedType === type ? 'checked' : ''}
/>
<div>
<div class="type-name">${t.types[type].name}</div>
<div class="type-description">${t.types[type].description}</div>
</div>
</label>
`
)
.join('')
const isValid = this.selectedType && this.email && this.name
const isDisabled = !isValid || this.isSubmitting
this.shadow.innerHTML = `
<style>${styles}</style>
<div class="portal">
<h2 class="title">${t.title}</h2>
<p class="subtitle">${t.subtitle}</p>
<form id="dsr-form">
<div class="form-group">
<label class="label">${t.requestType} *</label>
<div class="type-options">
${typesHtml}
</div>
</div>
<div class="form-group">
<label class="label">${t.name} *</label>
<input
type="text"
class="input"
id="name-input"
value="${this.name}"
placeholder="${t.namePlaceholder}"
required
/>
</div>
<div class="form-group">
<label class="label">${t.email} *</label>
<input
type="email"
class="input"
id="email-input"
value="${this.email}"
placeholder="${t.emailPlaceholder}"
required
/>
</div>
<div class="form-group">
<label class="label">${t.additionalInfo}</label>
<textarea
class="input textarea"
id="info-input"
placeholder="${t.additionalInfoPlaceholder}"
rows="4"
>${this.additionalInfo}</textarea>
</div>
${this.error ? `<div class="error">${this.error}</div>` : ''}
<button
type="submit"
class="btn-submit"
${isDisabled ? 'disabled' : ''}
>
${this.isSubmitting ? t.submitting : t.submit}
</button>
</form>
<p class="disclaimer">${t.disclaimer}</p>
</div>
`
// Bind events
const form = this.shadow.getElementById('dsr-form') as HTMLFormElement
form.onsubmit = this.handleSubmit
const nameInput = this.shadow.getElementById('name-input') as HTMLInputElement
nameInput.oninput = e => {
this.name = (e.target as HTMLInputElement).value
}
const emailInput = this.shadow.getElementById('email-input') as HTMLInputElement
emailInput.oninput = e => {
this.email = (e.target as HTMLInputElement).value
}
const infoInput = this.shadow.getElementById('info-input') as HTMLTextAreaElement
infoInput.oninput = e => {
this.additionalInfo = (e.target as HTMLTextAreaElement).value
}
// Bind radio buttons
this.shadow.querySelectorAll<HTMLInputElement>('input[name="dsrType"]').forEach(radio => {
radio.onchange = () => {
this.handleTypeSelect(radio.value as DSRRequestType)
}
})
}
private renderSuccess(styles: string): void {
const t = this.t
this.shadow.innerHTML = `
<style>${styles}</style>
<div class="portal">
<div class="success">
<div class="success-icon">✓</div>
<h2 class="success-title">${t.successTitle}</h2>
<p class="success-message">
${t.successMessage} ${this.email}.
</p>
</div>
</div>
`
}
}
// Register the custom element
if (typeof customElements !== 'undefined') {
customElements.define('breakpilot-dsr-portal', DSRPortalElement)
}

View File

@@ -0,0 +1,13 @@
/**
* BreakPilot Compliance SDK - Web Components
*
* Available components:
* - <breakpilot-consent-banner>
* - <breakpilot-dsr-portal>
* - <breakpilot-compliance-score>
*/
export { BreakPilotElement, COMMON_STYLES } from './base'
export { ConsentBannerElement } from './consent-banner'
export { DSRPortalElement } from './dsr-portal'
export { ComplianceScoreElement } from './compliance-score'

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,32 @@
import { defineConfig } from 'tsup'
export default defineConfig([
// Main bundle with web components
{
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
},
// Embed script (IIFE for <script> tag usage)
{
entry: ['src/embed.ts'],
format: ['iife'],
globalName: 'BreakPilotSDK',
outDir: 'dist',
minify: true,
sourcemap: true,
outExtension: () => ({ js: '.min.js' }),
},
// Embed script (ESM for modern usage)
{
entry: ['src/embed.ts'],
format: ['esm', 'cjs'],
dts: false,
splitting: false,
sourcemap: true,
},
])

View File

@@ -0,0 +1,53 @@
{
"name": "@breakpilot/compliance-sdk-vue",
"version": "0.0.1",
"description": "BreakPilot Compliance SDK - Vue 3 Plugin",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint src/",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@breakpilot/compliance-sdk-core": "workspace:*",
"@breakpilot/compliance-sdk-types": "workspace:*"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.3.0",
"vue": "^3.4.0"
},
"peerDependencies": {
"vue": "^3.3.0"
},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/breakpilot/compliance-sdk.git",
"directory": "packages/vue"
},
"keywords": [
"compliance",
"gdpr",
"dsgvo",
"vue",
"vue3",
"plugin"
]
}

View File

@@ -0,0 +1,9 @@
/**
* Vue 3 Composables for BreakPilot Compliance SDK
*/
export { useCompliance, type UseComplianceReturn } from './useCompliance'
export { useDSGVO, type UseDSGVOReturn } from './useDSGVO'
export { useRAG, type UseRAGReturn } from './useRAG'
export { useControls, type UseControlsReturn } from './useControls'
export { useSecurity, type UseSecurityReturn, type ScanOptions } from './useSecurity'

View File

@@ -0,0 +1,140 @@
/**
* Main compliance composable
*/
import { computed, type ComputedRef } from 'vue'
import { useComplianceStore, type ComplianceStore } from '../plugin'
import type { SDKState, SDKAction, SDKStep } from '@breakpilot/compliance-sdk-types'
import { SDK_STEPS } from '@breakpilot/compliance-sdk-types'
export interface UseComplianceReturn {
// Store
state: SDKState
dispatch: (action: SDKAction) => void
store: ComplianceStore
// Status
isReady: ComputedRef<boolean>
error: ComputedRef<Error | null>
// Progress
completionPercentage: ComputedRef<number>
phase1Completion: ComputedRef<number>
phase2Completion: ComputedRef<number>
completedSteps: ComputedRef<string[]>
// Navigation
currentStep: ComputedRef<SDKStep | undefined>
nextStep: ComputedRef<SDKStep | undefined>
previousStep: ComputedRef<SDKStep | undefined>
canGoNext: ComputedRef<boolean>
canGoPrevious: ComputedRef<boolean>
goToStep: (stepId: string) => void
goNext: () => void
goPrevious: () => void
// Actions
completeStep: (stepId: string) => void
resetProgress: () => void
saveState: () => Promise<void>
}
export function useCompliance(): UseComplianceReturn {
const store = useComplianceStore()
const { state, dispatch, client } = store
// Status
const isReady = computed(() => store.isReady)
const error = computed(() => store.error)
// Progress calculations
const completedSteps = computed(() => state.completedSteps)
const completionPercentage = computed(() => {
return Math.round((state.completedSteps.length / SDK_STEPS.length) * 100)
})
const phase1Completion = computed(() => {
const phase1Steps = SDK_STEPS.filter(s => s.phase === 1)
const completed = phase1Steps.filter(s => state.completedSteps.includes(s.id))
return Math.round((completed.length / phase1Steps.length) * 100)
})
const phase2Completion = computed(() => {
const phase2Steps = SDK_STEPS.filter(s => s.phase === 2)
const completed = phase2Steps.filter(s => state.completedSteps.includes(s.id))
return Math.round((completed.length / phase2Steps.length) * 100)
})
// Navigation
const currentStep = computed(() => SDK_STEPS.find(s => s.id === state.currentStep))
const currentIndex = computed(() => SDK_STEPS.findIndex(s => s.id === state.currentStep))
const nextStep = computed(() => {
const idx = currentIndex.value
return idx < SDK_STEPS.length - 1 ? SDK_STEPS[idx + 1] : undefined
})
const previousStep = computed(() => {
const idx = currentIndex.value
return idx > 0 ? SDK_STEPS[idx - 1] : undefined
})
const canGoNext = computed(() => currentIndex.value < SDK_STEPS.length - 1)
const canGoPrevious = computed(() => currentIndex.value > 0)
const goToStep = (stepId: string): void => {
dispatch({ type: 'SET_CURRENT_STEP', payload: stepId })
}
const goNext = (): void => {
if (nextStep.value) {
goToStep(nextStep.value.id)
}
}
const goPrevious = (): void => {
if (previousStep.value) {
goToStep(previousStep.value.id)
}
}
// Actions
const completeStep = (stepId: string): void => {
if (!state.completedSteps.includes(stepId)) {
dispatch({ type: 'COMPLETE_STEP', payload: stepId })
}
}
const resetProgress = (): void => {
dispatch({ type: 'RESET_PROGRESS' })
}
const saveState = async (): Promise<void> => {
await client.saveState(state)
}
return {
state,
dispatch,
store,
isReady,
error,
completionPercentage,
phase1Completion,
phase2Completion,
completedSteps,
currentStep,
nextStep,
previousStep,
canGoNext,
canGoPrevious,
goToStep,
goNext,
goPrevious,
completeStep,
resetProgress,
saveState,
}
}

View File

@@ -0,0 +1,258 @@
/**
* Controls composable for compliance controls management
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
Control,
Evidence,
Risk,
ControlDomain,
ImplementationStatus,
EvidenceType,
RiskLikelihood,
RiskImpact,
} from '@breakpilot/compliance-sdk-types'
export interface UseControlsReturn {
// Controls
controls: ComputedRef<Control[]>
controlsByDomain: ComputedRef<Record<ControlDomain, Control[]>>
implementedControls: ComputedRef<Control[]>
pendingControls: ComputedRef<Control[]>
getControl: (id: string) => Control | undefined
addControl: (control: Omit<Control, 'id' | 'createdAt' | 'updatedAt'>) => void
updateControl: (id: string, updates: Partial<Control>) => void
deleteControl: (id: string) => void
setControlStatus: (id: string, status: ImplementationStatus) => void
// Evidence
evidence: ComputedRef<Evidence[]>
validEvidence: ComputedRef<Evidence[]>
expiredEvidence: ComputedRef<Evidence[]>
getEvidenceForControl: (controlId: string) => Evidence[]
addEvidence: (evidence: Omit<Evidence, 'id' | 'uploadedAt'>) => void
updateEvidence: (id: string, updates: Partial<Evidence>) => void
deleteEvidence: (id: string) => void
// Risks
risks: ComputedRef<Risk[]>
criticalRisks: ComputedRef<Risk[]>
unmitigatedRisks: ComputedRef<Risk[]>
getRisksForControl: (controlId: string) => Risk[]
addRisk: (risk: Omit<Risk, 'id' | 'createdAt' | 'updatedAt'>) => void
updateRisk: (id: string, updates: Partial<Risk>) => void
deleteRisk: (id: string) => void
assessRisk: (id: string, likelihood: RiskLikelihood, impact: RiskImpact) => void
// Compliance Score
complianceScore: ComputedRef<number>
scoreByDomain: ComputedRef<Record<ControlDomain, number>>
}
export function useControls(): UseControlsReturn {
const store = useComplianceStore()
const { state, dispatch, compliance } = store
// Controls
const controls = computed(() => state.controls)
const controlsByDomain = computed(() => {
const domains: ControlDomain[] = [
'ACCESS_CONTROL',
'DATA_PROTECTION',
'NETWORK_SECURITY',
'INCIDENT_RESPONSE',
'BUSINESS_CONTINUITY',
'COMPLIANCE',
'RISK_MANAGEMENT',
'ASSET_MANAGEMENT',
'HUMAN_RESOURCES',
]
return domains.reduce(
(acc, domain) => {
acc[domain] = state.controls.filter(c => c.domain === domain)
return acc
},
{} as Record<ControlDomain, Control[]>
)
})
const implementedControls = computed(() =>
state.controls.filter(c => c.implementationStatus === 'IMPLEMENTED')
)
const pendingControls = computed(() =>
state.controls.filter(c => c.implementationStatus === 'NOT_IMPLEMENTED')
)
const getControl = (id: string): Control | undefined => {
return state.controls.find(c => c.id === id)
}
const addControl = (control: Omit<Control, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_CONTROL', payload: control })
}
const updateControl = (id: string, updates: Partial<Control>): void => {
dispatch({ type: 'UPDATE_CONTROL', payload: { id, updates } })
}
const deleteControl = (id: string): void => {
dispatch({ type: 'DELETE_CONTROL', payload: id })
}
const setControlStatus = (id: string, status: ImplementationStatus): void => {
dispatch({
type: 'UPDATE_CONTROL',
payload: { id, updates: { implementationStatus: status } },
})
}
// Evidence
const evidence = computed(() => state.evidence)
const validEvidence = computed(() => {
const now = new Date()
return state.evidence.filter(e => !e.validUntil || new Date(e.validUntil) > now)
})
const expiredEvidence = computed(() => {
const now = new Date()
return state.evidence.filter(e => e.validUntil && new Date(e.validUntil) <= now)
})
const getEvidenceForControl = (controlId: string): Evidence[] => {
return state.evidence.filter(e => e.controlIds.includes(controlId))
}
const addEvidence = (ev: Omit<Evidence, 'id' | 'uploadedAt'>): void => {
dispatch({ type: 'ADD_EVIDENCE', payload: ev })
}
const updateEvidence = (id: string, updates: Partial<Evidence>): void => {
dispatch({ type: 'UPDATE_EVIDENCE', payload: { id, updates } })
}
const deleteEvidence = (id: string): void => {
dispatch({ type: 'DELETE_EVIDENCE', payload: id })
}
// Risks
const risks = computed(() => state.risks)
const criticalRisks = computed(() => compliance.getCriticalRisks())
const unmitigatedRisks = computed(() =>
state.risks.filter(r => r.status === 'IDENTIFIED' || r.status === 'ANALYZED')
)
const getRisksForControl = (controlId: string): Risk[] => {
return state.risks.filter(r => r.controlIds?.includes(controlId))
}
const addRisk = (risk: Omit<Risk, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_RISK', payload: risk })
}
const updateRisk = (id: string, updates: Partial<Risk>): void => {
dispatch({ type: 'UPDATE_RISK', payload: { id, updates } })
}
const deleteRisk = (id: string): void => {
dispatch({ type: 'DELETE_RISK', payload: id })
}
const assessRisk = (id: string, likelihood: RiskLikelihood, impact: RiskImpact): void => {
const severity = likelihood * impact
let severityLevel: Risk['severity']
if (severity >= 20) severityLevel = 'CRITICAL'
else if (severity >= 12) severityLevel = 'HIGH'
else if (severity >= 6) severityLevel = 'MEDIUM'
else severityLevel = 'LOW'
dispatch({
type: 'UPDATE_RISK',
payload: {
id,
updates: {
likelihood,
impact,
severity: severityLevel,
status: 'ANALYZED',
},
},
})
}
// Compliance Score
const complianceScore = computed(() => {
const score = compliance.calculateComplianceScore()
return score.overall
})
const scoreByDomain = computed(() => {
const domains: ControlDomain[] = [
'ACCESS_CONTROL',
'DATA_PROTECTION',
'NETWORK_SECURITY',
'INCIDENT_RESPONSE',
'BUSINESS_CONTINUITY',
'COMPLIANCE',
'RISK_MANAGEMENT',
'ASSET_MANAGEMENT',
'HUMAN_RESOURCES',
]
return domains.reduce(
(acc, domain) => {
const domainControls = state.controls.filter(c => c.domain === domain)
if (domainControls.length === 0) {
acc[domain] = 0
return acc
}
const implemented = domainControls.filter(c => c.implementationStatus === 'IMPLEMENTED')
const partial = domainControls.filter(c => c.implementationStatus === 'PARTIAL')
acc[domain] = Math.round(
((implemented.length + partial.length * 0.5) / domainControls.length) * 100
)
return acc
},
{} as Record<ControlDomain, number>
)
})
return {
controls,
controlsByDomain,
implementedControls,
pendingControls,
getControl,
addControl,
updateControl,
deleteControl,
setControlStatus,
evidence,
validEvidence,
expiredEvidence,
getEvidenceForControl,
addEvidence,
updateEvidence,
deleteEvidence,
risks,
criticalRisks,
unmitigatedRisks,
getRisksForControl,
addRisk,
updateRisk,
deleteRisk,
assessRisk,
complianceScore,
scoreByDomain,
}
}

View File

@@ -0,0 +1,246 @@
/**
* DSGVO/GDPR composable
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
DSRRequest,
DSRRequestType,
ConsentRecord,
ConsentPurpose,
ProcessingActivity,
TOM,
TOMCategory,
DSFA,
RetentionPolicy,
} from '@breakpilot/compliance-sdk-types'
export interface UseDSGVOReturn {
// DSR
dsrRequests: ComputedRef<DSRRequest[]>
pendingDSRCount: ComputedRef<number>
submitDSR: (type: DSRRequestType, email: string, name: string) => Promise<DSRRequest>
updateDSRStatus: (
requestId: string,
status: DSRRequest['status'],
notes?: string
) => Promise<void>
// Consent
consents: ComputedRef<ConsentRecord[]>
activeConsents: ComputedRef<ConsentRecord[]>
hasConsent: (purpose: ConsentPurpose) => boolean
recordConsent: (
purpose: ConsentPurpose,
granted: boolean,
source: string
) => Promise<ConsentRecord>
revokeConsent: (consentId: string) => Promise<void>
// VVT (Processing Activities)
processingActivities: ComputedRef<ProcessingActivity[]>
addProcessingActivity: (activity: Omit<ProcessingActivity, 'id' | 'createdAt'>) => void
updateProcessingActivity: (id: string, updates: Partial<ProcessingActivity>) => void
deleteProcessingActivity: (id: string) => void
// TOM
toms: ComputedRef<TOM[]>
tomsByCategory: ComputedRef<Record<TOMCategory, TOM[]>>
addTOM: (tom: Omit<TOM, 'id' | 'createdAt' | 'updatedAt'>) => void
updateTOM: (id: string, updates: Partial<TOM>) => void
deleteTOM: (id: string) => void
// DSFA
dsfas: ComputedRef<DSFA[]>
createDSFA: (title: string, description: string) => DSFA
updateDSFA: (id: string, updates: Partial<DSFA>) => void
// Retention
retentionPolicies: ComputedRef<RetentionPolicy[]>
addRetentionPolicy: (policy: Omit<RetentionPolicy, 'id'>) => void
updateRetentionPolicy: (id: string, updates: Partial<RetentionPolicy>) => void
deleteRetentionPolicy: (id: string) => void
// Loading state
isLoading: Ref<boolean>
}
export function useDSGVO(): UseDSGVOReturn {
const store = useComplianceStore()
const { state, dispatch, dsgvo } = store
const isLoading = ref(false)
// DSR
const dsrRequests = computed(() => state.dsrRequests)
const pendingDSRCount = computed(
() => state.dsrRequests.filter(r => r.status === 'PENDING' || r.status === 'IN_PROGRESS').length
)
const submitDSR = async (
type: DSRRequestType,
email: string,
name: string
): Promise<DSRRequest> => {
isLoading.value = true
try {
return await dsgvo.submitDSR(type, email, name)
} finally {
isLoading.value = false
}
}
const updateDSRStatus = async (
requestId: string,
status: DSRRequest['status'],
notes?: string
): Promise<void> => {
isLoading.value = true
try {
await dsgvo.updateDSRStatus(requestId, status, notes)
} finally {
isLoading.value = false
}
}
// Consent
const consents = computed(() => state.consents)
const activeConsents = computed(() =>
state.consents.filter(c => c.status === 'ACTIVE' && !c.revokedAt)
)
const hasConsent = (purpose: ConsentPurpose): boolean => {
return dsgvo.hasActiveConsent(purpose)
}
const recordConsent = async (
purpose: ConsentPurpose,
granted: boolean,
source: string
): Promise<ConsentRecord> => {
isLoading.value = true
try {
return await dsgvo.recordConsent(purpose, granted, source)
} finally {
isLoading.value = false
}
}
const revokeConsent = async (consentId: string): Promise<void> => {
isLoading.value = true
try {
await dsgvo.revokeConsent(consentId)
} finally {
isLoading.value = false
}
}
// VVT
const processingActivities = computed(() => state.processingActivities)
const addProcessingActivity = (activity: Omit<ProcessingActivity, 'id' | 'createdAt'>): void => {
dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: activity })
}
const updateProcessingActivity = (id: string, updates: Partial<ProcessingActivity>): void => {
dispatch({ type: 'UPDATE_PROCESSING_ACTIVITY', payload: { id, updates } })
}
const deleteProcessingActivity = (id: string): void => {
dispatch({ type: 'DELETE_PROCESSING_ACTIVITY', payload: id })
}
// TOM
const toms = computed(() => state.toms)
const tomsByCategory = computed(() => {
const categories: TOMCategory[] = [
'ZUTRITTSKONTROLLE',
'ZUGANGSKONTROLLE',
'ZUGRIFFSKONTROLLE',
'WEITERGABEKONTROLLE',
'EINGABEKONTROLLE',
'AUFTRAGSKONTROLLE',
'VERFUEGBARKEITSKONTROLLE',
'TRENNUNGSGEBOT',
]
return categories.reduce(
(acc, cat) => {
acc[cat] = state.toms.filter(t => t.category === cat)
return acc
},
{} as Record<TOMCategory, TOM[]>
)
})
const addTOM = (tom: Omit<TOM, 'id' | 'createdAt' | 'updatedAt'>): void => {
dispatch({ type: 'ADD_TOM', payload: tom })
}
const updateTOM = (id: string, updates: Partial<TOM>): void => {
dispatch({ type: 'UPDATE_TOM', payload: { id, updates } })
}
const deleteTOM = (id: string): void => {
dispatch({ type: 'DELETE_TOM', payload: id })
}
// DSFA
const dsfas = computed(() => state.dsfas)
const createDSFA = (title: string, description: string): DSFA => {
return dsgvo.createDSFA(title, description)
}
const updateDSFA = (id: string, updates: Partial<DSFA>): void => {
dispatch({ type: 'UPDATE_DSFA', payload: { id, updates } })
}
// Retention
const retentionPolicies = computed(() => state.retentionPolicies)
const addRetentionPolicy = (policy: Omit<RetentionPolicy, 'id'>): void => {
dispatch({ type: 'ADD_RETENTION_POLICY', payload: policy })
}
const updateRetentionPolicy = (id: string, updates: Partial<RetentionPolicy>): void => {
dispatch({ type: 'UPDATE_RETENTION_POLICY', payload: { id, updates } })
}
const deleteRetentionPolicy = (id: string): void => {
dispatch({ type: 'DELETE_RETENTION_POLICY', payload: id })
}
return {
dsrRequests,
pendingDSRCount,
submitDSR,
updateDSRStatus,
consents,
activeConsents,
hasConsent,
recordConsent,
revokeConsent,
processingActivities,
addProcessingActivity,
updateProcessingActivity,
deleteProcessingActivity,
toms,
tomsByCategory,
addTOM,
updateTOM,
deleteTOM,
dsfas,
createDSFA,
updateDSFA,
retentionPolicies,
addRetentionPolicy,
updateRetentionPolicy,
deleteRetentionPolicy,
isLoading,
}
}

View File

@@ -0,0 +1,136 @@
/**
* RAG (Retrieval-Augmented Generation) composable
*/
import { computed, type ComputedRef, type Ref, ref, reactive } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
SearchResponse,
AssistantResponse,
ChatMessage,
LegalDocument,
} from '@breakpilot/compliance-sdk-types'
export interface UseRAGReturn {
// Search
search: (query: string, regulationCodes?: string[]) => Promise<SearchResponse>
searchResults: Ref<SearchResponse | null>
// Chat
ask: (question: string, context?: string) => Promise<AssistantResponse>
chatHistory: Ref<ChatMessage[]>
clearChat: () => void
isTyping: Ref<boolean>
// Documents
documents: ComputedRef<LegalDocument[]>
availableRegulations: ComputedRef<string[]>
// Loading state
isLoading: Ref<boolean>
error: Ref<Error | null>
}
export function useRAG(): UseRAGReturn {
const store = useComplianceStore()
const { state, rag } = store
const isLoading = ref(false)
const isTyping = ref(false)
const error = ref<Error | null>(null)
const searchResults = ref<SearchResponse | null>(null)
const chatHistory = ref<ChatMessage[]>([])
// Search
const search = async (query: string, regulationCodes?: string[]): Promise<SearchResponse> => {
isLoading.value = true
error.value = null
try {
const results = await rag.search(query, regulationCodes)
searchResults.value = results
return results
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
// Chat
const ask = async (question: string, context?: string): Promise<AssistantResponse> => {
isLoading.value = true
isTyping.value = true
error.value = null
// Add user message to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'user',
content: question,
timestamp: new Date().toISOString(),
})
try {
const response = await rag.ask(question, context)
// Add assistant response to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'assistant',
content: response.answer,
timestamp: new Date().toISOString(),
citations: response.citations,
})
return response
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
// Add error message to history
chatHistory.value.push({
id: `msg_${Date.now()}`,
role: 'assistant',
content: 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.',
timestamp: new Date().toISOString(),
})
throw err
} finally {
isLoading.value = false
isTyping.value = false
}
}
const clearChat = (): void => {
chatHistory.value = []
rag.clearHistory()
}
// Documents
const documents = computed(() => state.legalDocuments)
const availableRegulations = computed(() => {
const codes = new Set<string>()
state.legalDocuments.forEach(doc => {
if (doc.regulationCode) {
codes.add(doc.regulationCode)
}
})
return Array.from(codes).sort()
})
return {
search,
searchResults,
ask,
chatHistory,
clearChat,
isTyping,
documents,
availableRegulations,
isLoading,
error,
}
}

View File

@@ -0,0 +1,185 @@
/**
* Security composable for SBOM and security scanning
*/
import { computed, type ComputedRef, type Ref, ref } from 'vue'
import { useComplianceStore } from '../plugin'
import type {
SBOM,
SBOMComponent,
SecurityIssue,
Vulnerability,
SecurityScanResult,
} from '@breakpilot/compliance-sdk-types'
export interface UseSecurityReturn {
// SBOM
sbom: ComputedRef<SBOM | null>
components: ComputedRef<SBOMComponent[]>
componentsByCategory: ComputedRef<Record<string, SBOMComponent[]>>
vulnerableComponents: ComputedRef<SBOMComponent[]>
generateSBOM: (source?: string) => Promise<SBOM>
exportSBOM: (format: 'cyclonedx' | 'spdx') => Promise<Blob>
// Security Issues
securityIssues: ComputedRef<SecurityIssue[]>
criticalIssues: ComputedRef<SecurityIssue[]>
openIssues: ComputedRef<SecurityIssue[]>
issuesBySeverity: ComputedRef<Record<string, SecurityIssue[]>>
// Scanning
scan: (options?: ScanOptions) => Promise<SecurityScanResult>
lastScanResult: Ref<SecurityScanResult | null>
isScanning: Ref<boolean>
// Vulnerabilities
vulnerabilities: ComputedRef<Vulnerability[]>
getVulnerabilitiesForComponent: (componentId: string) => Vulnerability[]
// Loading state
isLoading: Ref<boolean>
error: Ref<Error | null>
}
export interface ScanOptions {
tools?: string[]
targetPath?: string
excludePaths?: string[]
}
export function useSecurity(): UseSecurityReturn {
const store = useComplianceStore()
const { state, security } = store
const isLoading = ref(false)
const isScanning = ref(false)
const error = ref<Error | null>(null)
const lastScanResult = ref<SecurityScanResult | null>(null)
// SBOM
const sbom = computed(() => state.sbom)
const components = computed(() => state.sbom?.components || [])
const componentsByCategory = computed(() => {
const comps = state.sbom?.components || []
return comps.reduce(
(acc, comp) => {
const cat = comp.category || 'other'
if (!acc[cat]) acc[cat] = []
acc[cat].push(comp)
return acc
},
{} as Record<string, SBOMComponent[]>
)
})
const vulnerableComponents = computed(() => {
return components.value.filter(c => c.vulnerabilities && c.vulnerabilities.length > 0)
})
const generateSBOM = async (source?: string): Promise<SBOM> => {
isLoading.value = true
error.value = null
try {
return await security.generateSBOM(source)
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
const exportSBOM = async (format: 'cyclonedx' | 'spdx'): Promise<Blob> => {
isLoading.value = true
error.value = null
try {
return await security.exportSBOM(format)
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isLoading.value = false
}
}
// Security Issues
const securityIssues = computed(() => state.securityIssues)
const criticalIssues = computed(() =>
state.securityIssues.filter(i => i.severity === 'CRITICAL' || i.severity === 'HIGH')
)
const openIssues = computed(() =>
state.securityIssues.filter(i => i.status === 'OPEN' || i.status === 'IN_PROGRESS')
)
const issuesBySeverity = computed(() => {
return state.securityIssues.reduce(
(acc, issue) => {
if (!acc[issue.severity]) acc[issue.severity] = []
acc[issue.severity].push(issue)
return acc
},
{} as Record<string, SecurityIssue[]>
)
})
// Scanning
const scan = async (options?: ScanOptions): Promise<SecurityScanResult> => {
isScanning.value = true
isLoading.value = true
error.value = null
try {
const result = await security.scan(options)
lastScanResult.value = result
return result
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err))
throw err
} finally {
isScanning.value = false
isLoading.value = false
}
}
// Vulnerabilities
const vulnerabilities = computed(() => {
const vulns: Vulnerability[] = []
components.value.forEach(comp => {
if (comp.vulnerabilities) {
vulns.push(...comp.vulnerabilities)
}
})
return vulns
})
const getVulnerabilitiesForComponent = (componentId: string): Vulnerability[] => {
const comp = components.value.find(c => c.name === componentId)
return comp?.vulnerabilities || []
}
return {
sbom,
components,
componentsByCategory,
vulnerableComponents,
generateSBOM,
exportSBOM,
securityIssues,
criticalIssues,
openIssues,
issuesBySeverity,
scan,
lastScanResult,
isScanning,
vulnerabilities,
getVulnerabilitiesForComponent,
isLoading,
error,
}
}

View File

@@ -0,0 +1,64 @@
/**
* @breakpilot/compliance-sdk-vue
*
* Vue 3 integration for BreakPilot Compliance SDK
*
* Usage:
* import { createApp } from 'vue'
* import { CompliancePlugin, useCompliance } from '@breakpilot/compliance-sdk-vue'
*
* const app = createApp(App)
* app.use(CompliancePlugin, {
* apiEndpoint: 'https://compliance.example.com/api/v1',
* apiKey: 'pk_live_xxx',
* tenantId: 'tenant_xxx'
* })
*/
// Plugin
export {
CompliancePlugin,
useComplianceStore,
COMPLIANCE_KEY,
type CompliancePluginOptions,
type ComplianceStore,
} from './plugin'
// Composables
export {
useCompliance,
useDSGVO,
useRAG,
useControls,
useSecurity,
type UseComplianceReturn,
type UseDSGVOReturn,
type UseRAGReturn,
type UseControlsReturn,
type UseSecurityReturn,
type ScanOptions,
} from './composables'
// Re-export types
export type {
SDKState,
SDKAction,
SDKStep,
Risk,
Control,
Evidence,
DSRRequest,
ConsentRecord,
ProcessingActivity,
TOM,
DSFA,
RetentionPolicy,
Obligation,
SBOM,
SBOMComponent,
SecurityIssue,
Vulnerability,
SearchResponse,
AssistantResponse,
ChatMessage,
} from '@breakpilot/compliance-sdk-types'

View File

@@ -0,0 +1,164 @@
/**
* BreakPilot Compliance SDK - Vue 3 Plugin
*
* Usage:
* import { createApp } from 'vue'
* import { CompliancePlugin } from '@breakpilot/compliance-sdk-vue'
*
* const app = createApp(App)
* app.use(CompliancePlugin, {
* apiEndpoint: 'https://compliance.example.com/api/v1',
* apiKey: 'pk_live_xxx',
* tenantId: 'tenant_xxx'
* })
*/
import { type App, reactive, inject, type InjectionKey, readonly } from 'vue'
import {
ComplianceClient,
sdkReducer,
DSGVOModule,
ComplianceModule,
RAGModule,
SecurityModule,
StateSyncManager,
} from '@breakpilot/compliance-sdk-core'
import type { SDKState, SDKAction } from '@breakpilot/compliance-sdk-types'
import { createInitialState } from '@breakpilot/compliance-sdk-types'
// ============================================================================
// Types
// ============================================================================
export interface CompliancePluginOptions {
apiEndpoint: string
apiKey: string
tenantId?: string
autoSync?: boolean
syncInterval?: number
debug?: boolean
}
export interface ComplianceStore {
state: SDKState
dispatch: (action: SDKAction) => void
client: ComplianceClient
dsgvo: DSGVOModule
compliance: ComplianceModule
rag: RAGModule
security: SecurityModule
syncManager: StateSyncManager
isReady: boolean
error: Error | null
}
// ============================================================================
// Injection Key
// ============================================================================
export const COMPLIANCE_KEY: InjectionKey<ComplianceStore> = Symbol('compliance')
// ============================================================================
// Plugin
// ============================================================================
export const CompliancePlugin = {
install(app: App, options: CompliancePluginOptions): void {
// Create client
const client = new ComplianceClient({
apiEndpoint: options.apiEndpoint,
apiKey: options.apiKey,
tenantId: options.tenantId,
})
// Create reactive state
const state = reactive<SDKState>(createInitialState())
// Create dispatch function
const dispatch = (action: SDKAction): void => {
const newState = sdkReducer(state, action)
Object.assign(state, newState)
}
// Create modules
const dsgvo = new DSGVOModule(state, dispatch, client)
const compliance = new ComplianceModule(state, dispatch, client)
const rag = new RAGModule(state, dispatch, client)
const security = new SecurityModule(state, dispatch, client)
// Create sync manager
const syncManager = new StateSyncManager({
client,
syncInterval: options.syncInterval || 30000,
onStateLoaded: loadedState => {
Object.assign(state, loadedState)
},
onSyncError: error => {
console.error('[ComplianceSDK] Sync error:', error)
store.error = error
},
})
// Create store
const store = reactive<ComplianceStore>({
state: state as SDKState,
dispatch,
client,
dsgvo,
compliance,
rag,
security,
syncManager,
isReady: false,
error: null,
})
// Initialize
const initialize = async (): Promise<void> => {
try {
// Load state from server
const savedState = await client.getState()
if (savedState) {
Object.assign(state, savedState.state)
}
// Start sync if enabled
if (options.autoSync !== false) {
syncManager.start()
}
store.isReady = true
if (options.debug) {
console.log('[ComplianceSDK] Initialized:', state)
}
} catch (error) {
store.error = error instanceof Error ? error : new Error(String(error))
console.error('[ComplianceSDK] Initialization failed:', error)
}
}
// Start initialization
initialize()
// Provide store
app.provide(COMPLIANCE_KEY, store)
// Also make available as global property
app.config.globalProperties.$compliance = store
},
}
// ============================================================================
// Injection Helper
// ============================================================================
export function useComplianceStore(): ComplianceStore {
const store = inject(COMPLIANCE_KEY)
if (!store) {
throw new Error(
'[ComplianceSDK] No store found. Did you forget to install the CompliancePlugin?'
)
}
return store
}

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
external: ['vue'],
})