import type { Kysely } from "kysely"; import { dropTables, entityTable, execDdl, oneOf } from "../src/migration-helpers.ts"; // W9-D 规则组:收录策略的过滤条件 + 偏好标签合一(docs/design/subscriptions.md §6)。 // rule_group = 结构化层(每层一个 AND/OR/NOT 表达式,第 k 层 pri = 100 - k)+ 硬排除;规则串只是它的文本表示。 // rule_atom = 自定义条件(对齐 MP 自定义规则 7 字段,不允许覆盖内置关键字)。 // 偏好标签原样迁成规则组(同 id、同指纹,legacy_kind/legacy_rule 保留旧语义),旧端点改为别名;抑制记录挂到规则组。 // 版本目标 rule_group_ids 有序引用规则组;default_order 非空 = 该类(订阅/洗版/搜索)默认链的一员。 // biome-ignore lint/suspicious/noExplicitAny: 迁移不依赖应用表类型 export async function up(db: Kysely): Promise { await execDdl( db, ` ${entityTable( "rule_group", ` name text not null check (length(name) between 1 and 80), description text, kind text not null default 'acquisition' ${oneOf("kind", ["acquisition", "upgrade", "search"])}, scope jsonb not null default '{}'::jsonb, layers jsonb not null default '[]'::jsonb, hard_excludes jsonb not null default '[]'::jsonb, ladder_required boolean not null default true, rule_string text, source text not null default 'manual' ${oneOf("source", ["manual", "chat", "agent"])}, enabled boolean not null default true, revision integer not null default 1 check (revision >= 1), fingerprint text not null, evidence jsonb not null default '{}'::jsonb, default_order integer, auto_fill boolean, legacy_kind text check (legacy_kind is null or legacy_kind in ('soft', 'hard', 'version', 'target')), legacy_rule jsonb, suppressed_at timestamptz, deleted_at timestamptz`, )} create index rule_group_live_idx on rule_group (kind, default_order) where deleted_at is null; create index rule_group_fingerprint_idx on rule_group (fingerprint) where deleted_at is null; ${entityTable( "rule_atom", ` key text not null unique check (key ~ '^([A-Z][A-Z0-9]*|[0-9]+[A-Z][A-Z0-9]*)$'), name text, description text, include jsonb not null default '[]'::jsonb, exclude jsonb not null default '[]'::jsonb, match_fields jsonb not null default '[]'::jsonb, unit_size_mb jsonb, min_seeders integer check (min_seeders is null or min_seeders >= 0), publish_delay_minutes jsonb, free boolean not null default false`, )} alter table acquisition_slot add column rule_group_ids jsonb not null default '[]'::jsonb check (jsonb_typeof(rule_group_ids) = 'array'); alter table preference_suppression add column rule_group_id uuid references rule_group (id) on delete set null; create index preference_suppression_rule_group_idx on preference_suppression (rule_group_id) where rule_group_id is not null; insert into rule_group (id, name, description, kind, scope, layers, hard_excludes, ladder_required, source, enabled, revision, fingerprint, evidence, legacy_kind, legacy_rule, suppressed_at, deleted_at, created_at, updated_at) select id, label, description, 'acquisition', scope, '[]'::jsonb, '[]'::jsonb, kind <> 'soft', case origin when 'manual' then 'manual' when 'conversation' then 'chat' else 'agent' end, status = 'active', revision, fingerprint, evidence, kind, rule, case when status <> 'active' then updated_at end, case when status = 'deleted' then updated_at end, created_at, updated_at from preference_tag; update preference_suppression set rule_group_id = preference_tag_id where preference_tag_id is not null; insert into rule_group (name, description, kind, source, fingerprint, default_order) values ('订阅默认', '订阅没有指定规则组时使用', 'acquisition', 'manual', 'seed:acquisition', 0), ('洗版默认', '版本目标开启升级时额外叠加', 'upgrade', 'manual', 'seed:upgrade', 0), ('搜索默认', '手动搜索与 Agent 搜索资源时使用', 'search', 'manual', 'seed:search', 0); `, ); } // biome-ignore lint/suspicious/noExplicitAny: 同上 export async function down(db: Kysely): Promise { await execDdl( db, ` alter table preference_suppression drop column if exists rule_group_id; alter table acquisition_slot drop column if exists rule_group_ids; ${dropTables(["rule_atom", "rule_group"])} `, ); }