Files
UHD_Media_Tool/packages/db
rootandClaude Fable 5.1 cc8b3a7df4 feat(db,contracts): full v0.1 data model migrations and shared zod contracts (W1-1)
- migrations 0002-0012 split by domain (foundation/instances/content/identity/
  sources/assets/policy/execution/agent/ops/organization): 41 tables, uuid pk,
  created_at/updated_at + touch trigger, text+CHECK status sets, pgcrypto/pg_trgm
- key constraints: source_release(site_id, site_release_id), observation(feed_id, guid),
  external_identity accepted partial unique, download_task(instance_id, torrent_hash),
  one active acquisition_plan per slot, one active path_binding per entity/season,
  asset_location root+path uniqueness, organization_record change_seq/change_xid cursor
- secret table (ciphertext/nonce/kdf) + secret_ref columns; outbox dispatch fields (ADR-002)
- src/types.ts generated from the migrated schema (scripts/gen-types.ts), drift-checked in tests;
  int8 -> number, date -> string parsers; migrator accepts migrationTableSchema
- contracts: Row/Create/Update schemas per domain, public organization-record API
  (changeSeq cursor), GlobalSettings, CapabilityDescriptor + 14 capability IO schemas,
  typed SSE events, Api endpoint table
- tests: migration up/down round trip on real PG in an isolated schema, constraint checks,
  contracts<->db column/enum sync, positive/negative parse for every exported schema

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-27 18:11:37 +08:00
..

@umt/db

PostgreSQL 访问层:Kysely 客户端与连接池、表类型(src/types.ts)、迁移文件(migrations/)与迁移器。表名 snake_case,与 REQ §12 实体名一致。

