feat: 平台治理与权限体系完善,存储进度/GPU预留/审批中心与日志整合
- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请 - 存储: MinIO 存储进度迁移、对象存储安全加固与测试 - 计算: GPU 资源预留、compute 轮询与同步增强 - 权限: permission v2 迁移、权限安全验收测试 - 日志: 后端运行日志中文说明、操作日志整合 - 数据处理/评测: 数据转换与模型评测优化 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
-- 用途:切换到新的 PG 数据集时,一次性创建平台运行所需的全部数据库对象与
|
||||
-- 基础种子数据(幂等,可重复执行)。
|
||||
--
|
||||
-- 覆盖范围(与运行时代码实际使用的表一致):
|
||||
-- 覆盖范围(与运行时代码实际使用的表一致,离线新库只需执行本文件):
|
||||
-- 001_platform_runtime.sql 平台核心表
|
||||
-- 002_governance.sql 治理表(租户 / 审批 / 审计 / 留存)
|
||||
-- 003_tenant_quota.sql 租户配额列
|
||||
@@ -16,10 +16,9 @@
|
||||
-- 说明:
|
||||
-- * 本脚本通过 psql 执行,包含 DO $$ ... $$ 块与事务,不能用应用的
|
||||
-- executescript()(按分号切分)执行。
|
||||
-- * 应用启动时 PlatformStore.ensure_schema() 只会自动执行
|
||||
-- 001 / 002_governance / 003_tenant_quota;数据处理表需另跑
|
||||
-- 002_data_process.sql(本脚本已包含)。应用首次启动还会自动补充
|
||||
-- admin/operator 种子用户(本脚本已包含,二选一即可)。
|
||||
-- * 本文件已经合并平台运行、治理、数据处理、权限、MinIO、GPU 预留、
|
||||
-- 缓存治理和数据转换等全部初始化对象;全新数据库无需再执行其他 SQL。
|
||||
-- 应用启动时的 ensure_schema() 仅作为兼容兜底,不是离线初始化前置条件。
|
||||
-- * 脚本内所有 DDL 均使用 IF NOT EXISTS / ADD COLUMN IF NOT EXISTS,
|
||||
-- 可在已初始化的库上安全重复执行。
|
||||
--
|
||||
@@ -45,12 +44,20 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
password_hash TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
platform_role TEXT NOT NULL DEFAULT 'platform_user',
|
||||
status TEXT NOT NULL,
|
||||
permissions TEXT NOT NULL,
|
||||
create_time TEXT NOT NULL,
|
||||
last_login TEXT,
|
||||
protected INTEGER NOT NULL DEFAULT 0
|
||||
protected INTEGER NOT NULL DEFAULT 0,
|
||||
tenant_id TEXT NOT NULL DEFAULT 'default',
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(status, deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_platform_role ON users(platform_role, status, deleted_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS models (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -68,6 +75,9 @@ CREATE TABLE IF NOT EXISTS models (
|
||||
created_by TEXT,
|
||||
tenant_id TEXT,
|
||||
project_id TEXT,
|
||||
storage_status TEXT NOT NULL DEFAULT 'pending',
|
||||
storage_error TEXT,
|
||||
storage_version_id TEXT,
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);
|
||||
@@ -131,6 +141,10 @@ CREATE TABLE IF NOT EXISTS model_export_jobs (
|
||||
create_time TEXT NOT NULL,
|
||||
completed_at TEXT
|
||||
);
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS archive_status TEXT NOT NULL DEFAULT 'pending';
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS archive_error TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS datasets (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -209,8 +223,16 @@ CREATE TABLE IF NOT EXISTS fine_tune_tasks (
|
||||
compute_node_id TEXT REFERENCES compute_nodes(id) ON DELETE SET NULL,
|
||||
gpus TEXT NOT NULL,
|
||||
sync_job_id TEXT,
|
||||
compute_job_id TEXT
|
||||
compute_job_id TEXT,
|
||||
tenant_id TEXT NOT NULL DEFAULT 'default',
|
||||
created_by TEXT,
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_tenant_active
|
||||
ON fine_tune_tasks(tenant_id, status, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_creator
|
||||
ON fine_tune_tasks(created_by, deleted_at, create_time DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fine_tune_metrics (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -273,6 +295,8 @@ CREATE TABLE IF NOT EXISTS resource_replicas (
|
||||
node_id TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
|
||||
resource_type TEXT NOT NULL,
|
||||
resource_id TEXT NOT NULL,
|
||||
version_id TEXT,
|
||||
storage_object_id TEXT,
|
||||
local_path TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
sync_status TEXT NOT NULL,
|
||||
@@ -310,6 +334,11 @@ CREATE TABLE IF NOT EXISTS storage_objects (
|
||||
create_time TEXT NOT NULL,
|
||||
UNIQUE (resource_type, resource_id, version_id, object_key)
|
||||
);
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS cleanup_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_cleanup_error TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_verified_at TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS storage_cache_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -323,6 +352,23 @@ CREATE TABLE IF NOT EXISTS storage_cache_jobs (
|
||||
create_time TEXT NOT NULL,
|
||||
completed_at TEXT
|
||||
);
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS version_id TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS checksum_sha256 TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS byte_size BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS last_accessed_at TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS protected_until TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS storage_cleanup_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
storage_object_id TEXT NOT NULL REFERENCES storage_objects(id) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL DEFAULT 'delete',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
error TEXT,
|
||||
create_time TEXT NOT NULL,
|
||||
completed_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_cleanup_status ON storage_cleanup_jobs(status, create_time);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_objects_resource ON storage_objects(resource_type, resource_id, version_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_cache_jobs_node_status ON storage_cache_jobs(node_id, status);
|
||||
@@ -332,8 +378,16 @@ CREATE TABLE IF NOT EXISTS eval_tasks (
|
||||
name TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
create_time TEXT NOT NULL
|
||||
create_time TEXT NOT NULL,
|
||||
created_by TEXT,
|
||||
tenant_id TEXT,
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS eval_dimensions (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -349,8 +403,17 @@ CREATE TABLE IF NOT EXISTS compare_tasks (
|
||||
name TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
create_time TEXT NOT NULL
|
||||
create_time TEXT NOT NULL,
|
||||
created_by TEXT,
|
||||
tenant_id TEXT,
|
||||
deleted_at TEXT,
|
||||
deleted_by TEXT
|
||||
);
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
CREATE INDEX IF NOT EXISTS idx_compare_tasks_active ON compare_tasks(status, deleted_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_status ON fine_tune_tasks(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_compute_job ON fine_tune_tasks(compute_job_id);
|
||||
@@ -368,6 +431,24 @@ CREATE INDEX IF NOT EXISTS idx_compute_jobs_task ON compute_jobs(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_compute_jobs_node_status ON compute_jobs(node_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpu_allocations_node_status ON gpu_allocations(node_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_gpu_allocations_active ON gpu_allocations(node_id, gpu_index) WHERE status IN ('allocated','running');
|
||||
|
||||
-- 评测/推理等非训练任务的统一 GPU 原子预留。训练任务继续使用 gpu_allocations。
|
||||
CREATE TABLE IF NOT EXISTS gpu_reservations (
|
||||
id TEXT PRIMARY KEY,
|
||||
node_id TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
|
||||
gpu_index INTEGER NOT NULL,
|
||||
owner_type TEXT NOT NULL CHECK (owner_type IN ('eval', 'inference')),
|
||||
owner_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'reserved' CHECK (status IN ('reserved', 'released')),
|
||||
create_time TEXT NOT NULL,
|
||||
released_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_owner
|
||||
ON gpu_reservations(owner_type, owner_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_node_status
|
||||
ON gpu_reservations(node_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_gpu_reservations_active
|
||||
ON gpu_reservations(node_id, gpu_index) WHERE status = 'reserved';
|
||||
CREATE INDEX IF NOT EXISTS idx_scheduler_locks_expires ON scheduler_locks(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_dataset_files_dataset ON dataset_files(dataset_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpus_node ON gpus(node_id);
|
||||
@@ -393,6 +474,9 @@ CREATE TABLE IF NOT EXISTS projects (
|
||||
create_by TEXT,
|
||||
updated_at TEXT
|
||||
);
|
||||
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);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_members (
|
||||
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
@@ -433,6 +517,15 @@ CREATE TABLE IF NOT EXISTS acls (
|
||||
);
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS storage_status TEXT NOT NULL DEFAULT 'pending';
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS storage_error TEXT;
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS storage_version_id TEXT;
|
||||
UPDATE models SET storage_status = CASE
|
||||
WHEN model_source IN ('api', 'online') THEN 'not_applicable'
|
||||
WHEN storage_status IS NULL OR storage_status = '' THEN 'pending'
|
||||
ELSE storage_status
|
||||
END
|
||||
WHERE storage_status IS NULL OR storage_status = '' OR model_source IN ('api', 'online');
|
||||
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
@@ -462,6 +555,7 @@ ALTER TABLE datasets ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE models ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS tenant_id TEXT NOT NULL DEFAULT 'default';
|
||||
|
||||
-- ============================================================================
|
||||
-- 二、治理表(来源:002_governance.sql)
|
||||
@@ -477,6 +571,15 @@ CREATE TABLE IF NOT EXISTS tenants (
|
||||
retention_policy_id TEXT,
|
||||
create_time TEXT
|
||||
);
|
||||
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);
|
||||
INSERT INTO tenants (id, name, code, status, quota, create_time)
|
||||
VALUES ('default', '默认租户', 'default', 'active', '{}', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'))
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
INSERT INTO tenants (id, name, code, status, quota, create_time)
|
||||
VALUES ('admin', '管理员租户', 'admin', 'active', '{}', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'))
|
||||
ON CONFLICT (id) DO UPDATE SET name='管理员租户', code='admin', status='active', deleted_at=NULL, deleted_by=NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS approval_templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -534,6 +637,34 @@ CREATE INDEX IF NOT EXISTS idx_audit_project ON audit_logs(project_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_time ON audit_logs(time);
|
||||
|
||||
-- ---- 操作日志(接口操作审计) ----
|
||||
CREATE TABLE IF NOT EXISTS operation_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
username TEXT,
|
||||
module TEXT,
|
||||
action TEXT,
|
||||
target_type TEXT,
|
||||
target_id TEXT,
|
||||
target_name TEXT,
|
||||
status TEXT NOT NULL,
|
||||
error_message TEXT,
|
||||
error_type TEXT,
|
||||
error_traceback TEXT,
|
||||
func_name TEXT,
|
||||
detail TEXT,
|
||||
client_ip TEXT,
|
||||
request_method TEXT,
|
||||
request_path TEXT,
|
||||
trace_id TEXT,
|
||||
duration_ms REAL,
|
||||
create_time TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_logs_user_time ON operation_logs(user_id, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_logs_module_time ON operation_logs(module, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_logs_status ON operation_logs(status, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_operation_logs_create_time ON operation_logs(create_time DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS retention_policies (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
@@ -876,29 +1007,201 @@ CREATE INDEX IF NOT EXISTS idx_data_convert_tasks_create_time ON data_convert_ta
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS storage_backend TEXT NOT NULL DEFAULT 'minio';
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS output_storage_object_id TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS output_content TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS processed_by TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS processed_at TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS deleted_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 eval_tasks ADD COLUMN IF NOT EXISTS report_storage_object_id TEXT;
|
||||
ALTER TABLE resource_replicas ADD COLUMN IF NOT EXISTS version_id TEXT;
|
||||
ALTER TABLE resource_replicas ADD COLUMN IF NOT EXISTS storage_object_id TEXT;
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_replicas_object_002 ON resource_replicas(storage_object_id, node_id);
|
||||
|
||||
-- ============================================================================
|
||||
-- 七、种子数据:初始管理员 / 操作员
|
||||
-- 六、权限 2.0:租户成员、资源申请、ACL 生命周期和审批动作
|
||||
-- ============================================================================
|
||||
|
||||
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;
|
||||
|
||||
-- GPU/存储任务使用的租户配额原子预留账本。任务提交时预留,终态或故障回收。
|
||||
CREATE TABLE IF NOT EXISTS tenant_quota_reservations (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
owner_type TEXT NOT NULL,
|
||||
owner_id TEXT NOT NULL,
|
||||
gpu_count INTEGER NOT NULL DEFAULT 0,
|
||||
storage_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'reserved',
|
||||
create_time TEXT NOT NULL,
|
||||
released_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_tenant_quota_reservations_tenant
|
||||
ON tenant_quota_reservations(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_tenant_quota_reservations_owner
|
||||
ON tenant_quota_reservations(owner_type, owner_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_tenant_quota_reservations_active_owner
|
||||
ON tenant_quota_reservations(owner_type, owner_id) WHERE status = 'reserved';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_access_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
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,
|
||||
decided_at TEXT,
|
||||
decided_by TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_access_requests_resource
|
||||
ON resource_access_requests(resource_type, resource_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_access_requests_applicant
|
||||
ON resource_access_requests(applicant_id, status);
|
||||
|
||||
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;
|
||||
CREATE INDEX IF NOT EXISTS idx_acls_tenant_active ON acls(tenant_id, revoked_at, expires_at);
|
||||
|
||||
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';
|
||||
|
||||
-- ============================================================================
|
||||
-- 七、权限 2.0 完整闭环:审批策略、执行状态、结构化审计与历史归属
|
||||
-- ============================================================================
|
||||
|
||||
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 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);
|
||||
|
||||
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 = '');
|
||||
|
||||
-- ============================================================================
|
||||
-- 八、种子数据:初始管理员 / 操作员
|
||||
-- 应用首次启动(ensure_seed_data)也会自动创建;此处提供以便脱离应用直接初始化。
|
||||
-- 密码:admin / admin123,operator / operator123(上线前请改密)。
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO users
|
||||
(id, username, password_hash, display_name, role, status, permissions, create_time, protected)
|
||||
(id, username, password_hash, display_name, role, platform_role, status, permissions, create_time, protected, tenant_id)
|
||||
VALUES
|
||||
(
|
||||
'u_admin', 'admin', 'pbkdf2_sha256$390000$ygft_init_salt_admin$2b6f31f22968c4f5a30bcf0acf066b7a0f58d4773d15c5ab898ba715ea87b5bd',
|
||||
'Platform Admin', 'admin', 'active',
|
||||
'Admin', 'admin', 'platform_admin', 'active',
|
||||
'["dashboard","fine-tune","model-eval","model-inference","model-manage","dataset","data-process","data-convert","compute","hardware","logs","user-settings"]',
|
||||
to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), 1
|
||||
to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), 1, 'admin'
|
||||
),
|
||||
(
|
||||
'u_operator', 'operator', 'pbkdf2_sha256$390000$ygft_init_salt_op$525bf35d02ed26f37952cbd6862b0ae358b9d1a7fa0cbbf0217aa2b5dd544125',
|
||||
'Platform Operator', 'operator', 'active',
|
||||
'["dashboard","fine-tune","model-eval","model-inference","model-manage","dataset","data-process","data-convert","compute","hardware","logs"]',
|
||||
to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), 0
|
||||
'Platform Operator', 'operator', 'platform_user', 'active',
|
||||
'["dashboard","fine-tune","model-eval","model-inference","model-manage","dataset","data-process","data-convert","hardware","logs"]',
|
||||
to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'), 0, 'default'
|
||||
)
|
||||
ON CONFLICT (username) DO NOTHING;
|
||||
UPDATE users SET display_name='Admin', tenant_id='admin' WHERE username='admin';
|
||||
|
||||
INSERT INTO tenant_members (tenant_id, user_id, role, status, joined_at)
|
||||
VALUES
|
||||
('admin', 'u_admin', 'owner', 'active', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')),
|
||||
('default', 'u_operator', 'member', 'active', to_char(now(), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'))
|
||||
ON CONFLICT (tenant_id, user_id) DO NOTHING;
|
||||
UPDATE tenant_members SET status='disabled' WHERE tenant_id='default' AND user_id='u_admin';
|
||||
|
||||
-- Canonical hierarchy fields for new and historical rows. Project columns are
|
||||
-- intentionally retained only as compatibility data and are not used here.
|
||||
UPDATE users
|
||||
SET platform_role = CASE WHEN role = 'admin' OR COALESCE(protected, 0) = 1
|
||||
THEN 'platform_admin' ELSE 'platform_user' END
|
||||
WHERE platform_role IS NULL OR platform_role NOT IN ('platform_admin', 'platform_user');
|
||||
INSERT INTO tenant_members (tenant_id, user_id, role, status, joined_at)
|
||||
SELECT t.id, t.owner_user_id, 'owner', 'active', COALESCE(t.create_time, NOW()::text)
|
||||
FROM tenants t
|
||||
JOIN users u ON u.id = t.owner_user_id
|
||||
WHERE t.owner_user_id IS NOT NULL AND COALESCE(t.status, 'active') = 'active'
|
||||
ON CONFLICT (tenant_id, user_id) DO UPDATE SET role='owner', status='active';
|
||||
UPDATE tenant_members tm
|
||||
SET role='member'
|
||||
FROM users u
|
||||
WHERE tm.user_id=u.id AND tm.role='owner'
|
||||
AND (u.role <> 'admin' AND COALESCE(u.protected, 0)=0)
|
||||
AND NOT EXISTS (SELECT 1 FROM tenants t WHERE t.id=tm.tenant_id AND t.owner_user_id=tm.user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_datasets_tenant_active ON datasets(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_tenant_active ON models(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_trained_models_tenant_active ON trained_models(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_eval_tasks_tenant_active ON eval_tasks(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_compare_tasks_tenant_active ON compare_tasks(tenant_id, deleted_at, create_time DESC);
|
||||
|
||||
COMMIT;
|
||||
|
||||
32
backend/app/db/sql/005_storage_progress_migration.sql
Normal file
32
backend/app/db/sql/005_storage_progress_migration.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- YG Fine-Tune Platform additive migration: MinIO completion and cache manifest.
|
||||
-- Execute with psql against an existing database after taking a schema backup.
|
||||
-- The statements are idempotent and are also included in 000_full_init.sql.
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE resource_replicas ADD COLUMN IF NOT EXISTS version_id TEXT;
|
||||
ALTER TABLE resource_replicas ADD COLUMN IF NOT EXISTS storage_object_id TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS tenant_id TEXT NOT NULL DEFAULT 'default';
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS metadata TEXT NOT NULL DEFAULT '{}';
|
||||
ALTER TABLE model_artifacts ADD COLUMN IF NOT EXISTS storage_object_id TEXT;
|
||||
ALTER TABLE model_artifacts ADD COLUMN IF NOT EXISTS storage_backend TEXT NOT NULL DEFAULT 'minio';
|
||||
ALTER TABLE dataset_files ADD COLUMN IF NOT EXISTS storage_object_id TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS storage_backend TEXT NOT NULL DEFAULT 'minio';
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS output_storage_object_id TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS output_content TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS project_id TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS report_storage_object_id TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_replicas_object_005
|
||||
ON resource_replicas(storage_object_id, node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_objects_resource_005
|
||||
ON storage_objects(resource_type, resource_id, version_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_eval_tasks_active_005
|
||||
ON eval_tasks(create_time DESC) WHERE deleted_at IS NULL;
|
||||
|
||||
COMMIT;
|
||||
21
backend/app/db/sql/006_gpu_reservations.sql
Normal file
21
backend/app/db/sql/006_gpu_reservations.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- 原子 GPU 预留:评测和推理与训练统一纳入调度占用模型。
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gpu_reservations (
|
||||
id TEXT PRIMARY KEY,
|
||||
node_id TEXT NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
|
||||
gpu_index INTEGER NOT NULL,
|
||||
owner_type TEXT NOT NULL CHECK (owner_type IN ('eval', 'inference')),
|
||||
owner_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'reserved' CHECK (status IN ('reserved', 'released')),
|
||||
create_time TEXT NOT NULL,
|
||||
released_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_owner
|
||||
ON gpu_reservations(owner_type, owner_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_node_status
|
||||
ON gpu_reservations(node_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_gpu_reservations_active
|
||||
ON gpu_reservations(node_id, gpu_index) WHERE status = 'reserved';
|
||||
|
||||
COMMIT;
|
||||
33
backend/app/db/sql/007_platform_completion.sql
Normal file
33
backend/app/db/sql/007_platform_completion.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
-- 平台可靠性闭环:导出权限、对象清理、缓存 manifest 元数据。
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS archive_status TEXT NOT NULL DEFAULT 'pending';
|
||||
ALTER TABLE model_export_jobs ADD COLUMN IF NOT EXISTS archive_error TEXT;
|
||||
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS cleanup_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_cleanup_error TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_verified_at TEXT;
|
||||
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS version_id TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS checksum_sha256 TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS byte_size BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS last_accessed_at TEXT;
|
||||
ALTER TABLE storage_cache_jobs ADD COLUMN IF NOT EXISTS protected_until TEXT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS storage_cleanup_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
storage_object_id TEXT NOT NULL REFERENCES storage_objects(id) ON DELETE CASCADE,
|
||||
action TEXT NOT NULL DEFAULT 'delete',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
error TEXT,
|
||||
create_time TEXT NOT NULL,
|
||||
completed_at TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_cleanup_status ON storage_cleanup_jobs(status, create_time);
|
||||
|
||||
COMMIT;
|
||||
53
backend/app/db/sql/008_permission_v2.sql
Normal file
53
backend/app/db/sql/008_permission_v2.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
-- 权限 2.0:租户成员、资源申请、ACL 生命周期和审批动作
|
||||
|
||||
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);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_access_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
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,
|
||||
decided_at TEXT,
|
||||
decided_by TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_access_requests_resource
|
||||
ON resource_access_requests(resource_type, resource_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_access_requests_applicant
|
||||
ON resource_access_requests(applicant_id, status);
|
||||
|
||||
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;
|
||||
CREATE INDEX IF NOT EXISTS idx_acls_tenant_active ON acls(tenant_id, revoked_at, expires_at);
|
||||
|
||||
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';
|
||||
|
||||
116
backend/app/db/sql/009_permission_completion.sql
Normal file
116
backend/app/db/sql/009_permission_completion.sql
Normal file
@@ -0,0 +1,116 @@
|
||||
-- 权限 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 = '');
|
||||
21
backend/app/db/sql/010_permission_quota_membership.sql
Normal file
21
backend/app/db/sql/010_permission_quota_membership.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Permission 2.0: atomic tenant quota reservations for GPU-backed tasks.
|
||||
-- This migration is additive and safe to run repeatedly.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tenant_quota_reservations (
|
||||
id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
owner_type TEXT NOT NULL,
|
||||
owner_id TEXT NOT NULL,
|
||||
gpu_count INTEGER NOT NULL DEFAULT 0,
|
||||
storage_bytes BIGINT NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'reserved',
|
||||
create_time TEXT NOT NULL,
|
||||
released_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tenant_quota_reservations_tenant
|
||||
ON tenant_quota_reservations(tenant_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_tenant_quota_reservations_owner
|
||||
ON tenant_quota_reservations(owner_type, owner_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_tenant_quota_reservations_active_owner
|
||||
ON tenant_quota_reservations(owner_type, owner_id) WHERE status = 'reserved';
|
||||
20
backend/app/db/sql/011_permission_lifecycle.sql
Normal file
20
backend/app/db/sql/011_permission_lifecycle.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Permission 2.0: user and inference task lifecycle tombstones.
|
||||
-- Additive migration, safe to execute repeatedly.
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE compare_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
||||
ALTER TABLE data_convert_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS cleanup_attempts INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_cleanup_error TEXT;
|
||||
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS last_verified_at TEXT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_active ON users(status, deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_compare_tasks_active ON compare_tasks(status, deleted_at);
|
||||
63
backend/app/db/sql/012_tenant_user_hierarchy.sql
Normal file
63
backend/app/db/sql/012_tenant_user_hierarchy.sql
Normal file
@@ -0,0 +1,63 @@
|
||||
-- Tenant/user hierarchy alignment. Additive and safe to run repeatedly.
|
||||
-- New business flows use tenant_members, platform_role, tenant_id and
|
||||
-- created_by. Legacy role/project fields remain for compatibility only.
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS platform_role TEXT NOT NULL DEFAULT 'platform_user';
|
||||
UPDATE users
|
||||
SET platform_role = CASE
|
||||
WHEN role = 'admin' OR COALESCE(protected, 0) = 1 THEN 'platform_admin'
|
||||
ELSE 'platform_user'
|
||||
END
|
||||
WHERE platform_role IS NULL OR platform_role NOT IN ('platform_admin', 'platform_user');
|
||||
CREATE INDEX IF NOT EXISTS idx_users_platform_role ON users(platform_role, status, deleted_at);
|
||||
|
||||
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS tenant_id TEXT NOT NULL DEFAULT 'default';
|
||||
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS created_by TEXT;
|
||||
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS deleted_at TEXT;
|
||||
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS deleted_by TEXT;
|
||||
UPDATE fine_tune_tasks
|
||||
SET tenant_id = COALESCE(NULLIF(tenant_id, ''), payload::json->>'tenant_id', 'default'),
|
||||
created_by = COALESCE(NULLIF(created_by, ''), payload::json->>'created_by')
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR created_by IS NULL OR created_by = '';
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_tenant_active
|
||||
ON fine_tune_tasks(tenant_id, status, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_creator
|
||||
ON fine_tune_tasks(created_by, deleted_at, create_time DESC);
|
||||
|
||||
INSERT INTO tenants (id, name, code, status, quota, create_time)
|
||||
VALUES ('default', '默认租户', 'default', 'active', '{}', NOW()::text)
|
||||
ON CONFLICT (id) DO UPDATE
|
||||
SET status = CASE WHEN COALESCE(tenants.status, 'active') = 'deleted' THEN 'active' ELSE tenants.status END,
|
||||
deleted_at = CASE WHEN COALESCE(tenants.status, 'active') = 'deleted' THEN NULL ELSE tenants.deleted_at END,
|
||||
deleted_by = CASE WHEN COALESCE(tenants.status, 'active') = 'deleted' THEN NULL ELSE tenants.deleted_by END;
|
||||
|
||||
-- Make the tenant owner relationship explicit and repair older tenant rows.
|
||||
INSERT INTO tenant_members (tenant_id, user_id, role, status, joined_at)
|
||||
SELECT t.id, t.owner_user_id, 'owner', 'active', COALESCE(t.create_time, NOW()::text)
|
||||
FROM tenants t
|
||||
JOIN users u ON u.id = t.owner_user_id
|
||||
WHERE t.owner_user_id IS NOT NULL
|
||||
AND COALESCE(t.status, 'active') = 'active'
|
||||
ON CONFLICT (tenant_id, user_id) DO UPDATE
|
||||
SET role = 'owner', status = 'active';
|
||||
|
||||
-- Existing account creation used admin as a tenant owner. Platform role and
|
||||
-- tenant role are separate, so keep only explicit tenant owners as owners.
|
||||
UPDATE tenant_members tm
|
||||
SET role = 'member'
|
||||
FROM users u
|
||||
WHERE tm.user_id = u.id
|
||||
AND tm.role = 'owner'
|
||||
AND (u.role <> 'admin' AND COALESCE(u.protected, 0) = 0)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM tenants t
|
||||
WHERE t.id = tm.tenant_id AND t.owner_user_id = tm.user_id
|
||||
);
|
||||
|
||||
-- Project is no longer a business isolation boundary. Keep historical columns
|
||||
-- readable, but make tenant-scoped queries the only supported new path.
|
||||
CREATE INDEX IF NOT EXISTS idx_datasets_tenant_active ON datasets(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_tenant_active ON models(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_trained_models_tenant_active ON trained_models(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_eval_tasks_tenant_active ON eval_tasks(tenant_id, deleted_at, create_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_compare_tasks_tenant_active ON compare_tasks(tenant_id, deleted_at, create_time DESC);
|
||||
Reference in New Issue
Block a user