-- B03:外部与私人资料、原文版本、清理候选与榜单快照。 -- 原文只追加不可改写;清理是对照产物;榜单快照把时点观察与效果推断分开(无 run 归因字段)。 DO $migration$ BEGIN -- 来源:同源再导入落到版本链;kind 区分作者稿、参考书、网页、笔记与灵感。 CREATE TABLE public.muse_source ( source_id uuid PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('author_draft','reference','web','note','inspiration')), title text NOT NULL, origin text NOT NULL, authorized_uses jsonb NOT NULL DEFAULT '[]'::jsonb, created_at timestamptz NOT NULL DEFAULT clock_timestamp(), UNIQUE (origin, kind), CHECK (jsonb_typeof(authorized_uses) = 'array') ); -- 原文版本:只追加;内容哈希用于证据校验与重复辨认。 CREATE TABLE public.muse_source_version ( source_id uuid NOT NULL REFERENCES public.muse_source(source_id), revision bigint NOT NULL CHECK (revision > 0), content_hash text NOT NULL, content text NOT NULL, import_result jsonb NOT NULL, imported_at timestamptz NOT NULL DEFAULT clock_timestamp(), PRIMARY KEY (source_id, revision), CHECK (jsonb_typeof(import_result) = 'object') ); CREATE INDEX ON public.muse_source_version (source_id, imported_at); -- 清理候选:对照产物,原文不受影响;删除项可定位、可回退。 CREATE TABLE public.muse_clean_candidate ( candidate_id uuid PRIMARY KEY, source_id uuid NOT NULL REFERENCES public.muse_source(source_id), revision bigint NOT NULL, removals jsonb NOT NULL, status text NOT NULL CHECK (status IN ('proposed','applied','rejected')), cleaned_hash text, cleaned_text text, created_at timestamptz NOT NULL DEFAULT clock_timestamp(), UNIQUE (source_id, revision, candidate_id), CHECK (jsonb_typeof(removals) = 'array'), CHECK ((status = 'applied') = (cleaned_hash IS NOT NULL AND cleaned_text IS NOT NULL)), FOREIGN KEY (source_id, revision) REFERENCES public.muse_source_version(source_id, revision) ); -- 榜单来源登记:具名榜种与采集方式。 CREATE TABLE public.muse_ranking_source ( ranking_id text PRIMARY KEY, name text NOT NULL, url text NOT NULL, 采集方式 text NOT NULL ); -- 榜单快照:时刻观察;失败保留失败态;不携带任何 run 归因。 CREATE TABLE public.muse_ranking_snapshot ( snapshot_id uuid PRIMARY KEY, ranking_id text NOT NULL REFERENCES public.muse_ranking_source(ranking_id), captured_at timestamptz NOT NULL DEFAULT clock_timestamp(), status text NOT NULL CHECK (status IN ('ok','failed')), failure_reason text, items jsonb, CHECK ((status = 'ok') = (items IS NOT NULL)), CHECK (items IS NULL OR jsonb_typeof(items) = 'array') ); CREATE INDEX ON public.muse_ranking_snapshot (ranking_id, captured_at DESC); END $migration$;