规则

  • 迁移是唯一事实来源。所有 schema 变更只能新增迁移,已发布迁移不得修改。迁移用原生 DDL 书写(src/migration-helpers.ts:entityTable() 统一加 uuid 主键 gen_random_uuid()、created_at/updated_at 与 updated_at 触发器;oneOf() 生成状态 CHECK)。migrations/ 内相对导入必须带 .ts 扩展名(Kysely 用原生 import() 加载)。
  • src/types.ts 是生成物:pnpm db:migrate && pnpm --filter @umt/db gen:types。CHECK 枚举自动变成字面量联合(如 AcquisitionSlotStatus)。test/schema.test.ts 会在隔离 schema 里重新反射并比对,漂移即失败;packages/contracts/test/db-sync.test.ts 再校验 contracts 的 Row schema 与枚举和这里一致。
  • 状态列一律 text + CHECK,不用 PG enum(便于迁移加值)。JSONB 只放证据、规则参数、原文快照与演进字段;稳定查询字段用关系列。
  • 凭证只存 secret_ref(→ secret.id),明文不入库、不进日志。
  • createDb() 把 int8 解析为 number(超出 2^53 抛错)、date 保持 YYYY-MM-DD 字符串。写 jsonb 数组时须先 JSON.stringify(pg 会把 JS 数组当 PG 数组)。
  • 测试对真实库(默认 postgres://umt:umt@localhost:55433/umt)在临时 schema 中跑迁移往返;无库环境设 UMT_SKIP_DB_TESTS=1。在非 public schema 迁移时要传 createMigrator(db, { migrationTableSchema })。

迁移清单

文件 内容
0001_init settings、api_key、audit_event、outbox_event
0002_foundation pgcrypto / pg_trgm、umt_touch_updated_at()、secret;outbox 投递字段(ADR-002);api_key.updated_at
0003_instances library_root、downloader_instance、media_server_instance、path_mapping
0004_content media_entity、content_unit、title_alias
0005_identity external_identity、identity_revision
0006_sources site、site_account、feed_source、source_release、observation
0007_assets media_asset、asset_unit_coverage、asset_location、path_binding
0008_policy policy_revision、subscription、acquisition_slot、preference_tag、preference_suppression
0009_execution task、acquisition_plan、download_task、asset_operation;回填 media_asset.download_task_id、acquisition_slot.reserved_plan_id
0010_agent agent_run、artifact、decision、approval、automation_grant
0011_ops reconcile_run、server_mapping
0012_organization organization_record(change_seq 序列 + 触发器)、organization_file_result;回填 asset_operation.organization_record_id

表清单(41 张)

底座 / 运维

表 用途与关键不变量
settings 全局设置 key→jsonb;key 为 GlobalSettings 的分组名(automation/cache/organize/retention/pi/budget)
api_key API Key(只存 hash);scope ⊂ read/organize.read/organize.execute/admin,可撤销
audit_event 审计日志,只追加
outbox_event 业务事务内写入的事件;分发器扫 status='pending' and next_attempt_at<=now(),idempotency_key 唯一,作为引擎入队幂等键
secret 加密凭证(ciphertext/nonce/auth_tag/kdf/key_version);其他表只存 secret_ref
reconcile_run 五类校验器的运行记录,resume_cursor 支持中断续扫;mode=read_only 用于恢复后对账
server_mapping Emby 兼容库存条目 ↔ 本地对象;(media_server_instance_id, server_item_id) 唯一;未知 ≠ 缺失,不构成纳管授权

实例

表 用途与关键不变量
library_root 媒体库根(执行进程可访问路径);management_mode register_only/managed,只登记范围不被全局规则隐式升级为自动下载
downloader_instance qB 实例;managed_tag 管理标签;cache_volume_key 聚合共用缓存卷的水位
media_server_instance Emby/Jellyfin 实例
path_mapping 下载器/媒体服务器路径前缀 → 本地路径;同实例同 remote_prefix 唯一;view_kind 与实例列一致(CHECK)

内容 / 身份

表 用途与关键不变量
media_entity 本地作品,UUID 为主身份;合并时 status=merged 且 merged_into_id 指向目标(旧 ID 跳转);field_locks 人工锁字段刷新不得覆盖
content_unit 季/集/特别篇/分段;编号(season/episode/source_issue/air_date)可修订,不是唯一身份;numbering_locked 人工锁
title_alias 原名/译名/简称/站点原标题;normalized 上建 gin trgm 索引做模糊召回
external_identity 本地对象 ↔ 外部 ID;(provider, entity_type, external_id) 对 accepted 部分唯一,同一本地对象每个 provider+type 只有一个 accepted;候选(proposed/conflicted)可并存;404/超时只记 verify_status
identity_revision 身份每次变更的修订历史,(external_identity_id, revision) 唯一,只追加

来源

表 用途与关键不变量
site 站点(adapter/限流/熔断/快照游标);关闭站点即停止网络访问与新候选
site_account 站点账号,凭证走 secret_ref
feed_source RSS/列表/查询入口;真实 URL(含 passkey)只在 url_secret_ref,对外只给 url_display;按 next_fetch_at 调度
source_release 站点资源;(site_id, site_release_id) 唯一,多 Feed 观察汇到同一行;source_published_at/first_seen_at 分开
observation 一次原始观察;(feed_id, guid) 唯一,content_digest 变化即条目更新

文件

表 用途与关键不变量
media_asset 文件资产(与位置分离);附属文件 parent_asset_id 指向主视频;内容版本 content_edition 与交付特征 delivery 分开
asset_unit_coverage 资产覆盖哪些内容单元(合集/分段),(media_asset_id, content_unit_id) 唯一
asset_location 资产的一个位置;同一库根 + 相对路径在非 removed 状态下唯一;状态 available/unavailable/suspected_missing/confirmed_missing/removed
path_binding 作品/季目录绑定;一旦建立不因元数据刷新改变,只有显式整理可替换(旧行标 superseded);同作品同季只有一个 active,同一路径只绑定一个 active;历史双目录用 conflict

规则

表 用途与关键不变量
policy_revision 策略版本(草案/发布/回滚);(policy_key, revision) 唯一,每个 policy_key 只有一个 published
subscription 追踪目标(单次/持续,仅本季/后续季,优先级);作品未建档时用 title_query
acquisition_slot 独立收录目标;状态 empty/reserved/downloading/verifying/satisfied/upgrade_pending/attention;(media_entity_id, content_unit_id, name) 唯一(NULLS NOT DISTINCT)
preference_tag 前端可编辑偏好标签;fingerprint 在未删除标签中唯一
preference_suppression 取消标签的抑制记录;active 时 Agent 不得据同一 fingerprint 自动加回

执行

表 用途与关键不变量
task 业务工作单/时间线投影(引擎只管推进);(kind, idempotency_key) 唯一;按 (status, next_run_at) 调度
acquisition_plan 下载计划 + Slot/预算预留;每个 Slot 同时只能有一个 reserved/submitted 计划(部分唯一);idempotency_key 唯一
download_task 下载器任务映射;(instance_id, torrent_hash) 唯一;pause_reason 区分用户/阈值暂停;next_check_at 对账调度
asset_operation 副作用账本;operation_id 唯一;attempt_token 做 fencing

人工 / AI

表 用途与关键不变量
agent_run Agent 运行、预算/用量、会话状态、事件序号
artifact 证据/计划/提案产物;提案不直接晋升为事实
decision 待处理中心条目;同一 dedupe_key 同时只有一个未关闭条目
approval 单次审批,绑定 plan + payload_hash + expected_revision
automation_grant 持续授权(动作 + 范围 + 预算 + 策略版本),可撤销

整理

表 用途与关键不变量
organization_record 整理批次(公开整理记录 API 的来源);change_seq 在任何 update(含文件明细变化)时取新序列值并唯一,change_xid 记录变更事务号
organization_file_result 逐文件前后路径与结果;增删改会刷新父记录 change_seq

游标读取:where change_seq > $after and change_xid < pg_snapshot_xmin(pg_current_snapshot()) order by change_seq limit $n。第二个条件排除仍在进行中的事务,避免"序号小但提交晚"被跳过;返回的最大 change_seq 即 nextCursor。