-- 整库版本与作业:把"测试封存 / 生产生效 / 生产预备份"三种版本与持久作业分开记录。 -- 现有 jobs 表是队列与租约;这里记录业务身份、阶段与结果,重启后仍可读。 CREATE TABLE library_versions ( id VARCHAR(64) PRIMARY KEY, scope VARCHAR(16) NOT NULL CHECK (scope IN ('internal','external')), kind VARCHAR(24) NOT NULL CHECK (kind IN ('test_sealed','production_effective','production_prebackup')), snapshot_id VARCHAR(64) NOT NULL DEFAULT '', version_label VARCHAR(64) NOT NULL DEFAULT '', source_job_id VARCHAR(64) NOT NULL DEFAULT '', evidence JSONB NOT NULL DEFAULT '{}'::jsonb, created_by VARCHAR(128) NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL, superseded_at TIMESTAMPTZ ); CREATE INDEX library_versions_scope_kind ON library_versions(scope, kind, created_at DESC, id DESC); -- 每个目标只有一个"当前生产生效版本";写入新版本时旧的必须先被 supersede。 CREATE UNIQUE INDEX library_versions_effective ON library_versions(scope) WHERE kind = 'production_effective' AND superseded_at IS NULL; CREATE TABLE library_jobs ( id VARCHAR(64) PRIMARY KEY, scope VARCHAR(16) NOT NULL CHECK (scope IN ('internal','external')), kind VARCHAR(16) NOT NULL CHECK (kind IN ('seal','sync')), state VARCHAR(16) NOT NULL CHECK (state IN ('pending','running','succeeded','failed','cancelled')), stage VARCHAR(32) NOT NULL DEFAULT '', idempotency_key VARCHAR(255) NOT NULL, request_digest VARCHAR(64) NOT NULL, request JSONB NOT NULL DEFAULT '{}'::jsonb, queue_job_id VARCHAR(64) NOT NULL DEFAULT '', snapshot_id VARCHAR(64) NOT NULL DEFAULT '', target_snapshot_id VARCHAR(64) NOT NULL DEFAULT '', actor VARCHAR(128) NOT NULL DEFAULT '', error TEXT NOT NULL DEFAULT '', recovery VARCHAR(255) NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL, finished_at TIMESTAMPTZ ); CREATE UNIQUE INDEX library_jobs_idempotency ON library_jobs(idempotency_key); -- 同一目标同一类作业只能有一个在飞:互斥由数据库保证,不依赖前端或单 worker 部署。 CREATE UNIQUE INDEX library_jobs_active ON library_jobs(scope, kind) WHERE state IN ('pending','running'); CREATE INDEX library_jobs_recent ON library_jobs(scope, created_at DESC, id DESC); CREATE TABLE library_job_stages ( job_id VARCHAR(64) NOT NULL REFERENCES library_jobs(id) ON DELETE CASCADE, stage VARCHAR(32) NOT NULL, state VARCHAR(16) NOT NULL DEFAULT 'pending' CHECK (state IN ('pending','running','succeeded','failed','skipped')), -- NULL 表示"总量未知":绝不能当成 0,也不据此算百分比。 total BIGINT, completed BIGINT, bytes_total BIGINT, bytes_done BIGINT, detail TEXT NOT NULL DEFAULT '', updated_at TIMESTAMPTZ NOT NULL, PRIMARY KEY (job_id, stage) );