-- ============================================================================ -- Migration 011: Vendor Compliance Schema -- Vendor Management, Contract/AVV Management, Findings, Templates -- -- Implements DSGVO Art. 28 (Auftragsverarbeitung) requirements: -- - Vendor registry with risk scoring and classification -- - Contract/AVV document management with AI-assisted review -- - Compliance findings from contract analysis -- - Control instances for vendor-level control assessments -- - Pre-filled templates for vendors, processing activities, and TOMs -- ============================================================================ -- ============================================================================ -- Vendors (Service Provider Registry) -- ============================================================================ CREATE TABLE IF NOT EXISTS vendor_vendors ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, -- Basic info name VARCHAR(255) NOT NULL, legal_form VARCHAR(100), country VARCHAR(10) NOT NULL DEFAULT 'DE', -- ISO 3166-1 alpha-2 address JSONB, -- {street, city, postalCode, country, state} website VARCHAR(500), -- Contact contact_name VARCHAR(255), contact_email VARCHAR(255), contact_phone VARCHAR(100), contact_department VARCHAR(255), -- Role & Classification role VARCHAR(50) NOT NULL DEFAULT 'PROCESSOR', -- PROCESSOR, CONTROLLER, JOINT_CONTROLLER, SUB_PROCESSOR, THIRD_PARTY service_category VARCHAR(50), -- HOSTING, CRM, ERP, ANALYTICS, etc. (19 categories) service_description TEXT, data_access_level VARCHAR(50) DEFAULT 'NONE', -- NONE, POTENTIAL, ADMINISTRATIVE, CONTENT -- Processing & Compliance processing_locations JSONB DEFAULT '[]', -- [{country, region, isPrimary, isEU, isAdequate}] certifications JSONB DEFAULT '[]', -- ["ISO 27001", "SOC 2", etc.] -- Risk Scoring (0-100) inherent_risk_score INT DEFAULT 0, residual_risk_score INT DEFAULT 0, manual_risk_adjustment INT, -- Contract & Review review_frequency VARCHAR(50) DEFAULT 'ANNUAL', -- QUARTERLY, SEMI_ANNUAL, ANNUAL, BIENNIAL last_review_date TIMESTAMPTZ, next_review_date TIMESTAMPTZ, -- Links processing_activity_ids JSONB DEFAULT '[]', -- UUIDs of linked processing activities -- Status status VARCHAR(50) DEFAULT 'ACTIVE', -- ACTIVE, INACTIVE, PENDING_REVIEW, TERMINATED -- Template reference (if created from template) template_id VARCHAR(100), -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID NOT NULL ); -- ============================================================================ -- Contracts (including AVV/DPA) -- ============================================================================ CREATE TABLE IF NOT EXISTS vendor_contracts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE, -- Document info file_name VARCHAR(500), original_name VARCHAR(500), mime_type VARCHAR(100), file_size BIGINT, storage_path VARCHAR(1000), -- MinIO path -- Classification document_type VARCHAR(50) NOT NULL, -- AVV, MSA, SLA, SCC, NDA, TOM_ANNEX, CERTIFICATION, SUB_PROCESSOR_LIST -- Metadata (extracted or manual) parties JSONB, -- [{name, role, address}] effective_date DATE, expiration_date DATE, auto_renewal BOOLEAN DEFAULT FALSE, renewal_notice_period VARCHAR(100), -- Review Status review_status VARCHAR(50) DEFAULT 'PENDING', -- PENDING, IN_PROGRESS, COMPLETED, FAILED review_completed_at TIMESTAMPTZ, compliance_score INT, -- 0-100 -- Versioning version VARCHAR(50) DEFAULT '1.0', previous_version_id UUID REFERENCES vendor_contracts(id), -- Content (extracted text for analysis) extracted_text TEXT, page_count INT, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID NOT NULL ); -- ============================================================================ -- Findings (from contract reviews) -- ============================================================================ CREATE TABLE IF NOT EXISTS vendor_findings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, contract_id UUID REFERENCES vendor_contracts(id) ON DELETE CASCADE, vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE, -- Classification finding_type VARCHAR(20) NOT NULL, -- OK, GAP, RISK, UNKNOWN category VARCHAR(50) NOT NULL, -- AVV_CONTENT, SUBPROCESSOR, INCIDENT, AUDIT_RIGHTS, DELETION, TOM, TRANSFER, LIABILITY, SLA, DATA_SUBJECT_RIGHTS, CONFIDENTIALITY, INSTRUCTION, TERMINATION, GENERAL severity VARCHAR(20) NOT NULL, -- LOW, MEDIUM, HIGH, CRITICAL -- Content title VARCHAR(500) NOT NULL, description TEXT, recommendation TEXT, -- Citations (from contract text) citations JSONB DEFAULT '[]', -- [{documentId, page, startChar, endChar, quotedText, quoteHash}] -- Workflow status VARCHAR(50) DEFAULT 'OPEN', -- OPEN, IN_PROGRESS, RESOLVED, ACCEPTED, FALSE_POSITIVE assignee VARCHAR(255), due_date DATE, resolution TEXT, resolved_at TIMESTAMPTZ, resolved_by UUID, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ============================================================================ -- Control Instances (applied controls per vendor) -- ============================================================================ CREATE TABLE IF NOT EXISTS vendor_control_instances ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE, vendor_id UUID NOT NULL REFERENCES vendor_vendors(id) ON DELETE CASCADE, -- Control reference control_id VARCHAR(100) NOT NULL, -- e.g., VND-TRF-01 control_domain VARCHAR(50), -- TRANSFER, AUDIT, DELETION, INCIDENT, SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE -- Assessment status VARCHAR(50) DEFAULT 'PLANNED', -- PASS, PARTIAL, FAIL, NOT_APPLICABLE, PLANNED evidence_ids JSONB DEFAULT '[]', notes TEXT, -- Timing last_assessed_at TIMESTAMPTZ, last_assessed_by UUID, next_assessment_date TIMESTAMPTZ, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(tenant_id, vendor_id, control_id) ); -- ============================================================================ -- Templates (pre-filled templates for various entity types) -- ============================================================================ CREATE TABLE IF NOT EXISTS compliance_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID REFERENCES compliance_tenants(id) ON DELETE CASCADE, -- NULL for system templates -- Template info template_type VARCHAR(50) NOT NULL, -- VENDOR, PROCESSING_ACTIVITY, TOM, CONTROL_SET template_id VARCHAR(100) NOT NULL UNIQUE, -- e.g., tpl-vendor-cloud-iaas category VARCHAR(100), -- HR, SALES, MARKETING, CLOUD_INFRASTRUCTURE, etc. -- Content name_de VARCHAR(500) NOT NULL, name_en VARCHAR(500) NOT NULL, description_de TEXT, description_en TEXT, -- Template data (full template content as JSONB) template_data JSONB NOT NULL, -- Organization industry VARCHAR(100), -- IT, HEALTHCARE, FINANCE, MANUFACTURING, RETAIL, etc. tags JSONB DEFAULT '[]', -- Metadata is_system BOOLEAN DEFAULT FALSE, -- true = pre-installed, false = user-created is_active BOOLEAN DEFAULT TRUE, usage_count INT DEFAULT 0, -- Audit created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ============================================================================ -- Indexes: Vendors -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_vendor_vendors_tenant ON vendor_vendors(tenant_id); CREATE INDEX IF NOT EXISTS idx_vendor_vendors_status ON vendor_vendors(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_vendor_vendors_role ON vendor_vendors(tenant_id, role); CREATE INDEX IF NOT EXISTS idx_vendor_vendors_service_category ON vendor_vendors(tenant_id, service_category); CREATE INDEX IF NOT EXISTS idx_vendor_vendors_next_review ON vendor_vendors(next_review_date) WHERE next_review_date IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_vendor_vendors_template_id ON vendor_vendors(template_id) WHERE template_id IS NOT NULL; -- ============================================================================ -- Indexes: Contracts -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_vendor_contracts_tenant ON vendor_contracts(tenant_id); CREATE INDEX IF NOT EXISTS idx_vendor_contracts_vendor ON vendor_contracts(vendor_id); CREATE INDEX IF NOT EXISTS idx_vendor_contracts_document_type ON vendor_contracts(tenant_id, document_type); CREATE INDEX IF NOT EXISTS idx_vendor_contracts_review_status ON vendor_contracts(tenant_id, review_status); CREATE INDEX IF NOT EXISTS idx_vendor_contracts_expiration ON vendor_contracts(expiration_date) WHERE expiration_date IS NOT NULL; -- ============================================================================ -- Indexes: Findings -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_vendor_findings_tenant ON vendor_findings(tenant_id); CREATE INDEX IF NOT EXISTS idx_vendor_findings_vendor ON vendor_findings(vendor_id); CREATE INDEX IF NOT EXISTS idx_vendor_findings_contract ON vendor_findings(contract_id) WHERE contract_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_vendor_findings_severity ON vendor_findings(tenant_id, severity); CREATE INDEX IF NOT EXISTS idx_vendor_findings_status ON vendor_findings(tenant_id, status); CREATE INDEX IF NOT EXISTS idx_vendor_findings_category ON vendor_findings(tenant_id, category); -- ============================================================================ -- Indexes: Control Instances -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_tenant ON vendor_control_instances(tenant_id); CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_vendor ON vendor_control_instances(vendor_id); CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_control_id ON vendor_control_instances(control_id); CREATE INDEX IF NOT EXISTS idx_vendor_control_instances_status ON vendor_control_instances(tenant_id, status); -- ============================================================================ -- Indexes: Templates -- ============================================================================ CREATE INDEX IF NOT EXISTS idx_compliance_templates_type ON compliance_templates(template_type); CREATE INDEX IF NOT EXISTS idx_compliance_templates_category ON compliance_templates(category) WHERE category IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_compliance_templates_industry ON compliance_templates(industry) WHERE industry IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_compliance_templates_system ON compliance_templates(is_system); CREATE INDEX IF NOT EXISTS idx_compliance_templates_active ON compliance_templates(is_active); CREATE INDEX IF NOT EXISTS idx_compliance_templates_template_id ON compliance_templates(template_id); -- ============================================================================ -- Triggers -- ============================================================================ -- Reuse existing update_updated_at_column function -- Vendors trigger DROP TRIGGER IF EXISTS update_vendor_vendors_updated_at ON vendor_vendors; CREATE TRIGGER update_vendor_vendors_updated_at BEFORE UPDATE ON vendor_vendors FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Contracts trigger DROP TRIGGER IF EXISTS update_vendor_contracts_updated_at ON vendor_contracts; CREATE TRIGGER update_vendor_contracts_updated_at BEFORE UPDATE ON vendor_contracts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Findings trigger DROP TRIGGER IF EXISTS update_vendor_findings_updated_at ON vendor_findings; CREATE TRIGGER update_vendor_findings_updated_at BEFORE UPDATE ON vendor_findings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Control instances trigger DROP TRIGGER IF EXISTS update_vendor_control_instances_updated_at ON vendor_control_instances; CREATE TRIGGER update_vendor_control_instances_updated_at BEFORE UPDATE ON vendor_control_instances FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Templates trigger DROP TRIGGER IF EXISTS update_compliance_templates_updated_at ON compliance_templates; CREATE TRIGGER update_compliance_templates_updated_at BEFORE UPDATE ON compliance_templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ============================================================================ -- Comments -- ============================================================================ -- Table comments COMMENT ON TABLE vendor_vendors IS 'Service provider registry for vendor compliance management (DSGVO Art. 28)'; COMMENT ON TABLE vendor_contracts IS 'Contract and AVV/DPA document management with AI-assisted review'; COMMENT ON TABLE vendor_findings IS 'Compliance findings from contract reviews and vendor assessments'; COMMENT ON TABLE vendor_control_instances IS 'Applied controls per vendor with assessment tracking'; COMMENT ON TABLE compliance_templates IS 'Pre-filled templates for vendors, processing activities, TOMs, and control sets'; -- Vendor column comments COMMENT ON COLUMN vendor_vendors.role IS 'DSGVO role: PROCESSOR (Art. 28), CONTROLLER, JOINT_CONTROLLER (Art. 26), SUB_PROCESSOR, THIRD_PARTY'; COMMENT ON COLUMN vendor_vendors.data_access_level IS 'Level of access to personal data: NONE, POTENTIAL, ADMINISTRATIVE, CONTENT'; COMMENT ON COLUMN vendor_vendors.processing_locations IS 'JSONB array: Data processing locations with EU/adequacy status for transfer assessment'; COMMENT ON COLUMN vendor_vendors.certifications IS 'JSONB array: Vendor certifications (ISO 27001, SOC 2, etc.)'; COMMENT ON COLUMN vendor_vendors.inherent_risk_score IS 'Risk score (0-100) before controls are applied'; COMMENT ON COLUMN vendor_vendors.residual_risk_score IS 'Risk score (0-100) after controls are applied'; COMMENT ON COLUMN vendor_vendors.review_frequency IS 'How often the vendor must be reviewed: QUARTERLY, SEMI_ANNUAL, ANNUAL, BIENNIAL'; COMMENT ON COLUMN vendor_vendors.processing_activity_ids IS 'JSONB array: UUIDs linking to dsgvo_processing_activities entries'; COMMENT ON COLUMN vendor_vendors.template_id IS 'Reference to compliance_templates.template_id if vendor was created from a template'; -- Contract column comments COMMENT ON COLUMN vendor_contracts.document_type IS 'Contract type: AVV (Auftragsverarbeitungsvertrag), MSA, SLA, SCC (Standard Contractual Clauses), NDA, TOM_ANNEX, CERTIFICATION, SUB_PROCESSOR_LIST'; COMMENT ON COLUMN vendor_contracts.storage_path IS 'MinIO object storage path for the uploaded document'; COMMENT ON COLUMN vendor_contracts.compliance_score IS 'AI-assessed compliance score (0-100) from contract review'; COMMENT ON COLUMN vendor_contracts.extracted_text IS 'Full text extracted from the document for AI analysis'; COMMENT ON COLUMN vendor_contracts.previous_version_id IS 'Self-referencing FK for contract version history'; -- Finding column comments COMMENT ON COLUMN vendor_findings.finding_type IS 'Classification: OK (compliant), GAP (missing clause), RISK (problematic clause), UNKNOWN (could not determine)'; COMMENT ON COLUMN vendor_findings.category IS 'DSGVO Art. 28 requirement category the finding relates to'; COMMENT ON COLUMN vendor_findings.citations IS 'JSONB array: References to specific contract text passages with page/character offsets'; COMMENT ON COLUMN vendor_findings.status IS 'Workflow status: OPEN, IN_PROGRESS, RESOLVED, ACCEPTED (risk accepted), FALSE_POSITIVE'; -- Control instance column comments COMMENT ON COLUMN vendor_control_instances.control_id IS 'Control identifier (e.g., VND-TRF-01 for transfer controls)'; COMMENT ON COLUMN vendor_control_instances.control_domain IS 'Control domain: TRANSFER, AUDIT, DELETION, INCIDENT, SUBPROCESSOR, TOM, CONTRACT, DATA_SUBJECT, SECURITY, GOVERNANCE'; COMMENT ON COLUMN vendor_control_instances.status IS 'Assessment result: PASS, PARTIAL, FAIL, NOT_APPLICABLE, PLANNED'; COMMENT ON COLUMN vendor_control_instances.evidence_ids IS 'JSONB array: References to evidence documents or contract IDs'; -- Template column comments COMMENT ON COLUMN compliance_templates.template_type IS 'Template category: VENDOR, PROCESSING_ACTIVITY, TOM, CONTROL_SET'; COMMENT ON COLUMN compliance_templates.template_id IS 'Human-readable unique identifier (e.g., tpl-vendor-cloud-iaas)'; COMMENT ON COLUMN compliance_templates.template_data IS 'JSONB: Full template content including all pre-filled fields'; COMMENT ON COLUMN compliance_templates.is_system IS 'true = pre-installed system template, false = user-created tenant template'; COMMENT ON COLUMN compliance_templates.usage_count IS 'Number of times this template has been used to create entities';