diff --git a/admin-compliance/app/sdk/document-generator/contextBridge.ts b/admin-compliance/app/sdk/document-generator/contextBridge.ts new file mode 100644 index 0000000..35f0815 --- /dev/null +++ b/admin-compliance/app/sdk/document-generator/contextBridge.ts @@ -0,0 +1,393 @@ +/** + * Template-Spec v1 — Context Bridge + * + * Maps a structured TemplateContext (canonical entities: PROVIDER, CUSTOMER, etc.) + * to the flat {{PLACEHOLDER}} map used by legacy templates. + * + * Flow: TemplateContext → contextToPlaceholders() → Record + * → string.replace({{KEY}}, value) in renderer + */ + +// ============================================================================= +// Types +// ============================================================================= + +export interface ProviderCtx { + LEGAL_NAME: string + LEGAL_FORM?: string + ADDRESS_LINE: string + POSTAL_CODE: string + CITY: string + COUNTRY: string + EMAIL: string + PHONE?: string + WEBSITE_URL?: string + CEO_NAME?: string + REGISTER_COURT?: string + REGISTER_NUMBER?: string + VAT_ID?: string +} + +export interface CustomerCtx { + LEGAL_NAME: string + ADDRESS_LINE?: string + POSTAL_CODE?: string + CITY?: string + COUNTRY?: string + CONTACT_NAME?: string + EMAIL: string + IS_CONSUMER: boolean + IS_BUSINESS: boolean +} + +export interface ServiceCtx { + NAME: string + DESCRIPTION: string + MODEL: 'SaaS' | 'PaaS' | 'IaaS' | 'OnPrem' | 'Hybrid' | '' + TIER: string + DATA_LOCATION: string + EXPORT_FORMATS?: string[] + EXPORT_WINDOW_DAYS?: number + MIN_TERM_MONTHS?: number + TERMINATION_NOTICE_DAYS?: number +} + +export interface LegalCtx { + GOVERNING_LAW: string + JURISDICTION_CITY: string + VERSION_DATE: string // YYYY-MM-DD + EFFECTIVE_DATE: string // YYYY-MM-DD + LANG: 'de' | 'en' +} + +export interface PrivacyCtx { + CONTACT_EMAIL: string + DPO_NAME: string + DPO_EMAIL: string + SUPERVISORY_AUTHORITY_NAME?: string + SUPERVISORY_AUTHORITY_ADDRESS?: string + PRIVACY_POLICY_URL: string + COOKIE_POLICY_URL?: string + ANALYTICS_RETENTION_MONTHS?: number + DATA_TRANSFER_THIRD_COUNTRIES?: string +} + +export interface SLACtx { + AVAILABILITY_PERCENT: number | '' + MAINTENANCE_NOTICE_HOURS: number | '' + SUPPORT_EMAIL: string + SUPPORT_PHONE?: string + SUPPORT_HOURS: string + RESPONSE_CRITICAL_H: number | '' + RESOLUTION_CRITICAL_H: number | '' + RESPONSE_HIGH_H: number | '' + RESOLUTION_HIGH_H: number | '' + RESPONSE_MEDIUM_H: number | '' + RESOLUTION_MEDIUM_H: number | '' + RESPONSE_LOW_H: number | '' +} + +export interface PaymentsCtx { + MONTHLY_FEE_EUR: number | '' + PAYMENT_DUE_DAY: number | '' + PAYMENT_METHOD: string + PAYMENT_DAYS: number | '' +} + +export interface SecurityCtx { + INCIDENT_NOTICE_HOURS: number | '' + LOG_RETENTION_DAYS: number | '' + SECURITY_LOG_RETENTION_DAYS: number | '' +} + +export interface NDACtx { + PURPOSE: string + DURATION_YEARS: number | '' + PENALTY_AMOUNT_EUR: number | null | '' +} + +export interface ConsentCtx { + WEBSITE_NAME: string + ANALYTICS_TOOLS: string | null + MARKETING_PARTNERS: string | null +} + +export interface TemplateContext { + PROVIDER: ProviderCtx + CUSTOMER: CustomerCtx + SERVICE: ServiceCtx + LEGAL: LegalCtx + PRIVACY: PrivacyCtx + SLA: SLACtx + PAYMENTS: PaymentsCtx + SECURITY: SecurityCtx + NDA: NDACtx + CONSENT: ConsentCtx +} + +export interface ComputedFlags { + IS_B2C: boolean + IS_B2B: boolean + SERVICE_IS_SAAS: boolean + SERVICE_IS_HYBRID: boolean + HAS_PENALTY: boolean + HAS_ANALYTICS: boolean + HAS_MARKETING: boolean +} + +// ============================================================================= +// Empty default context +// ============================================================================= + +export const EMPTY_CONTEXT: TemplateContext = { + PROVIDER: { + LEGAL_NAME: '', LEGAL_FORM: '', ADDRESS_LINE: '', POSTAL_CODE: '', CITY: '', + COUNTRY: 'DE', EMAIL: '', PHONE: '', WEBSITE_URL: '', + CEO_NAME: '', REGISTER_COURT: '', REGISTER_NUMBER: '', VAT_ID: '', + }, + CUSTOMER: { + LEGAL_NAME: '', ADDRESS_LINE: '', POSTAL_CODE: '', CITY: '', COUNTRY: 'DE', + CONTACT_NAME: '', EMAIL: '', IS_CONSUMER: false, IS_BUSINESS: true, + }, + SERVICE: { + NAME: '', DESCRIPTION: '', MODEL: 'SaaS', TIER: '', DATA_LOCATION: 'Deutschland', + EXPORT_FORMATS: [], EXPORT_WINDOW_DAYS: 30, MIN_TERM_MONTHS: 12, + TERMINATION_NOTICE_DAYS: 30, + }, + LEGAL: { + GOVERNING_LAW: 'Deutschland', JURISDICTION_CITY: '', VERSION_DATE: '', EFFECTIVE_DATE: '', + LANG: 'de', + }, + PRIVACY: { + CONTACT_EMAIL: '', DPO_NAME: '', DPO_EMAIL: '', + SUPERVISORY_AUTHORITY_NAME: '', SUPERVISORY_AUTHORITY_ADDRESS: '', + PRIVACY_POLICY_URL: '', COOKIE_POLICY_URL: '', + ANALYTICS_RETENTION_MONTHS: 13, DATA_TRANSFER_THIRD_COUNTRIES: 'nicht statt', + }, + SLA: { + AVAILABILITY_PERCENT: 99.5, MAINTENANCE_NOTICE_HOURS: 72, + SUPPORT_EMAIL: '', SUPPORT_PHONE: '', SUPPORT_HOURS: 'Mo–Fr 09:00–18:00 CET', + RESPONSE_CRITICAL_H: 2, RESOLUTION_CRITICAL_H: 8, + RESPONSE_HIGH_H: 4, RESOLUTION_HIGH_H: 24, + RESPONSE_MEDIUM_H: 24, RESOLUTION_MEDIUM_H: 120, + RESPONSE_LOW_H: 72, + }, + PAYMENTS: { + MONTHLY_FEE_EUR: '', PAYMENT_DUE_DAY: 1, PAYMENT_METHOD: 'Rechnung', PAYMENT_DAYS: 14, + }, + SECURITY: { + INCIDENT_NOTICE_HOURS: 24, LOG_RETENTION_DAYS: 7, SECURITY_LOG_RETENTION_DAYS: 30, + }, + NDA: { PURPOSE: '', DURATION_YEARS: 5, PENALTY_AMOUNT_EUR: null }, + CONSENT: { WEBSITE_NAME: '', ANALYTICS_TOOLS: null, MARKETING_PARTNERS: null }, +} + +// ============================================================================= +// Bridge: context → flat placeholder map +// ============================================================================= + +function str(v: unknown): string { + if (v === null || v === undefined || v === '') return '' + if (Array.isArray(v)) return v.join(', ') + return String(v) +} + +/** Compute compound address from PROVIDER fields */ +function providerAddress(p: ProviderCtx): string { + const parts = [p.ADDRESS_LINE, [p.POSTAL_CODE, p.CITY].filter(Boolean).join(' ')].filter(Boolean) + return parts.join(', ') +} + +/** + * Maps a TemplateContext to a flat Record of placeholder values. + * Keys are the {{PLACEHOLDER}} strings used in templates. + */ +export function contextToPlaceholders(ctx: TemplateContext): Record { + const p = ctx.PROVIDER + const c = ctx.CUSTOMER + const s = ctx.SERVICE + const l = ctx.LEGAL + const prv = ctx.PRIVACY + const sla = ctx.SLA + const pay = ctx.PAYMENTS + const sec = ctx.SECURITY + const nda = ctx.NDA + const con = ctx.CONSENT + + const address = providerAddress(p) + + return { + // --- PROVIDER --- + '{{COMPANY_NAME}}': str(p.LEGAL_NAME), + '{{PROVIDER_NAME}}': str(p.LEGAL_NAME), + '{{DISCLOSING_PARTY}}': str(p.LEGAL_NAME), // NDA: provider is disclosing party + '{{SERVICE_PROVIDER}}': str(p.LEGAL_NAME), // SLA + '{{COMPANY_ADDRESS}}': address, + '{{PROVIDER_ADDRESS}}': address, + '{{CONTACT_EMAIL}}': str(p.EMAIL), + '{{PROVIDER_EMAIL}}': str(p.EMAIL), + '{{ABUSE_EMAIL}}': str(p.EMAIL), // AUP abuse contact + '{{REPORT_EMAIL}}': prv.CONTACT_EMAIL ? str(prv.CONTACT_EMAIL) : str(p.EMAIL), // community moderation + + // --- CUSTOMER --- + '{{CUSTOMER_NAME}}': str(c.LEGAL_NAME), + '{{RECEIVING_PARTY}}': str(c.LEGAL_NAME), // NDA + '{{CUSTOMER}}': str(c.LEGAL_NAME), // SLA + + // --- SERVICE --- + '{{SERVICE_NAME}}': str(s.NAME), + '{{PLATFORM_NAME}}': str(s.NAME), // community / copyright + '{{SERVICE_DESCRIPTION}}': str(s.DESCRIPTION), + '{{SERVICE_MODEL}}': str(s.MODEL), + '{{SERVICE_TIER}}': str(s.TIER), + '{{DATA_LOCATION}}': str(s.DATA_LOCATION), + '{{EXPORT_FORMATS}}': Array.isArray(s.EXPORT_FORMATS) ? s.EXPORT_FORMATS.join(', ') : '', + '{{EXPORT_WINDOW_DAYS}}': str(s.EXPORT_WINDOW_DAYS), + '{{MIN_TERM_MONTHS}}': str(s.MIN_TERM_MONTHS), + '{{TERMINATION_NOTICE_DAYS}}': str(s.TERMINATION_NOTICE_DAYS), + + // --- LEGAL --- + '{{GOVERNING_LAW}}': str(l.GOVERNING_LAW), + '{{JURISDICTION_CITY}}': str(l.JURISDICTION_CITY), + '{{VERSION_DATE}}': str(l.VERSION_DATE), + '{{EFFECTIVE_DATE}}': str(l.EFFECTIVE_DATE), + '{{START_DATE}}': str(l.EFFECTIVE_DATE), // cloud contract start date + + // --- PRIVACY --- + '{{PRIVACY_CONTACT_EMAIL}}': str(prv.CONTACT_EMAIL), + '{{DPO_NAME}}': str(prv.DPO_NAME), + '{{DPO_EMAIL}}': str(prv.DPO_EMAIL), + '{{COPYRIGHT_CONTACT_NAME}}': str(prv.DPO_NAME), // copyright policy + '{{COPYRIGHT_EMAIL}}': str(prv.DPO_EMAIL), + '{{PRIVACY_POLICY_URL}}': str(prv.PRIVACY_POLICY_URL), + '{{COOKIE_POLICY_URL}}': str(prv.COOKIE_POLICY_URL), + '{{ANALYTICS_RETENTION_MONTHS}}': str(prv.ANALYTICS_RETENTION_MONTHS), + '{{DATA_TRANSFER_THIRD_COUNTRIES}}': str(prv.DATA_TRANSFER_THIRD_COUNTRIES), + + // --- SLA --- + '{{AVAILABILITY_PERCENT}}': str(sla.AVAILABILITY_PERCENT), + '{{MAINTENANCE_NOTICE_HOURS}}': str(sla.MAINTENANCE_NOTICE_HOURS), + '{{SUPPORT_EMAIL}}': str(sla.SUPPORT_EMAIL), + '{{SUPPORT_PHONE}}': str(sla.SUPPORT_PHONE), + '{{SUPPORT_HOURS}}': str(sla.SUPPORT_HOURS), + '{{RESPONSE_CRITICAL_H}}': str(sla.RESPONSE_CRITICAL_H), + '{{RESOLUTION_CRITICAL_H}}': str(sla.RESOLUTION_CRITICAL_H), + '{{RESPONSE_HIGH_H}}': str(sla.RESPONSE_HIGH_H), + '{{RESOLUTION_HIGH_H}}': str(sla.RESOLUTION_HIGH_H), + '{{RESPONSE_MEDIUM_H}}': str(sla.RESPONSE_MEDIUM_H), + '{{RESOLUTION_MEDIUM_H}}': str(sla.RESOLUTION_MEDIUM_H), + '{{RESPONSE_LOW_H}}': str(sla.RESPONSE_LOW_H), + + // --- PAYMENTS --- + '{{MONTHLY_FEE}}': str(pay.MONTHLY_FEE_EUR), + '{{PAYMENT_DUE_DAY}}': str(pay.PAYMENT_DUE_DAY), + '{{PAYMENT_METHOD}}': str(pay.PAYMENT_METHOD), + '{{PAYMENT_DAYS}}': str(pay.PAYMENT_DAYS), + + // --- SECURITY --- + '{{INCIDENT_NOTICE_HOURS}}': str(sec.INCIDENT_NOTICE_HOURS), + '{{LOG_RETENTION_DAYS}}': str(sec.LOG_RETENTION_DAYS), + '{{SECURITY_LOG_RETENTION_DAYS}}': str(sec.SECURITY_LOG_RETENTION_DAYS), + + // --- NDA --- + '{{PURPOSE}}': str(nda.PURPOSE), + '{{DURATION_YEARS}}': str(nda.DURATION_YEARS), + '{{PENALTY_AMOUNT}}': nda.PENALTY_AMOUNT_EUR !== null ? str(nda.PENALTY_AMOUNT_EUR) : '', + + // --- CONSENT --- + '{{WEBSITE_NAME}}': con.WEBSITE_NAME || str(p.WEBSITE_URL), + '{{ANALYTICS_TOOLS}}': str(con.ANALYTICS_TOOLS), + '{{MARKETING_PARTNERS}}': str(con.MARKETING_PARTNERS), + } +} + +// ============================================================================= +// Computed flags +// ============================================================================= + +export function computeFlags(ctx: TemplateContext): ComputedFlags { + return { + IS_B2C: ctx.CUSTOMER.IS_CONSUMER, + IS_B2B: ctx.CUSTOMER.IS_BUSINESS, + SERVICE_IS_SAAS: ctx.SERVICE.MODEL === 'SaaS', + SERVICE_IS_HYBRID: ctx.SERVICE.MODEL === 'Hybrid', + HAS_PENALTY: ctx.NDA.PENALTY_AMOUNT_EUR !== null && ctx.NDA.PENALTY_AMOUNT_EUR !== '', + HAS_ANALYTICS: !!ctx.CONSENT.ANALYTICS_TOOLS, + HAS_MARKETING: !!ctx.CONSENT.MARKETING_PARTNERS, + } +} + +// ============================================================================= +// Section relevance — which sections are useful for a given template +// ============================================================================= + +/** All placeholder patterns covered by each context section */ +const SECTION_COVERS: Record = { + PROVIDER: ['{{COMPANY_NAME}}', '{{PROVIDER_NAME}}', '{{DISCLOSING_PARTY}}', '{{SERVICE_PROVIDER}}', '{{COMPANY_ADDRESS}}', '{{PROVIDER_ADDRESS}}', '{{CONTACT_EMAIL}}', '{{PROVIDER_EMAIL}}', '{{ABUSE_EMAIL}}', '{{REPORT_EMAIL}}'], + CUSTOMER: ['{{CUSTOMER_NAME}}', '{{RECEIVING_PARTY}}', '{{CUSTOMER}}'], + SERVICE: ['{{SERVICE_NAME}}', '{{PLATFORM_NAME}}', '{{SERVICE_DESCRIPTION}}', '{{SERVICE_MODEL}}', '{{SERVICE_TIER}}', '{{DATA_LOCATION}}', '{{EXPORT_FORMATS}}', '{{EXPORT_WINDOW_DAYS}}', '{{MIN_TERM_MONTHS}}', '{{TERMINATION_NOTICE_DAYS}}'], + LEGAL: ['{{GOVERNING_LAW}}', '{{JURISDICTION_CITY}}', '{{VERSION_DATE}}', '{{EFFECTIVE_DATE}}', '{{START_DATE}}'], + PRIVACY: ['{{PRIVACY_CONTACT_EMAIL}}', '{{DPO_NAME}}', '{{DPO_EMAIL}}', '{{COPYRIGHT_CONTACT_NAME}}', '{{COPYRIGHT_EMAIL}}', '{{PRIVACY_POLICY_URL}}', '{{COOKIE_POLICY_URL}}', '{{ANALYTICS_RETENTION_MONTHS}}', '{{DATA_TRANSFER_THIRD_COUNTRIES}}'], + SLA: ['{{AVAILABILITY_PERCENT}}', '{{MAINTENANCE_NOTICE_HOURS}}', '{{SUPPORT_EMAIL}}', '{{SUPPORT_PHONE}}', '{{SUPPORT_HOURS}}', '{{RESPONSE_CRITICAL_H}}', '{{RESOLUTION_CRITICAL_H}}', '{{RESPONSE_HIGH_H}}', '{{RESOLUTION_HIGH_H}}', '{{RESPONSE_MEDIUM_H}}', '{{RESOLUTION_MEDIUM_H}}', '{{RESPONSE_LOW_H}}'], + PAYMENTS: ['{{MONTHLY_FEE}}', '{{PAYMENT_DUE_DAY}}', '{{PAYMENT_METHOD}}', '{{PAYMENT_DAYS}}'], + SECURITY: ['{{INCIDENT_NOTICE_HOURS}}', '{{LOG_RETENTION_DAYS}}', '{{SECURITY_LOG_RETENTION_DAYS}}'], + NDA: ['{{PURPOSE}}', '{{DURATION_YEARS}}', '{{PENALTY_AMOUNT}}'], + CONSENT: ['{{WEBSITE_NAME}}', '{{ANALYTICS_TOOLS}}', '{{MARKETING_PARTNERS}}'], +} + +/** + * Returns which context sections are relevant for a given list of template placeholders. + */ +export function getRelevantSections(placeholders: string[]): (keyof TemplateContext)[] { + const pSet = new Set(placeholders) + return (Object.keys(SECTION_COVERS) as (keyof TemplateContext)[]).filter( + (section) => SECTION_COVERS[section].some((ph) => pSet.has(ph)) + ) +} + +/** + * Returns a list of placeholder keys that are NOT covered by the context bridge. + * These need to be filled in manually. + */ +export function getUncoveredPlaceholders(placeholders: string[], ctx: TemplateContext): string[] { + const bridged = contextToPlaceholders(ctx) + return placeholders.filter((ph) => !(ph in bridged)) +} + +/** + * Strict validation: returns placeholder keys that are required but empty. + * "Required" means: placeholder appears in template AND bridge covers it AND bridge produces empty string. + */ +export function getMissingRequired(placeholders: string[], ctx: TemplateContext): string[] { + const bridged = contextToPlaceholders(ctx) + return placeholders.filter((ph) => ph in bridged && !bridged[ph]) +} + +// ============================================================================= +// Helpers to set nested context values by dot-path +// ============================================================================= + +/** + * Returns a new TemplateContext with the value at dotPath set. + * e.g. setContextPath(ctx, 'PROVIDER.LEGAL_NAME', 'ACME GmbH') + */ +export function setContextPath(ctx: TemplateContext, dotPath: string, value: unknown): TemplateContext { + const [section, ...rest] = dotPath.split('.') as [keyof TemplateContext, ...string[]] + const key = rest.join('.') + return { + ...ctx, + [section]: { + ...(ctx[section] as Record), + [key]: value, + }, + } +} + +/** + * Get a nested context value by dot-path. + */ +export function getContextPath(ctx: TemplateContext, dotPath: string): unknown { + const [section, ...rest] = dotPath.split('.') as [keyof TemplateContext, ...string[]] + const sectionObj = ctx[section] as Record + return sectionObj?.[rest.join('.')] +} diff --git a/admin-compliance/app/sdk/document-generator/ruleset.v1.json b/admin-compliance/app/sdk/document-generator/ruleset.v1.json new file mode 100644 index 0000000..5f09c25 --- /dev/null +++ b/admin-compliance/app/sdk/document-generator/ruleset.v1.json @@ -0,0 +1,151 @@ +{ + "ruleset_version": "1.0.0", + "engine": { + "language": "jsonlogic", + "phases": ["compute_flags", "auto_defaults", "hard_validations", "auto_remove_blocks", "module_requirements", "warnings"] + }, + "compute_flags": [ + { "id": "FLAG_IS_B2C", "set": "computed_flags.IS_B2C", "expr": { "==": [{ "var": "context.CUSTOMER.IS_CONSUMER" }, true] } }, + { "id": "FLAG_IS_B2B", "set": "computed_flags.IS_B2B", "expr": { "==": [{ "var": "context.CUSTOMER.IS_BUSINESS" }, true] } }, + { "id": "FLAG_SERVICE_IS_SAAS", "set": "computed_flags.SERVICE_IS_SAAS", "expr": { "==": [{ "var": "context.SERVICE.MODEL" }, "SaaS"] } }, + { "id": "FLAG_SERVICE_IS_HYBRID","set": "computed_flags.SERVICE_IS_HYBRID","expr": { "==": [{ "var": "context.SERVICE.MODEL" }, "Hybrid"] } }, + { "id": "FLAG_HAS_PENALTY", "set": "computed_flags.HAS_PENALTY", "expr": { "!=": [{ "var": "context.NDA.PENALTY_AMOUNT_EUR" }, null] } }, + { "id": "FLAG_HAS_ANALYTICS", "set": "computed_flags.HAS_ANALYTICS", "expr": { "!=": [{ "var": "context.CONSENT.ANALYTICS_TOOLS" }, null] } }, + { "id": "FLAG_HAS_MARKETING", "set": "computed_flags.HAS_MARKETING", "expr": { "!=": [{ "var": "context.CONSENT.MARKETING_PARTNERS" }, null] } } + ], + "auto_defaults": [ + { + "id": "DEFAULT_SECURITY_LOG_RETENTION", + "when": { "==": [{ "var": "context.SECURITY.SECURITY_LOG_RETENTION_DAYS" }, 0] }, + "actions": [{ "type": "set", "path": "context.SECURITY.SECURITY_LOG_RETENTION_DAYS", "value": 30 }], + "note": "Set SECURITY_LOG_RETENTION_DAYS=30 when 0 is supplied." + }, + { + "id": "DEFAULT_LOG_RETENTION", + "when": { "==": [{ "var": "context.SECURITY.LOG_RETENTION_DAYS" }, 0] }, + "actions": [{ "type": "set", "path": "context.SECURITY.LOG_RETENTION_DAYS", "value": 7 }] + } + ], + "hard_validations": [ + { + "id": "DOC_LANG_MATCH_DE", + "severity": "ERROR", + "when": { "in": [{ "var": "doc_type" }, ["nda_de", "sla_de", "community_de", "copyright_de", "cloud_contract_de", "data_usage_clause_de", "cookie_banner_de", "agb_de"]] }, + "assert_all": [{ "==": [{ "var": "render.lang" }, "de"] }], + "message": "doc_type requires render.lang=de." + }, + { + "id": "DOC_LANG_MATCH_EN", + "severity": "ERROR", + "when": { "in": [{ "var": "doc_type" }, ["nda_en", "acceptable_use_en", "liability_clause_en"]] }, + "assert_all": [{ "==": [{ "var": "render.lang" }, "en"] }], + "message": "doc_type requires render.lang=en." + }, + { + "id": "SLA_AVAILABILITY_RANGE", + "severity": "ERROR", + "when": { "==": [{ "var": "doc_type" }, "sla_de"] }, + "assert_all": [ + { ">=": [{ "var": "context.SLA.AVAILABILITY_PERCENT" }, 90] }, + { "<=": [{ "var": "context.SLA.AVAILABILITY_PERCENT" }, 99.99] } + ], + "message": "SLA.AVAILABILITY_PERCENT must be between 90 and 99.99." + }, + { + "id": "CUSTOMER_ROLE_XOR", + "severity": "ERROR", + "when": { "==": [true, true] }, + "assert_all": [{ + "!=": [{ "var": "context.CUSTOMER.IS_CONSUMER" }, { "var": "context.CUSTOMER.IS_BUSINESS" }] + }], + "message": "Customer must be either consumer or business (exclusive)." + }, + { + "id": "PROVIDER_IDENTITY", + "severity": "ERROR", + "when": { "==": [true, true] }, + "assert_all": [ + { "!=": [{ "var": "context.PROVIDER.LEGAL_NAME" }, ""] }, + { "!=": [{ "var": "context.PROVIDER.EMAIL" }, ""] } + ], + "message": "PROVIDER.LEGAL_NAME and PROVIDER.EMAIL are required." + } + ], + "auto_remove_blocks": [ + { + "id": "REMOVE_NDA_PENALTY_BLOCK", + "when": { "==": [{ "var": "computed_flags.HAS_PENALTY" }, false] }, + "actions": [{ "type": "remove_block", "block_id": "NDA_PENALTY_BLOCK" }] + }, + { + "id": "REMOVE_COOKIE_ANALYTICS_SECTION", + "when": { "==": [{ "var": "computed_flags.HAS_ANALYTICS" }, false] }, + "actions": [{ "type": "remove_block", "block_id": "COOKIE_ANALYTICS_BLOCK" }] + }, + { + "id": "REMOVE_COOKIE_MARKETING_SECTION", + "when": { "==": [{ "var": "computed_flags.HAS_MARKETING" }, false] }, + "actions": [{ "type": "remove_block", "block_id": "COOKIE_MARKETING_BLOCK" }] + } + ], + "module_requirements": [ + { + "id": "REQ_CLOUD_EXPORT_MODULE", + "severity": "ERROR", + "when": { + "and": [ + { "==": [{ "var": "doc_type" }, "cloud_contract_de"] }, + { "or": [ + { "==": [{ "var": "computed_flags.SERVICE_IS_SAAS" }, true] }, + { "==": [{ "var": "computed_flags.SERVICE_IS_HYBRID" }, true] } + ]} + ] + }, + "assert_all": [{ "in": ["CLOUD_EXPORT_DELETE_DE", { "var": "modules.enabled" }] }], + "message": "Cloud SaaS/Hybrid requires module CLOUD_EXPORT_DELETE_DE." + }, + { + "id": "REQ_B2C_WITHDRAWAL_MODULE", + "severity": "WARN", + "when": { + "and": [ + { "==": [{ "var": "doc_type" }, "agb_de"] }, + { "==": [{ "var": "computed_flags.IS_B2C" }, true] }, + { "==": [{ "var": "computed_flags.SERVICE_IS_SAAS" }, true] } + ] + }, + "assert_all": [{ "in": ["B2C_WITHDRAWAL_DE", { "var": "modules.enabled" }] }], + "message": "B2C + SaaS AGB: Consider adding module B2C_WITHDRAWAL_DE (business-model dependent)." + } + ], + "warnings": [ + { + "id": "WARN_EXPORT_FORMATS_MISSING", + "severity": "WARN", + "when": { + "and": [ + { "==": [{ "var": "doc_type" }, "cloud_contract_de"] }, + { "or": [ + { "==": [{ "var": "computed_flags.SERVICE_IS_SAAS" }, true] }, + { "==": [{ "var": "computed_flags.SERVICE_IS_HYBRID" }, true] } + ]} + ] + }, + "assert_all": [{ ">=": [{ "var": "context.SERVICE.EXPORT_WINDOW_DAYS" }, 1] }], + "message": "Consider setting SERVICE.EXPORT_FORMATS and EXPORT_WINDOW_DAYS for clearer exit handling." + }, + { + "id": "WARN_LEGAL_REVIEW", + "severity": "WARN", + "when": { "==": [true, true] }, + "assert_all": [], + "message": "These templates are self-authored and MIT-licensed. For production use, legal review is strongly recommended." + } + ], + "execution_contract": { + "strict_placeholder_policy": { + "mode": "error_on_missing", + "missing_placeholder_error_code": "MISSING_PLACEHOLDER" + } + } +} diff --git a/admin-compliance/app/sdk/document-generator/template_spec.schema.json b/admin-compliance/app/sdk/document-generator/template_spec.schema.json new file mode 100644 index 0000000..af66ba4 --- /dev/null +++ b/admin-compliance/app/sdk/document-generator/template_spec.schema.json @@ -0,0 +1,244 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://breakpilot.example.com/template-spec.schema.json", + "title": "TemplateSpec v1 (STRICT)", + "description": "Input contract for the BreakPilot Compliance Document Generator. Validates the structured context before rendering any legal template.", + "type": "object", + "additionalProperties": false, + "required": ["spec_version", "doc_type", "render", "context", "modules"], + "properties": { + "spec_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "doc_type": { + "type": "string", + "enum": [ + "nda_de", + "nda_en", + "sla_de", + "acceptable_use_en", + "community_de", + "copyright_de", + "cloud_contract_de", + "data_usage_clause_de", + "cookie_banner_de", + "agb_de", + "liability_clause_en" + ] + }, + "render": { + "type": "object", + "additionalProperties": false, + "required": ["lang", "variant", "format", "strict"], + "properties": { + "lang": { "type": "string", "enum": ["de", "en"] }, + "variant": { "type": "string", "enum": ["standard", "b2b", "b2c"] }, + "format": { "type": "string", "enum": ["plaintext", "markdown", "html"] }, + "strict": { "type": "boolean", "const": true } + } + }, + "context": { + "type": "object", + "additionalProperties": false, + "required": ["PROVIDER", "CUSTOMER", "SERVICE", "LEGAL", "PRIVACY", "SLA", "PAYMENTS", "SECURITY", "NDA", "CONSENT"], + "properties": { + "PROVIDER": { "$ref": "#/$defs/Provider" }, + "CUSTOMER": { "$ref": "#/$defs/Customer" }, + "SERVICE": { "$ref": "#/$defs/Service" }, + "LEGAL": { "$ref": "#/$defs/Legal" }, + "PRIVACY": { "$ref": "#/$defs/Privacy" }, + "SLA": { "$ref": "#/$defs/SLA" }, + "PAYMENTS": { "$ref": "#/$defs/Payments" }, + "SECURITY": { "$ref": "#/$defs/Security" }, + "NDA": { "$ref": "#/$defs/NDA" }, + "CONSENT": { "$ref": "#/$defs/Consent" } + } + }, + "modules": { + "type": "object", + "additionalProperties": false, + "required": ["enabled"], + "properties": { + "enabled": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + } + } + }, + "computed_flags": { + "type": "object", + "additionalProperties": false, + "properties": { + "IS_B2C": { "type": "boolean" }, + "IS_B2B": { "type": "boolean" }, + "SERVICE_IS_SAAS": { "type": "boolean" }, + "SERVICE_IS_HYBRID":{ "type": "boolean" }, + "HAS_PENALTY": { "type": "boolean" }, + "HAS_ANALYTICS": { "type": "boolean" }, + "HAS_MARKETING": { "type": "boolean" } + } + } + }, + "allOf": [ + { "if": { "properties": { "doc_type": { "const": "nda_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "nda_en" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "en" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "sla_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "acceptable_use_en" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "en" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "community_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "copyright_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "cloud_contract_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "data_usage_clause_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "cookie_banner_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "agb_de" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "de" } } } } } }, + { "if": { "properties": { "doc_type": { "const": "liability_clause_en" } }, "required": ["doc_type"] }, "then": { "properties": { "render": { "properties": { "lang": { "const": "en" } } } } } } + ], + "$defs": { + "NonEmptyString": { "type": "string", "minLength": 1 }, + "Email": { "type": "string", "format": "email" }, + "Date": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, + "Provider": { + "type": "object", + "additionalProperties": false, + "required": ["LEGAL_NAME", "ADDRESS_LINE", "POSTAL_CODE", "CITY", "COUNTRY", "EMAIL", "WEBSITE_URL"], + "properties": { + "LEGAL_NAME": { "$ref": "#/$defs/NonEmptyString" }, + "LEGAL_FORM": { "type": "string" }, + "ADDRESS_LINE": { "$ref": "#/$defs/NonEmptyString" }, + "POSTAL_CODE": { "$ref": "#/$defs/NonEmptyString" }, + "CITY": { "$ref": "#/$defs/NonEmptyString" }, + "COUNTRY": { "$ref": "#/$defs/NonEmptyString" }, + "EMAIL": { "$ref": "#/$defs/Email" }, + "PHONE": { "type": "string" }, + "WEBSITE_URL": { "$ref": "#/$defs/NonEmptyString" }, + "CEO_NAME": { "type": "string" }, + "REGISTER_COURT": { "type": "string" }, + "REGISTER_NUMBER": { "type": "string" }, + "VAT_ID": { "type": "string" } + } + }, + "Customer": { + "type": "object", + "additionalProperties": false, + "required": ["LEGAL_NAME", "EMAIL", "IS_CONSUMER", "IS_BUSINESS"], + "properties": { + "LEGAL_NAME": { "$ref": "#/$defs/NonEmptyString" }, + "ADDRESS_LINE": { "type": "string" }, + "POSTAL_CODE": { "type": "string" }, + "CITY": { "type": "string" }, + "COUNTRY": { "type": "string" }, + "CONTACT_NAME": { "type": "string" }, + "EMAIL": { "$ref": "#/$defs/Email" }, + "IS_CONSUMER": { "type": "boolean" }, + "IS_BUSINESS": { "type": "boolean" } + } + }, + "Service": { + "type": "object", + "additionalProperties": false, + "required": ["NAME", "DESCRIPTION", "MODEL", "TIER", "DATA_LOCATION"], + "properties": { + "NAME": { "$ref": "#/$defs/NonEmptyString" }, + "DESCRIPTION": { "$ref": "#/$defs/NonEmptyString" }, + "MODEL": { "type": "string", "enum": ["SaaS", "PaaS", "IaaS", "OnPrem", "Hybrid"] }, + "TIER": { "$ref": "#/$defs/NonEmptyString" }, + "DATA_LOCATION": { "$ref": "#/$defs/NonEmptyString" }, + "EXPORT_FORMATS": { "type": "array", "items": { "$ref": "#/$defs/NonEmptyString" }, "uniqueItems": true }, + "EXPORT_WINDOW_DAYS":{ "type": "integer", "minimum": 0 }, + "MIN_TERM_MONTHS": { "type": "integer", "minimum": 1 }, + "TERMINATION_NOTICE_DAYS": { "type": "integer", "minimum": 0 } + } + }, + "Legal": { + "type": "object", + "additionalProperties": false, + "required": ["GOVERNING_LAW", "JURISDICTION_CITY", "VERSION_DATE", "EFFECTIVE_DATE", "LANG"], + "properties": { + "GOVERNING_LAW": { "$ref": "#/$defs/NonEmptyString" }, + "JURISDICTION_CITY": { "$ref": "#/$defs/NonEmptyString" }, + "VERSION_DATE": { "$ref": "#/$defs/Date" }, + "EFFECTIVE_DATE": { "$ref": "#/$defs/Date" }, + "LANG": { "type": "string", "enum": ["de", "en"] } + } + }, + "Privacy": { + "type": "object", + "additionalProperties": false, + "required": ["CONTACT_EMAIL", "DPO_NAME", "DPO_EMAIL", "PRIVACY_POLICY_URL"], + "properties": { + "CONTACT_EMAIL": { "$ref": "#/$defs/Email" }, + "DPO_NAME": { "$ref": "#/$defs/NonEmptyString" }, + "DPO_EMAIL": { "$ref": "#/$defs/Email" }, + "SUPERVISORY_AUTHORITY_NAME": { "type": "string" }, + "SUPERVISORY_AUTHORITY_ADDRESS": { "type": "string" }, + "PRIVACY_POLICY_URL": { "$ref": "#/$defs/NonEmptyString" }, + "COOKIE_POLICY_URL": { "type": "string" }, + "ANALYTICS_RETENTION_MONTHS": { "type": "integer", "minimum": 1 }, + "DATA_TRANSFER_THIRD_COUNTRIES": { "type": "string" } + } + }, + "SLA": { + "type": "object", + "additionalProperties": false, + "required": ["AVAILABILITY_PERCENT", "MAINTENANCE_NOTICE_HOURS", "SUPPORT_EMAIL", "SUPPORT_HOURS", + "RESPONSE_CRITICAL_H", "RESOLUTION_CRITICAL_H", "RESPONSE_HIGH_H", "RESOLUTION_HIGH_H", + "RESPONSE_MEDIUM_H", "RESOLUTION_MEDIUM_H", "RESPONSE_LOW_H"], + "properties": { + "AVAILABILITY_PERCENT": { "type": "number", "minimum": 90, "maximum": 99.99 }, + "MAINTENANCE_NOTICE_HOURS":{ "type": "integer", "minimum": 0 }, + "SUPPORT_EMAIL": { "$ref": "#/$defs/Email" }, + "SUPPORT_PHONE": { "type": "string" }, + "SUPPORT_HOURS": { "$ref": "#/$defs/NonEmptyString" }, + "RESPONSE_CRITICAL_H": { "type": "integer", "minimum": 0 }, + "RESOLUTION_CRITICAL_H": { "type": "integer", "minimum": 0 }, + "RESPONSE_HIGH_H": { "type": "integer", "minimum": 0 }, + "RESOLUTION_HIGH_H": { "type": "integer", "minimum": 0 }, + "RESPONSE_MEDIUM_H": { "type": "integer", "minimum": 0 }, + "RESOLUTION_MEDIUM_H": { "type": "integer", "minimum": 0 }, + "RESPONSE_LOW_H": { "type": "integer", "minimum": 0 } + } + }, + "Payments": { + "type": "object", + "additionalProperties": false, + "required": ["MONTHLY_FEE_EUR", "PAYMENT_DUE_DAY", "PAYMENT_METHOD", "PAYMENT_DAYS"], + "properties": { + "MONTHLY_FEE_EUR": { "type": "number", "minimum": 0 }, + "PAYMENT_DUE_DAY": { "type": "integer", "minimum": 1, "maximum": 31 }, + "PAYMENT_METHOD": { "$ref": "#/$defs/NonEmptyString" }, + "PAYMENT_DAYS": { "type": "integer", "minimum": 0 } + } + }, + "Security": { + "type": "object", + "additionalProperties": false, + "required": ["INCIDENT_NOTICE_HOURS", "LOG_RETENTION_DAYS", "SECURITY_LOG_RETENTION_DAYS"], + "properties": { + "INCIDENT_NOTICE_HOURS": { "type": "integer", "minimum": 0 }, + "LOG_RETENTION_DAYS": { "type": "integer", "minimum": 0 }, + "SECURITY_LOG_RETENTION_DAYS": { "type": "integer", "minimum": 0 } + } + }, + "NDA": { + "type": "object", + "additionalProperties": false, + "required": ["PURPOSE", "DURATION_YEARS", "PENALTY_AMOUNT_EUR"], + "properties": { + "PURPOSE": { "$ref": "#/$defs/NonEmptyString" }, + "DURATION_YEARS": { "type": "integer", "minimum": 1, "maximum": 30 }, + "PENALTY_AMOUNT_EUR": { "type": ["number", "null"], "minimum": 0 } + } + }, + "Consent": { + "type": "object", + "additionalProperties": false, + "required": ["ANALYTICS_TOOLS", "MARKETING_PARTNERS", "WEBSITE_NAME"], + "properties": { + "WEBSITE_NAME": { "$ref": "#/$defs/NonEmptyString" }, + "ANALYTICS_TOOLS": { "type": ["string", "null"] }, + "MARKETING_PARTNERS": { "type": ["string", "null"] } + } + } + } +} diff --git a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx index c023e4e..895ffd5 100644 --- a/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx +++ b/admin-compliance/components/sdk/Sidebar/SDKSidebar.tsx @@ -262,18 +262,33 @@ interface AdditionalModuleItemProps { } function AdditionalModuleItem({ href, icon, label, isActive, collapsed }: AdditionalModuleItemProps) { + const isExternal = href.startsWith('http') + const className = `flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${ + collapsed ? 'justify-center' : '' + } ${ + isActive + ? 'bg-purple-100 text-purple-900 font-medium' + : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900' + }` + + if (isExternal) { + return ( + + {icon} + {!collapsed && ( + + {label} + + + + + )} + + ) + } + return ( - + {icon} {!collapsed && {label}} @@ -563,6 +578,30 @@ export function SDKSidebar({ collapsed = false, onCollapsedChange }: SDKSidebarP isActive={pathname === '/sdk/sdk-flow'} collapsed={collapsed} /> + + + + } + label="Developer Portal" + isActive={false} + collapsed={collapsed} + /> + + + + } + label="SDK Dokumentation" + isActive={false} + collapsed={collapsed} + /> {state.companyProfile?.machineBuilder?.ceMarkingRequired && (