-- 权限 2.0 完整闭环:审批策略、执行状态、结构化审计与历史租户归属 -- 该迁移可独立执行:兼容仅执行过 000_full_init.sql、或未执行 008 的旧数据库。 CREATE TABLE IF NOT EXISTS tenant_members ( tenant_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', status TEXT NOT NULL DEFAULT 'active', invited_by TEXT, joined_at TEXT, expires_at TEXT, PRIMARY KEY (tenant_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_tenant_members_user ON tenant_members(user_id, status); CREATE INDEX IF NOT EXISTS idx_tenant_members_tenant ON tenant_members(tenant_id, status); INSERT INTO tenant_members (tenant_id, user_id, role, status, joined_at) SELECT COALESCE(u.tenant_id, 'default'), u.id, CASE WHEN u.role='admin' OR COALESCE(u.protected, 0)=1 THEN 'owner' ELSE 'member' END, 'active', COALESCE(u.create_time, NOW()::text) FROM users u ON CONFLICT (tenant_id, user_id) DO NOTHING; CREATE TABLE IF NOT EXISTS resource_access_requests ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL DEFAULT 'default', resource_type TEXT NOT NULL, resource_id TEXT NOT NULL, applicant_id TEXT NOT NULL, principal_type TEXT NOT NULL DEFAULT 'user', principal_id TEXT NOT NULL, requested_permissions TEXT NOT NULL DEFAULT '[]', reason TEXT, approval_id TEXT, status TEXT NOT NULL DEFAULT 'pending', expires_at TEXT, created_at TEXT NOT NULL DEFAULT to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), decided_at TEXT, decided_by TEXT ); ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS action TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS requested_permissions TEXT NOT NULL DEFAULT '[]'; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS reason TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS expires_at TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS decided_by TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS decided_at TEXT; ALTER TABLE approval_steps ADD COLUMN IF NOT EXISTS approver_type TEXT NOT NULL DEFAULT 'user'; ALTER TABLE acls ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE acls ADD COLUMN IF NOT EXISTS granted_by TEXT; ALTER TABLE acls ADD COLUMN IF NOT EXISTS source_request_id TEXT; ALTER TABLE acls ADD COLUMN IF NOT EXISTS expires_at TEXT; ALTER TABLE acls ADD COLUMN IF NOT EXISTS revoked_at TEXT; ALTER TABLE datasets ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE models ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS tenant_id TEXT; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS action TEXT; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS resource_type TEXT; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS scope TEXT NOT NULL DEFAULT 'tenant'; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'active'; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS created_by TEXT; ALTER TABLE approval_templates ADD COLUMN IF NOT EXISTS updated_at TEXT; CREATE INDEX IF NOT EXISTS idx_approval_templates_match ON approval_templates(tenant_id, action, resource_type, status); ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS execution_status TEXT NOT NULL DEFAULT 'pending'; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS execution_error TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS executed_at TEXT; ALTER TABLE approval_instances ADD COLUMN IF NOT EXISTS executed_by TEXT; CREATE INDEX IF NOT EXISTS idx_approval_instances_execution ON approval_instances(status, execution_status, action, resource_type, resource_id); ALTER TABLE resource_access_requests ADD COLUMN IF NOT EXISTS cancelled_at TEXT; ALTER TABLE resource_access_requests ADD COLUMN IF NOT EXISTS cancelled_by TEXT; CREATE INDEX IF NOT EXISTS idx_resource_access_requests_expiry ON resource_access_requests(status, expires_at); ALTER TABLE projects ADD COLUMN IF NOT EXISTS deleted_at TEXT; ALTER TABLE projects ADD COLUMN IF NOT EXISTS deleted_by TEXT; CREATE INDEX IF NOT EXISTS idx_projects_active_tenant ON projects(tenant_id, status, deleted_at); ALTER TABLE tenants ADD COLUMN IF NOT EXISTS deleted_at TEXT; ALTER TABLE tenants ADD COLUMN IF NOT EXISTS deleted_by TEXT; CREATE INDEX IF NOT EXISTS idx_tenants_active ON tenants(status, deleted_at); ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS created_by TEXT; ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT NOT NULL DEFAULT 'default'; CREATE INDEX IF NOT EXISTS idx_data_convert_tasks_tenant ON data_convert_tasks(tenant_id, deleted_at); ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS result TEXT NOT NULL DEFAULT 'success'; ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS reason TEXT; ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS request_id TEXT; ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS session_id TEXT; ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS metadata TEXT; CREATE INDEX IF NOT EXISTS idx_audit_request ON audit_logs(request_id); CREATE INDEX IF NOT EXISTS idx_audit_result ON audit_logs(result, time); -- 历史资源按创建者租户补齐归属。无法识别的资源保留 default,后续由管理员复核。 UPDATE datasets d SET tenant_id = COALESCE(u.tenant_id, 'default') FROM users u WHERE d.created_by = u.id AND (d.tenant_id IS NULL OR d.tenant_id = ''); UPDATE models m SET tenant_id = COALESCE(u.tenant_id, 'default') FROM users u WHERE m.created_by = u.id AND (m.tenant_id IS NULL OR m.tenant_id = ''); UPDATE trained_models m SET tenant_id = COALESCE(u.tenant_id, 'default') FROM users u WHERE m.created_by = u.id AND (m.tenant_id IS NULL OR m.tenant_id = ''); UPDATE eval_tasks e SET tenant_id = COALESCE(u.tenant_id, 'default') FROM users u WHERE e.created_by = u.id AND (e.tenant_id IS NULL OR e.tenant_id = '');