-- B03:场景切片派生缓存与向量代次索引——参考书原文的确定性切窗。 -- 原文以 muse_source_version 为权威;切片与索引是可全量重建的派生数据,禁原地改写。 DO $migration$ BEGIN -- 切片台账:来源版本的定位窗;窗计划哈希锁定切窗方案,同版本只认一个窗计划。 CREATE TABLE public.muse_source_slice ( slice_id uuid PRIMARY KEY, source_id uuid NOT NULL REFERENCES public.muse_source(source_id), revision bigint NOT NULL CHECK (revision > 0), 起点 int NOT NULL CHECK (起点 >= 0), 终点 int NOT NULL CHECK (终点 > 起点), 窗计划哈希 text NOT NULL, 检索文本 text NOT NULL, 检索文本哈希 text NOT NULL CHECK (检索文本哈希 ~ '^[0-9a-f]{64}$'), built_at timestamptz NOT NULL DEFAULT clock_timestamp() ); -- 同版本同窗计划内起点唯一;不同窗计划可并存,重建时整计划替换。 CREATE UNIQUE INDEX muse_source_slice_window ON public.muse_source_slice (source_id, revision, 窗计划哈希, 起点); CREATE INDEX muse_source_slice_by_source ON public.muse_source_slice (source_id, revision); -- 派生数据禁原地改写;删除仅允许全量重建事务成批进行。 CREATE FUNCTION public.muse_guard_source_slice() RETURNS trigger LANGUAGE plpgsql AS $guard$ BEGIN RAISE EXCEPTION '场景切片是派生数据,禁止原地改写;重建请整批删除后重算'; END; $guard$; CREATE TRIGGER muse_source_slice_guard BEFORE UPDATE ON public.muse_source_slice FOR EACH ROW EXECUTE FUNCTION public.muse_guard_source_slice(); -- 切片向量索引:与 muse_method_index 同构(哈希 bigram,内存余弦)。 CREATE TABLE public.muse_source_slice_index ( slice_id uuid PRIMARY KEY REFERENCES public.muse_source_slice(slice_id) ON DELETE CASCADE, 检索文本 text NOT NULL, 检索文本哈希 text NOT NULL, vector double precision[] NOT NULL CHECK (array_length(vector, 1) > 0), embedder text NOT NULL, built_at timestamptz NOT NULL DEFAULT clock_timestamp() ); -- 代次台账:当前有效索引身份;检索时对不上即拒绝为陈旧。 CREATE TABLE public.muse_source_slice_index_version ( ledger_id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY, 索引身份 text NOT NULL, embedder text NOT NULL, slice_count int NOT NULL CHECK (slice_count >= 0), built_at timestamptz NOT NULL DEFAULT clock_timestamp(), active boolean NOT NULL DEFAULT false ); CREATE UNIQUE INDEX muse_source_slice_index_active ON public.muse_source_slice_index_version (active) WHERE active; -- 代次守卫:切片与索引的事务变更递增代次,检索按代次判新鲜(仿 V0059)。 CREATE TABLE public.muse_source_slice_guard ( singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton), generation bigint NOT NULL DEFAULT 0 CHECK (generation >= 0) ); INSERT INTO public.muse_source_slice_guard (singleton, generation) VALUES (true, 0); CREATE FUNCTION public.muse_source_slice_changed() RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog AS $guard$ BEGIN UPDATE public.muse_source_slice_guard SET generation = generation + 1 WHERE singleton; IF NOT FOUND THEN RAISE EXCEPTION 'source slice mutation guard is missing'; END IF; RETURN NULL; END; $guard$; CREATE TRIGGER muse_source_slice_changed AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON public.muse_source_slice FOR EACH STATEMENT EXECUTE FUNCTION public.muse_source_slice_changed(); CREATE TRIGGER muse_source_slice_index_changed AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON public.muse_source_slice_index FOR EACH STATEMENT EXECUTE FUNCTION public.muse_source_slice_changed(); CREATE TRIGGER muse_source_slice_head_changed AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON public.muse_source_slice_index_version FOR EACH STATEMENT EXECUTE FUNCTION public.muse_source_slice_changed(); REVOKE ALL ON public.muse_source_slice_guard FROM PUBLIC, muse_app, muse_eval; GRANT SELECT ON public.muse_source_slice_guard TO muse_app, muse_eval; REVOKE ALL ON FUNCTION public.muse_source_slice_changed() FROM PUBLIC; END $migration$;