- 添加 Dockerfile, docker-compose.yml, .dockerignore, nginx 配置 - 添加后端 API 设计文档、平台架构需求文档、系统开发计划 - 添加 PostgreSQL schema 设计 - 更新 README.md 和 design-qa.md
1387 lines
60 KiB
PL/PgSQL
1387 lines
60 KiB
PL/PgSQL
-- PostgreSQL schema for the model fine-tuning platform.
|
|
-- Recommended PostgreSQL version: 14+.
|
|
|
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
CREATE EXTENSION IF NOT EXISTS citext;
|
|
|
|
CREATE SCHEMA IF NOT EXISTS ft_platform;
|
|
SET search_path TO ft_platform, public;
|
|
|
|
-- =========================
|
|
-- Common helpers
|
|
-- =========================
|
|
|
|
CREATE OR REPLACE FUNCTION ft_platform.set_updated_at()
|
|
RETURNS trigger AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE OR REPLACE FUNCTION ft_platform.touch_updated_at(table_name regclass)
|
|
RETURNS void AS $$
|
|
BEGIN
|
|
EXECUTE format('DROP TRIGGER IF EXISTS trg_set_updated_at ON %s', table_name);
|
|
EXECUTE format(
|
|
'CREATE TRIGGER trg_set_updated_at BEFORE UPDATE ON %s
|
|
FOR EACH ROW EXECUTE FUNCTION ft_platform.set_updated_at()',
|
|
table_name
|
|
);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- =========================
|
|
-- Enums
|
|
-- =========================
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_role') THEN
|
|
CREATE TYPE user_role AS ENUM ('admin', 'operator', 'viewer');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_status') THEN
|
|
CREATE TYPE user_status AS ENUM ('active', 'disabled');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'task_status') THEN
|
|
CREATE TYPE task_status AS ENUM ('pending', 'running', 'completed', 'failed', 'stopped');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'model_type') THEN
|
|
CREATE TYPE model_type AS ENUM ('LLM', 'CV', 'NLP', 'Embedding', 'Other');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'model_purpose') THEN
|
|
CREATE TYPE model_purpose AS ENUM ('training', 'inference', 'evaluation');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'model_source') THEN
|
|
CREATE TYPE model_source AS ENUM ('local', 'api');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'dataset_type') THEN
|
|
CREATE TYPE dataset_type AS ENUM ('train', 'test', 'eval', 'val', 'other');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'dataset_storage') THEN
|
|
CREATE TYPE dataset_storage AS ENUM ('local', 'minio', 'cloud');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'dataset_source') THEN
|
|
CREATE TYPE dataset_source AS ENUM ('upload', 'task');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'train_type') THEN
|
|
CREATE TYPE train_type AS ENUM ('SFT', 'DPO', 'CPT');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'train_method') THEN
|
|
CREATE TYPE train_method AS ENUM ('lora', 'qlora', 'full', 'prefix', 'adapter', 'peft', 'adalora', 'longlora');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'process_type') THEN
|
|
CREATE TYPE process_type AS ENUM ('structured', 'unstructured', 'external');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'eval_type') THEN
|
|
CREATE TYPE eval_type AS ENUM ('custom', 'baseline');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'dimension_type') THEN
|
|
CREATE TYPE dimension_type AS ENUM ('classification', 'metric', 'text_similarity');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'result_status') THEN
|
|
CREATE TYPE result_status AS ENUM ('valid', 'modified', 'invalid');
|
|
END IF;
|
|
END $$;
|
|
|
|
-- =========================
|
|
-- User center and RBAC
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
username citext NOT NULL UNIQUE,
|
|
display_name varchar(100) NOT NULL,
|
|
password_hash text NOT NULL,
|
|
role user_role NOT NULL DEFAULT 'viewer',
|
|
status user_status NOT NULL DEFAULT 'active',
|
|
protected boolean NOT NULL DEFAULT false,
|
|
last_login_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('users');
|
|
|
|
CREATE TABLE IF NOT EXISTS permissions (
|
|
code varchar(64) PRIMARY KEY,
|
|
name varchar(100) NOT NULL,
|
|
description text,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS role_permissions (
|
|
role user_role NOT NULL,
|
|
permission_code varchar(64) NOT NULL REFERENCES permissions(code) ON DELETE CASCADE,
|
|
PRIMARY KEY (role, permission_code)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS user_permissions (
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
permission_code varchar(64) NOT NULL REFERENCES permissions(code) ON DELETE CASCADE,
|
|
allowed boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (user_id, permission_code)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS login_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
token_jti uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(),
|
|
ip inet,
|
|
user_agent text,
|
|
expires_at timestamptz NOT NULL,
|
|
revoked_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_login_sessions_user_created ON login_sessions(user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_login_sessions_expires ON login_sessions(expires_at) WHERE revoked_at IS NULL;
|
|
|
|
INSERT INTO permissions(code, name, description) VALUES
|
|
('dashboard', '服务看板', '访问服务看板'),
|
|
('fine-tune', '模型训练', '创建和管理微调任务'),
|
|
('model-eval', '模型评测', '创建和管理评测任务'),
|
|
('model-inference', '模型推理', '创建推理与模型对比任务'),
|
|
('model-manage', '模型管理', '登记、编辑、删除模型'),
|
|
('dataset', '数据集管理', '上传、编辑、下载数据集'),
|
|
('data-process', '数据处理', '创建和管理数据处理任务'),
|
|
('data-convert', '数据转换/工具', '使用数据转换和工具中心'),
|
|
('hardware', '平台性能', '查看硬件和系统监控'),
|
|
('logs', '查看日志', '查看系统与训练日志'),
|
|
('user-settings', '用户设置', '管理用户和权限')
|
|
ON CONFLICT (code) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description;
|
|
|
|
INSERT INTO role_permissions(role, permission_code)
|
|
SELECT 'admin'::user_role, code FROM permissions
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
INSERT INTO role_permissions(role, permission_code) VALUES
|
|
('operator', 'dashboard'),
|
|
('operator', 'fine-tune'),
|
|
('operator', 'model-eval'),
|
|
('operator', 'model-inference'),
|
|
('operator', 'model-manage'),
|
|
('operator', 'dataset'),
|
|
('operator', 'data-process'),
|
|
('operator', 'data-convert'),
|
|
('operator', 'hardware'),
|
|
('operator', 'logs'),
|
|
('viewer', 'dashboard'),
|
|
('viewer', 'model-eval'),
|
|
('viewer', 'model-inference'),
|
|
('viewer', 'dataset'),
|
|
('viewer', 'hardware'),
|
|
('viewer', 'logs')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Replace this password hash during deployment.
|
|
INSERT INTO users(username, display_name, password_hash, role, status, protected)
|
|
VALUES ('admin', '系统管理员', '$argon2id$replace-with-real-hash', 'admin', 'active', true)
|
|
ON CONFLICT (username) DO NOTHING;
|
|
|
|
-- =========================
|
|
-- Audit and operation logs
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id bigserial PRIMARY KEY,
|
|
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
username citext,
|
|
action varchar(100) NOT NULL,
|
|
resource_type varchar(80) NOT NULL,
|
|
resource_id text,
|
|
request_method varchar(12),
|
|
request_path text,
|
|
ip inet,
|
|
user_agent text,
|
|
success boolean NOT NULL DEFAULT true,
|
|
error_message text,
|
|
before_data jsonb,
|
|
after_data jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_created ON audit_logs(user_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS web_logs (
|
|
id bigserial PRIMARY KEY,
|
|
user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
level varchar(20) NOT NULL,
|
|
message text NOT NULL,
|
|
page text,
|
|
context jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_web_logs_created ON web_logs(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_web_logs_level_created ON web_logs(level, created_at DESC);
|
|
|
|
-- =========================
|
|
-- Files and object storage metadata
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS storage_objects (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
storage_type dataset_storage NOT NULL DEFAULT 'local',
|
|
bucket varchar(128),
|
|
object_key text NOT NULL,
|
|
original_name text,
|
|
mime_type varchar(200),
|
|
byte_size bigint NOT NULL DEFAULT 0 CHECK (byte_size >= 0),
|
|
checksum_sha256 char(64),
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_storage_object_location
|
|
ON storage_objects(storage_type, COALESCE(bucket, ''), object_key);
|
|
CREATE INDEX IF NOT EXISTS idx_storage_objects_checksum ON storage_objects(checksum_sha256);
|
|
|
|
-- =========================
|
|
-- Model registry
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS models (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
type model_type NOT NULL DEFAULT 'LLM',
|
|
purpose model_purpose NOT NULL,
|
|
model_source model_source NOT NULL DEFAULT 'local',
|
|
description text,
|
|
path text,
|
|
api_url text,
|
|
api_key_encrypted text,
|
|
online_model_name varchar(200),
|
|
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz,
|
|
CONSTRAINT ck_model_local_or_api CHECK (
|
|
(model_source = 'local' AND path IS NOT NULL)
|
|
OR
|
|
(model_source = 'api' AND api_url IS NOT NULL AND online_model_name IS NOT NULL)
|
|
)
|
|
);
|
|
SELECT touch_updated_at('models');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_models_name_alive ON models(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_models_purpose ON models(purpose) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_models_type_source ON models(type, model_source) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS trained_models (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
base_model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
fine_tune_task_id uuid,
|
|
train_method train_method,
|
|
adapter_path text,
|
|
merged boolean NOT NULL DEFAULT false,
|
|
merging boolean NOT NULL DEFAULT false,
|
|
merged_path text,
|
|
export_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
metrics jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('trained_models');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_trained_models_name_alive ON trained_models(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_trained_models_task ON trained_models(fine_tune_task_id);
|
|
CREATE INDEX IF NOT EXISTS idx_trained_models_base ON trained_models(base_model_id);
|
|
|
|
-- =========================
|
|
-- Datasets and versions
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS datasets (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
type dataset_type NOT NULL,
|
|
storage_type dataset_storage NOT NULL DEFAULT 'local',
|
|
source dataset_source NOT NULL DEFAULT 'upload',
|
|
source_task_id uuid,
|
|
size_bytes bigint NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
|
|
record_count bigint NOT NULL DEFAULT 0 CHECK (record_count >= 0),
|
|
description text,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('datasets');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_datasets_name_alive ON datasets(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_datasets_type_created ON datasets(type, created_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_datasets_source_task ON datasets(source_task_id) WHERE source = 'task';
|
|
|
|
CREATE TABLE IF NOT EXISTS dataset_files (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
dataset_id uuid NOT NULL REFERENCES datasets(id) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
storage_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
current_version_id uuid,
|
|
size_bytes bigint NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
|
|
record_count bigint NOT NULL DEFAULT 0 CHECK (record_count >= 0),
|
|
file_format varchar(40),
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('dataset_files');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_dataset_files_name_alive
|
|
ON dataset_files(dataset_id, name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_files_dataset ON dataset_files(dataset_id) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS dataset_file_versions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
dataset_file_id uuid NOT NULL REFERENCES dataset_files(id) ON DELETE CASCADE,
|
|
version_no integer NOT NULL CHECK (version_no > 0),
|
|
storage_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
content_preview text,
|
|
description text,
|
|
base_version_id uuid REFERENCES dataset_file_versions(id) ON DELETE SET NULL,
|
|
size_bytes bigint NOT NULL DEFAULT 0 CHECK (size_bytes >= 0),
|
|
record_count bigint NOT NULL DEFAULT 0 CHECK (record_count >= 0),
|
|
checksum_sha256 char(64),
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_dataset_file_versions_no
|
|
ON dataset_file_versions(dataset_file_id, version_no);
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_file_versions_file_created
|
|
ON dataset_file_versions(dataset_file_id, created_at DESC);
|
|
|
|
ALTER TABLE dataset_files
|
|
DROP CONSTRAINT IF EXISTS fk_dataset_files_current_version;
|
|
ALTER TABLE dataset_files
|
|
ADD CONSTRAINT fk_dataset_files_current_version
|
|
FOREIGN KEY (current_version_id) REFERENCES dataset_file_versions(id) ON DELETE SET NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS dataset_records (
|
|
id bigserial PRIMARY KEY,
|
|
dataset_id uuid NOT NULL REFERENCES datasets(id) ON DELETE CASCADE,
|
|
dataset_file_id uuid REFERENCES dataset_files(id) ON DELETE CASCADE,
|
|
version_id uuid REFERENCES dataset_file_versions(id) ON DELETE CASCADE,
|
|
line_no integer,
|
|
split varchar(20),
|
|
instruction text,
|
|
input text,
|
|
output text,
|
|
raw jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
status result_status NOT NULL DEFAULT 'valid',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_records_dataset_id ON dataset_records(dataset_id, id);
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_records_file_version ON dataset_records(dataset_file_id, version_id, line_no);
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_records_split ON dataset_records(dataset_id, split);
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_records_raw_gin ON dataset_records USING gin(raw);
|
|
|
|
-- =========================
|
|
-- Data processing
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS data_process_tasks (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
description text,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
process_type process_type NOT NULL,
|
|
source_dataset_id uuid REFERENCES datasets(id) ON DELETE SET NULL,
|
|
output_dataset_id uuid REFERENCES datasets(id) ON DELETE SET NULL,
|
|
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
progress numeric(5,2) NOT NULL DEFAULT 0 CHECK (progress >= 0 AND progress <= 100),
|
|
input_count bigint NOT NULL DEFAULT 0,
|
|
output_count bigint NOT NULL DEFAULT 0,
|
|
filtered_count bigint NOT NULL DEFAULT 0,
|
|
duplicate_count bigint NOT NULL DEFAULT 0,
|
|
error_count bigint NOT NULL DEFAULT 0,
|
|
failure_reason text,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('data_process_tasks');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_data_process_tasks_name_alive
|
|
ON data_process_tasks(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_tasks_status_created
|
|
ON data_process_tasks(status, created_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_tasks_creator_created
|
|
ON data_process_tasks(created_by, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS data_process_source_files (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
task_id uuid NOT NULL REFERENCES data_process_tasks(id) ON DELETE CASCADE,
|
|
storage_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
name text NOT NULL,
|
|
size_bytes bigint NOT NULL DEFAULT 0,
|
|
record_count bigint NOT NULL DEFAULT 0,
|
|
content_preview text,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_source_files_task ON data_process_source_files(task_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS data_process_preview_items (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
task_id uuid NOT NULL REFERENCES data_process_tasks(id) ON DELETE CASCADE,
|
|
source_file_id uuid REFERENCES data_process_source_files(id) ON DELETE CASCADE,
|
|
original_content text NOT NULL DEFAULT '',
|
|
edited_content text NOT NULL DEFAULT '',
|
|
source_start integer,
|
|
source_end integer,
|
|
source_start_line integer,
|
|
source_end_line integer,
|
|
token_count integer NOT NULL DEFAULT 0,
|
|
status varchar(20) NOT NULL DEFAULT 'original',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('data_process_preview_items');
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_preview_task_file
|
|
ON data_process_preview_items(task_id, source_file_id, created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS data_process_results (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
task_id uuid NOT NULL REFERENCES data_process_tasks(id) ON DELETE CASCADE,
|
|
preview_item_id uuid REFERENCES data_process_preview_items(id) ON DELETE SET NULL,
|
|
instruction text NOT NULL,
|
|
input text NOT NULL DEFAULT '',
|
|
output text NOT NULL,
|
|
original_instruction text,
|
|
original_input text,
|
|
original_output text,
|
|
status result_status NOT NULL DEFAULT 'valid',
|
|
error text,
|
|
split varchar(20),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('data_process_results');
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_results_task_status
|
|
ON data_process_results(task_id, status, id);
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_results_task_split
|
|
ON data_process_results(task_id, split);
|
|
|
|
-- =========================
|
|
-- Fine-tune tasks
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS fine_tune_tasks (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
description text,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
train_type train_type NOT NULL,
|
|
train_method train_method NOT NULL DEFAULT 'lora',
|
|
template varchar(80) NOT NULL DEFAULT 'qwen',
|
|
base_model_id uuid NOT NULL REFERENCES models(id) ON DELETE RESTRICT,
|
|
train_dataset_id uuid REFERENCES datasets(id) ON DELETE SET NULL,
|
|
auto_merge boolean NOT NULL DEFAULT false,
|
|
output_model_name varchar(150),
|
|
gpus integer[] NOT NULL DEFAULT '{}',
|
|
params jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
progress numeric(5,2) NOT NULL DEFAULT 0 CHECK (progress >= 0 AND progress <= 100),
|
|
process_id integer,
|
|
command text,
|
|
output_dir text,
|
|
log_file text,
|
|
train_duration_seconds integer,
|
|
failure_reason text,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('fine_tune_tasks');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_fine_tune_tasks_name_alive
|
|
ON fine_tune_tasks(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_status_created
|
|
ON fine_tune_tasks(status, created_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_dataset ON fine_tune_tasks(train_dataset_id);
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_base_model ON fine_tune_tasks(base_model_id);
|
|
|
|
ALTER TABLE trained_models
|
|
DROP CONSTRAINT IF EXISTS fk_trained_models_fine_tune_task;
|
|
ALTER TABLE trained_models
|
|
ADD CONSTRAINT fk_trained_models_fine_tune_task
|
|
FOREIGN KEY (fine_tune_task_id) REFERENCES fine_tune_tasks(id) ON DELETE SET NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS fine_tune_metrics (
|
|
id bigserial PRIMARY KEY,
|
|
task_id uuid NOT NULL REFERENCES fine_tune_tasks(id) ON DELETE CASCADE,
|
|
step integer,
|
|
epoch numeric(10,4),
|
|
loss numeric(18,8),
|
|
learning_rate numeric(18,12),
|
|
grad_norm numeric(18,8),
|
|
metrics jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_metrics_task_step ON fine_tune_metrics(task_id, step);
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_metrics_task_created ON fine_tune_metrics(task_id, created_at);
|
|
|
|
-- =========================
|
|
-- Inference and compare
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS inference_tasks (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
description text,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
load_status jsonb NOT NULL DEFAULT '{"loaded_models":[]}'::jsonb,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('inference_tasks');
|
|
CREATE INDEX IF NOT EXISTS idx_inference_tasks_status_created
|
|
ON inference_tasks(status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS inference_task_models (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
task_id uuid NOT NULL REFERENCES inference_tasks(id) ON DELETE CASCADE,
|
|
model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
trained_model_id uuid REFERENCES trained_models(id) ON DELETE SET NULL,
|
|
model_name varchar(150) NOT NULL,
|
|
model_path text,
|
|
gpu_id integer,
|
|
source varchar(40),
|
|
port integer,
|
|
pid integer,
|
|
status varchar(40) NOT NULL DEFAULT 'pending',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('inference_task_models');
|
|
CREATE INDEX IF NOT EXISTS idx_inference_task_models_task ON inference_task_models(task_id);
|
|
CREATE INDEX IF NOT EXISTS idx_inference_task_models_pid ON inference_task_models(pid) WHERE pid IS NOT NULL;
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_inference_task_models_port_alive
|
|
ON inference_task_models(port) WHERE port IS NOT NULL AND status IN ('loading', 'ready');
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
inference_task_id uuid REFERENCES inference_tasks(id) ON DELETE SET NULL,
|
|
title varchar(200),
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('chat_sessions');
|
|
CREATE INDEX IF NOT EXISTS idx_chat_sessions_task_created ON chat_sessions(inference_task_id, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
id bigserial PRIMARY KEY,
|
|
session_id uuid NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
|
|
model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
trained_model_id uuid REFERENCES trained_models(id) ON DELETE SET NULL,
|
|
role varchar(20) NOT NULL,
|
|
content text NOT NULL,
|
|
latency_ms integer,
|
|
token_usage jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id ON chat_messages(session_id, id);
|
|
|
|
-- =========================
|
|
-- Evaluation
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS eval_dimensions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(150) NOT NULL,
|
|
type dimension_type NOT NULL,
|
|
description text,
|
|
eval_model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
eval_method jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
eval_prompt text,
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
is_default boolean NOT NULL DEFAULT false,
|
|
bleu_n integer,
|
|
output_precision integer NOT NULL DEFAULT 3,
|
|
score_min numeric(12,4),
|
|
score_max numeric(12,4),
|
|
pass_threshold numeric(12,4),
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('eval_dimensions');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_eval_dimensions_name_alive
|
|
ON eval_dimensions(name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_eval_dimensions_active ON eval_dimensions(is_active) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS eval_tasks (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
eval_task_name varchar(150) NOT NULL,
|
|
eval_type eval_type NOT NULL DEFAULT 'custom',
|
|
model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
trained_model_id uuid REFERENCES trained_models(id) ON DELETE SET NULL,
|
|
dataset_id uuid REFERENCES datasets(id) ON DELETE SET NULL,
|
|
dimension_id uuid REFERENCES eval_dimensions(id) ON DELETE SET NULL,
|
|
gpu_id integer,
|
|
data_source varchar(40) NOT NULL DEFAULT 'dataset',
|
|
leaderboard boolean NOT NULL DEFAULT false,
|
|
basic_metrics jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
metric varchar(80),
|
|
score numeric(12,4),
|
|
overall_score numeric(12,4),
|
|
overall_score_max numeric(12,4),
|
|
overall_evaluation text,
|
|
improvement_suggestions jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
sample_count integer NOT NULL DEFAULT 0,
|
|
completed_count integer NOT NULL DEFAULT 0,
|
|
passed_count integer NOT NULL DEFAULT 0,
|
|
failure_reason text,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('eval_tasks');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_eval_tasks_name_alive
|
|
ON eval_tasks(eval_task_name) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_eval_tasks_status_created ON eval_tasks(status, created_at DESC) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_eval_tasks_model_dataset ON eval_tasks(model_id, dataset_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS eval_dimension_summaries (
|
|
id bigserial PRIMARY KEY,
|
|
eval_task_id uuid NOT NULL REFERENCES eval_tasks(id) ON DELETE CASCADE,
|
|
name varchar(150) NOT NULL,
|
|
score numeric(12,4),
|
|
max_score numeric(12,4),
|
|
pass_rate numeric(6,2),
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_eval_dimension_summaries_task ON eval_dimension_summaries(eval_task_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS eval_sample_results (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
eval_task_id uuid NOT NULL REFERENCES eval_tasks(id) ON DELETE CASCADE,
|
|
sample_index integer NOT NULL,
|
|
input text NOT NULL,
|
|
reference_answer text,
|
|
model_output text NOT NULL DEFAULT '',
|
|
score numeric(12,4),
|
|
max_score numeric(12,4),
|
|
passed boolean,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
judgement varchar(40),
|
|
evaluation_reason text,
|
|
error_type varchar(80),
|
|
dimension_scores jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('eval_sample_results');
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_eval_sample_results_task_index
|
|
ON eval_sample_results(eval_task_id, sample_index);
|
|
CREATE INDEX IF NOT EXISTS idx_eval_sample_results_task_status
|
|
ON eval_sample_results(eval_task_id, status);
|
|
|
|
-- =========================
|
|
-- Data convert and custom tools
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS data_convert_jobs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
convert_type varchar(40) NOT NULL DEFAULT 'json_to_jsonl',
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
output_name varchar(200) NOT NULL,
|
|
encoding varchar(40) NOT NULL DEFAULT 'UTF-8',
|
|
source_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
result_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
error_message text,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('data_convert_jobs');
|
|
CREATE INDEX IF NOT EXISTS idx_data_convert_jobs_user_created ON data_convert_jobs(created_by, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_data_convert_jobs_status ON data_convert_jobs(status, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS custom_tools (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name varchar(100) NOT NULL,
|
|
description text,
|
|
url text NOT NULL,
|
|
icon varchar(80) NOT NULL DEFAULT 'fa-cog',
|
|
visibility varchar(20) NOT NULL DEFAULT 'private',
|
|
owner_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('custom_tools');
|
|
CREATE INDEX IF NOT EXISTS idx_custom_tools_owner ON custom_tools(owner_id) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_custom_tools_visibility ON custom_tools(visibility) WHERE deleted_at IS NULL;
|
|
|
|
-- =========================
|
|
-- Metrics snapshots
|
|
-- =========================
|
|
|
|
CREATE TABLE IF NOT EXISTS system_metric_snapshots (
|
|
id bigserial PRIMARY KEY,
|
|
cpu jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
memory jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
disk jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
gpu jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
network jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
system jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_system_metric_snapshots_created ON system_metric_snapshots(created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS service_status_snapshots (
|
|
id bigserial PRIMARY KEY,
|
|
service_name varchar(100) NOT NULL,
|
|
state varchar(30) NOT NULL,
|
|
instances_online integer NOT NULL DEFAULT 0,
|
|
instances_total integer NOT NULL DEFAULT 0,
|
|
detail jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_service_status_snapshots_name_created
|
|
ON service_status_snapshots(service_name, created_at DESC);
|
|
|
|
-- =========================
|
|
-- Useful views
|
|
-- =========================
|
|
|
|
CREATE OR REPLACE VIEW v_user_effective_permissions AS
|
|
SELECT
|
|
u.id AS user_id,
|
|
u.username,
|
|
p.code AS permission_code,
|
|
COALESCE(up.allowed, rp.permission_code IS NOT NULL, false) AS allowed
|
|
FROM users u
|
|
CROSS JOIN permissions p
|
|
LEFT JOIN role_permissions rp
|
|
ON rp.role = u.role AND rp.permission_code = p.code
|
|
LEFT JOIN user_permissions up
|
|
ON up.user_id = u.id AND up.permission_code = p.code
|
|
WHERE u.deleted_at IS NULL;
|
|
|
|
CREATE OR REPLACE VIEW v_dataset_summary AS
|
|
SELECT
|
|
d.id,
|
|
d.name,
|
|
d.type,
|
|
d.storage_type,
|
|
d.source,
|
|
d.source_task_id,
|
|
d.size_bytes,
|
|
d.record_count,
|
|
d.description,
|
|
count(df.id) FILTER (WHERE df.deleted_at IS NULL) AS file_count,
|
|
d.created_at,
|
|
d.updated_at
|
|
FROM datasets d
|
|
LEFT JOIN dataset_files df ON df.dataset_id = d.id
|
|
WHERE d.deleted_at IS NULL
|
|
GROUP BY d.id;
|
|
|
|
-- =========================
|
|
-- Maintenance notes
|
|
-- =========================
|
|
|
|
-- 1. For very large installations, convert audit_logs, web_logs,
|
|
-- system_metric_snapshots, fine_tune_metrics and eval_sample_results to
|
|
-- monthly/range partitions.
|
|
-- 2. Keep large file bodies in storage_objects, not in relational rows.
|
|
-- 3. Encrypt api_key_encrypted and external source secrets at the application layer
|
|
-- with KMS or a deployment secret.
|
|
-- 4. Use soft delete for user-facing resources to preserve audit trails.
|
|
|
|
-- =========================
|
|
-- Enterprise governance and compute extension
|
|
-- =========================
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'tenant_status') THEN
|
|
CREATE TYPE tenant_status AS ENUM ('active', 'disabled');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'project_status') THEN
|
|
CREATE TYPE project_status AS ENUM ('active', 'archived', 'disabled');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'project_role') THEN
|
|
CREATE TYPE project_role AS ENUM ('owner', 'maintainer', 'developer', 'reviewer', 'viewer');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'approval_status') THEN
|
|
CREATE TYPE approval_status AS ENUM ('not_required', 'pending', 'approved', 'rejected', 'cancelled', 'expired');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'compute_job_type') THEN
|
|
CREATE TYPE compute_job_type AS ENUM ('fine_tune', 'eval', 'data_process', 'inference', 'merge', 'convert', 'import');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'job_priority') THEN
|
|
CREATE TYPE job_priority AS ENUM ('low', 'normal', 'high', 'urgent');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'gpu_status') THEN
|
|
CREATE TYPE gpu_status AS ENUM ('idle', 'reserved', 'running', 'draining', 'offline', 'error');
|
|
END IF;
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'service_level') THEN
|
|
CREATE TYPE service_level AS ENUM ('test', 'production');
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS tenants (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code citext NOT NULL UNIQUE,
|
|
name varchar(150) NOT NULL,
|
|
status tenant_status NOT NULL DEFAULT 'active',
|
|
owner_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
quota_config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
retention_config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('tenants');
|
|
CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS tenant_users (
|
|
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role user_role NOT NULL DEFAULT 'viewer',
|
|
is_tenant_admin boolean NOT NULL DEFAULT false,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (tenant_id, user_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_tenant_users_user ON tenant_users(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
|
|
code citext NOT NULL,
|
|
name varchar(150) NOT NULL,
|
|
description text,
|
|
status project_status NOT NULL DEFAULT 'active',
|
|
owner_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
quota_config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
default_acl jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
archived_at timestamptz,
|
|
deleted_at timestamptz,
|
|
UNIQUE (tenant_id, code)
|
|
);
|
|
SELECT touch_updated_at('projects');
|
|
CREATE INDEX IF NOT EXISTS idx_projects_tenant_status ON projects(tenant_id, status) WHERE deleted_at IS NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_projects_owner ON projects(owner_id) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS project_members (
|
|
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role project_role NOT NULL DEFAULT 'viewer',
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (project_id, user_id)
|
|
);
|
|
SELECT touch_updated_at('project_members');
|
|
CREATE INDEX IF NOT EXISTS idx_project_members_user ON project_members(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS storage_nodes (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code citext NOT NULL UNIQUE,
|
|
name varchar(150) NOT NULL,
|
|
node_type varchar(40) NOT NULL DEFAULT 'compute_local',
|
|
base_path text NOT NULL,
|
|
total_bytes bigint,
|
|
used_bytes bigint NOT NULL DEFAULT 0,
|
|
status varchar(40) NOT NULL DEFAULT 'online',
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('storage_nodes');
|
|
|
|
CREATE TABLE IF NOT EXISTS training_engines (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code citext NOT NULL UNIQUE,
|
|
name varchar(150) NOT NULL,
|
|
version varchar(80),
|
|
engine_type varchar(60) NOT NULL DEFAULT 'llama_factory',
|
|
executable_path text,
|
|
python_env_path text,
|
|
supported_task_types jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
supported_methods jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
supported_formats jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
schema jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
status varchar(40) NOT NULL DEFAULT 'enabled',
|
|
last_health_check_at timestamptz,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('training_engines');
|
|
|
|
INSERT INTO training_engines(
|
|
code, name, engine_type, supported_task_types, supported_methods, supported_formats, status
|
|
) VALUES (
|
|
'llama_factory',
|
|
'LLaMA-Factory',
|
|
'llama_factory',
|
|
'["SFT", "DPO", "CPT"]'::jsonb,
|
|
'["lora", "qlora", "full"]'::jsonb,
|
|
'["alpaca", "sharegpt", "dpo_pair", "pretrain_text"]'::jsonb,
|
|
'enabled'
|
|
) ON CONFLICT (code) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS quotas (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
subject_type varchar(40) NOT NULL,
|
|
subject_id uuid,
|
|
gpu_concurrency integer NOT NULL DEFAULT 0,
|
|
storage_bytes bigint NOT NULL DEFAULT 0,
|
|
max_running_jobs integer NOT NULL DEFAULT 0,
|
|
max_projects integer NOT NULL DEFAULT 0,
|
|
max_upload_file_bytes bigint NOT NULL DEFAULT 0,
|
|
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (subject_type, subject_id)
|
|
);
|
|
SELECT touch_updated_at('quotas');
|
|
CREATE INDEX IF NOT EXISTS idx_quotas_tenant_project ON quotas(tenant_id, project_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS quota_usage (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
subject_type varchar(40) NOT NULL,
|
|
subject_id uuid,
|
|
gpu_running integer NOT NULL DEFAULT 0,
|
|
storage_used_bytes bigint NOT NULL DEFAULT 0,
|
|
running_jobs integer NOT NULL DEFAULT 0,
|
|
usage_detail jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
measured_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (subject_type, subject_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_quota_usage_tenant_project ON quota_usage(tenant_id, project_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS retention_policies (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
audit_days integer NOT NULL DEFAULT 180,
|
|
login_log_days integer NOT NULL DEFAULT 180,
|
|
training_log_days integer NOT NULL DEFAULT 90,
|
|
metric_raw_days integer NOT NULL DEFAULT 30,
|
|
temp_file_days integer NOT NULL DEFAULT 1,
|
|
failed_job_workspace_days integer NOT NULL DEFAULT 14,
|
|
checkpoint_policy jsonb NOT NULL DEFAULT '{"keep_last":3,"keep_best":2,"failed_job_days":14}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (tenant_id, project_id)
|
|
);
|
|
SELECT touch_updated_at('retention_policies');
|
|
|
|
CREATE TABLE IF NOT EXISTS approval_templates (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
action varchar(80) NOT NULL,
|
|
name varchar(150) NOT NULL,
|
|
enabled boolean NOT NULL DEFAULT true,
|
|
approver_rules jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
risk_level varchar(40) NOT NULL DEFAULT 'medium',
|
|
expire_hours integer NOT NULL DEFAULT 24,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('approval_templates');
|
|
CREATE INDEX IF NOT EXISTS idx_approval_templates_scope_action
|
|
ON approval_templates(tenant_id, project_id, action);
|
|
|
|
CREATE TABLE IF NOT EXISTS approval_instances (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
template_id uuid REFERENCES approval_templates(id) ON DELETE SET NULL,
|
|
action varchar(80) NOT NULL,
|
|
resource_type varchar(80) NOT NULL,
|
|
resource_id text NOT NULL,
|
|
reason text,
|
|
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
status approval_status NOT NULL DEFAULT 'pending',
|
|
requested_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
decided_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
decided_at timestamptz,
|
|
expires_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('approval_instances');
|
|
CREATE INDEX IF NOT EXISTS idx_approval_instances_status_created
|
|
ON approval_instances(status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_approval_instances_scope
|
|
ON approval_instances(tenant_id, project_id, action, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_approval_instances_resource
|
|
ON approval_instances(resource_type, resource_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS approval_steps (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
approval_id uuid NOT NULL REFERENCES approval_instances(id) ON DELETE CASCADE,
|
|
step_no integer NOT NULL,
|
|
approver_user_id uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
approver_role varchar(80),
|
|
status approval_status NOT NULL DEFAULT 'pending',
|
|
comment text,
|
|
decided_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (approval_id, step_no)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_approval_steps_approver
|
|
ON approval_steps(approver_user_id, status);
|
|
|
|
CREATE TABLE IF NOT EXISTS compute_nodes (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
code citext NOT NULL UNIQUE,
|
|
name varchar(150) NOT NULL,
|
|
host varchar(200) NOT NULL,
|
|
api_base_url text NOT NULL,
|
|
storage_node_id uuid REFERENCES storage_nodes(id) ON DELETE SET NULL,
|
|
status varchar(40) NOT NULL DEFAULT 'online',
|
|
agent_version varchar(80),
|
|
gpu_count integer NOT NULL DEFAULT 0,
|
|
last_heartbeat_at timestamptz,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('compute_nodes');
|
|
CREATE INDEX IF NOT EXISTS idx_compute_nodes_status ON compute_nodes(status);
|
|
|
|
CREATE TABLE IF NOT EXISTS gpu_devices (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
compute_node_id uuid NOT NULL REFERENCES compute_nodes(id) ON DELETE CASCADE,
|
|
gpu_index integer NOT NULL,
|
|
uuid varchar(120) NOT NULL,
|
|
name varchar(150) NOT NULL,
|
|
status gpu_status NOT NULL DEFAULT 'idle',
|
|
memory_total_mb integer NOT NULL DEFAULT 0,
|
|
memory_used_mb integer NOT NULL DEFAULT 0,
|
|
utilization_percent numeric(5,2) NOT NULL DEFAULT 0,
|
|
temperature numeric(5,2),
|
|
power_w numeric(8,2),
|
|
driver_version varchar(80),
|
|
partition_type varchar(40) NOT NULL DEFAULT 'full',
|
|
parent_gpu_uuid varchar(120),
|
|
current_job_id uuid,
|
|
last_seen_at timestamptz,
|
|
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (compute_node_id, gpu_index),
|
|
UNIQUE (uuid)
|
|
);
|
|
SELECT touch_updated_at('gpu_devices');
|
|
CREATE INDEX IF NOT EXISTS idx_gpu_devices_node_status ON gpu_devices(compute_node_id, status);
|
|
|
|
CREATE TABLE IF NOT EXISTS compute_jobs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
app_task_type varchar(80) NOT NULL,
|
|
app_task_id uuid,
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL,
|
|
project_id uuid REFERENCES projects(id) ON DELETE SET NULL,
|
|
compute_node_id uuid REFERENCES compute_nodes(id) ON DELETE SET NULL,
|
|
engine_id uuid REFERENCES training_engines(id) ON DELETE SET NULL,
|
|
job_type compute_job_type NOT NULL,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
priority job_priority NOT NULL DEFAULT 'normal',
|
|
resource_request jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
workspace_root text,
|
|
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
progress numeric(5,2) NOT NULL DEFAULT 0 CHECK (progress >= 0 AND progress <= 100),
|
|
pid integer,
|
|
port integer,
|
|
failure_reason text,
|
|
requested_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
completed_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('compute_jobs');
|
|
CREATE INDEX IF NOT EXISTS idx_compute_jobs_scope_status
|
|
ON compute_jobs(tenant_id, project_id, status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_compute_jobs_node_status
|
|
ON compute_jobs(compute_node_id, status, priority, created_at);
|
|
CREATE INDEX IF NOT EXISTS idx_compute_jobs_app_task
|
|
ON compute_jobs(app_task_type, app_task_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS gpu_allocations (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
compute_job_id uuid NOT NULL REFERENCES compute_jobs(id) ON DELETE CASCADE,
|
|
gpu_device_id uuid NOT NULL REFERENCES gpu_devices(id) ON DELETE RESTRICT,
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL,
|
|
project_id uuid REFERENCES projects(id) ON DELETE SET NULL,
|
|
status varchar(40) NOT NULL DEFAULT 'reserved',
|
|
allocated_at timestamptz NOT NULL DEFAULT now(),
|
|
released_at timestamptz
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_gpu_allocations_job ON gpu_allocations(compute_job_id);
|
|
CREATE INDEX IF NOT EXISTS idx_gpu_allocations_scope ON gpu_allocations(tenant_id, project_id, allocated_at DESC);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_gpu_allocations_active_gpu
|
|
ON gpu_allocations(gpu_device_id) WHERE released_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS resource_acl (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
resource_type varchar(80) NOT NULL,
|
|
resource_id text NOT NULL,
|
|
subject_type varchar(40) NOT NULL,
|
|
subject_id text NOT NULL,
|
|
permissions text[] NOT NULL DEFAULT '{}',
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
UNIQUE (resource_type, resource_id, subject_type, subject_id)
|
|
);
|
|
SELECT touch_updated_at('resource_acl');
|
|
CREATE INDEX IF NOT EXISTS idx_resource_acl_resource
|
|
ON resource_acl(resource_type, resource_id);
|
|
CREATE INDEX IF NOT EXISTS idx_resource_acl_subject
|
|
ON resource_acl(subject_type, subject_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS file_upload_sessions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
storage_node_id uuid REFERENCES storage_nodes(id) ON DELETE SET NULL,
|
|
original_name text NOT NULL,
|
|
byte_size bigint NOT NULL DEFAULT 0,
|
|
checksum_sha256 char(64),
|
|
part_size_bytes integer NOT NULL DEFAULT 8388608,
|
|
uploaded_parts jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
status varchar(40) NOT NULL DEFAULT 'uploading',
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
expires_at timestamptz NOT NULL DEFAULT now() + interval '1 day',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('file_upload_sessions');
|
|
CREATE INDEX IF NOT EXISTS idx_file_upload_sessions_scope_status
|
|
ON file_upload_sessions(tenant_id, project_id, status, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS local_import_jobs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
compute_node_id uuid REFERENCES compute_nodes(id) ON DELETE SET NULL,
|
|
import_type varchar(40) NOT NULL,
|
|
source_path text NOT NULL,
|
|
target_resource_id uuid,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
scan_result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
failure_reason text,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
SELECT touch_updated_at('local_import_jobs');
|
|
CREATE INDEX IF NOT EXISTS idx_local_import_jobs_scope_status
|
|
ON local_import_jobs(tenant_id, project_id, status, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS fine_tune_checkpoints (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
fine_tune_task_id uuid NOT NULL REFERENCES fine_tune_tasks(id) ON DELETE CASCADE,
|
|
storage_object_id uuid REFERENCES storage_objects(id) ON DELETE SET NULL,
|
|
checkpoint_name varchar(200) NOT NULL,
|
|
step integer,
|
|
metric_name varchar(80),
|
|
metric_value numeric(18,8),
|
|
is_best boolean NOT NULL DEFAULT false,
|
|
protected boolean NOT NULL DEFAULT false,
|
|
size_bytes bigint NOT NULL DEFAULT 0,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_checkpoints_task_step
|
|
ON fine_tune_checkpoints(fine_tune_task_id, step DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_checkpoints_best
|
|
ON fine_tune_checkpoints(fine_tune_task_id, is_best) WHERE is_best;
|
|
|
|
CREATE TABLE IF NOT EXISTS model_services (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
model_id uuid REFERENCES models(id) ON DELETE SET NULL,
|
|
trained_model_id uuid REFERENCES trained_models(id) ON DELETE SET NULL,
|
|
compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL,
|
|
name varchar(150) NOT NULL,
|
|
service_level service_level NOT NULL DEFAULT 'test',
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
gpu_device_id uuid REFERENCES gpu_devices(id) ON DELETE SET NULL,
|
|
port integer,
|
|
max_concurrency integer NOT NULL DEFAULT 1,
|
|
timeout_seconds integer NOT NULL DEFAULT 120,
|
|
max_context_tokens integer,
|
|
approval_id uuid REFERENCES approval_instances(id) ON DELETE SET NULL,
|
|
created_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
started_at timestamptz,
|
|
stopped_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
deleted_at timestamptz
|
|
);
|
|
SELECT touch_updated_at('model_services');
|
|
CREATE INDEX IF NOT EXISTS idx_model_services_scope_status
|
|
ON model_services(tenant_id, project_id, status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS cleanup_jobs (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id uuid REFERENCES tenants(id) ON DELETE CASCADE,
|
|
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
|
|
cleanup_type varchar(80) NOT NULL,
|
|
status task_status NOT NULL DEFAULT 'pending',
|
|
target jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
failure_reason text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
started_at timestamptz,
|
|
completed_at timestamptz
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_cleanup_jobs_status_created ON cleanup_jobs(status, created_at DESC);
|
|
|
|
-- User identity provider extension. First release uses local accounts;
|
|
-- OIDC/LDAP can be enabled later without changing resource ownership tables.
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_provider varchar(40) NOT NULL DEFAULT 'local';
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS external_id text;
|
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS default_tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_users_external_provider ON users(auth_provider, external_id);
|
|
CREATE INDEX IF NOT EXISTS idx_users_default_tenant ON users(default_tenant_id);
|
|
|
|
-- Add tenant/project/resource governance columns to existing resource tables.
|
|
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS storage_node_id uuid REFERENCES storage_nodes(id) ON DELETE SET NULL;
|
|
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS visibility varchar(40) NOT NULL DEFAULT 'project';
|
|
ALTER TABLE storage_objects ADD COLUMN IF NOT EXISTS deleted_at timestamptz;
|
|
CREATE INDEX IF NOT EXISTS idx_storage_objects_scope
|
|
ON storage_objects(tenant_id, project_id, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE models ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE models ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE models ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE models ADD COLUMN IF NOT EXISTS visibility varchar(40) NOT NULL DEFAULT 'project';
|
|
ALTER TABLE models ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
CREATE INDEX IF NOT EXISTS idx_models_scope_status
|
|
ON models(tenant_id, project_id, approval_status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS visibility varchar(40) NOT NULL DEFAULT 'project';
|
|
ALTER TABLE trained_models ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
CREATE INDEX IF NOT EXISTS idx_trained_models_scope
|
|
ON trained_models(tenant_id, project_id, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS visibility varchar(40) NOT NULL DEFAULT 'project';
|
|
ALTER TABLE datasets ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
CREATE INDEX IF NOT EXISTS idx_datasets_scope_status
|
|
ON datasets(tenant_id, project_id, type, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE dataset_files ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE dataset_files ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_dataset_files_scope
|
|
ON dataset_files(tenant_id, project_id, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE data_process_tasks ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_process_tasks ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_process_tasks ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_process_tasks ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
ALTER TABLE data_process_tasks ADD COLUMN IF NOT EXISTS compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_data_process_tasks_scope_status
|
|
ON data_process_tasks(tenant_id, project_id, status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL;
|
|
ALTER TABLE fine_tune_tasks ADD COLUMN IF NOT EXISTS resume_checkpoint_id uuid REFERENCES fine_tune_checkpoints(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_fine_tune_tasks_scope_status
|
|
ON fine_tune_tasks(tenant_id, project_id, status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE inference_tasks ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE inference_tasks ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE inference_tasks ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE inference_tasks ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
ALTER TABLE inference_tasks ADD COLUMN IF NOT EXISTS compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_inference_tasks_scope_status
|
|
ON inference_tasks(tenant_id, project_id, status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS approval_status approval_status NOT NULL DEFAULT 'not_required';
|
|
ALTER TABLE eval_tasks ADD COLUMN IF NOT EXISTS compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_eval_tasks_scope_status
|
|
ON eval_tasks(tenant_id, project_id, status, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE data_convert_jobs ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_convert_jobs ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_convert_jobs ADD COLUMN IF NOT EXISTS owner_id uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
ALTER TABLE data_convert_jobs ADD COLUMN IF NOT EXISTS compute_job_id uuid REFERENCES compute_jobs(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_data_convert_jobs_scope_status
|
|
ON data_convert_jobs(tenant_id, project_id, status, created_at DESC);
|
|
|
|
ALTER TABLE custom_tools ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE custom_tools ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_custom_tools_scope
|
|
ON custom_tools(tenant_id, project_id, visibility, created_at DESC) WHERE deleted_at IS NULL;
|
|
|
|
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS approval_id uuid REFERENCES approval_instances(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_scope_created
|
|
ON audit_logs(tenant_id, project_id, created_at DESC);
|
|
|
|
ALTER TABLE web_logs ADD COLUMN IF NOT EXISTS tenant_id uuid REFERENCES tenants(id) ON DELETE SET NULL;
|
|
ALTER TABLE web_logs ADD COLUMN IF NOT EXISTS project_id uuid REFERENCES projects(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_web_logs_scope_created
|
|
ON web_logs(tenant_id, project_id, created_at DESC);
|
|
|
|
CREATE OR REPLACE VIEW v_project_member_permissions AS
|
|
SELECT
|
|
p.tenant_id,
|
|
pm.project_id,
|
|
pm.user_id,
|
|
pm.role,
|
|
CASE pm.role
|
|
WHEN 'owner' THEN ARRAY['read','write','execute','download','delete','manage_acl']
|
|
WHEN 'maintainer' THEN ARRAY['read','write','execute','download','delete']
|
|
WHEN 'developer' THEN ARRAY['read','write','execute','download']
|
|
WHEN 'reviewer' THEN ARRAY['read','download']
|
|
ELSE ARRAY['read']
|
|
END AS permissions
|
|
FROM project_members pm
|
|
JOIN projects p ON p.id = pm.project_id
|
|
WHERE p.deleted_at IS NULL;
|