-- 规划与上下文冻结两表:example_planning_section(规划/细纲/设定)+ example_context_freeze(冻结清单)。 -- 设计拍板 2026-07-30(docs/2026-07-30-落库与看板设计.md §2.6)。 -- 背景:今天规划/细纲在文件与 gitignore 临时目录(planner 直接写文件),冻结 manifest 是仓外临时 JSON 算完即弃; -- 主仓 muse_content_planning_section 暂缓未建。两表新建,补齐"已确认细纲/冻结上下文"的持久化缺口。 -- planning_section 可变(shadow→confirmed,带公共列+updated_at);context_freeze append-only(冻结一旦生成不可改)。 -- 全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。 -- ══ example_planning_section:规划/细纲/设定(可变;书级 target_chapter 为空,章级填章号)══ CREATE TABLE IF NOT EXISTS example_planning_section ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, work_id BIGINT NOT NULL, -- → muse_content_work.id(软引用) target_chapter INTEGER, -- 章级(fine_outline)填章号;书级(setting/outline/state/assembly)为空 section_type VARCHAR(32) NOT NULL, -- setting/outline/state/assembly/fine_outline schema_type VARCHAR(32), -- 对应 meta/schemas 型(work_core/outline/narrative_state…) version INTEGER NOT NULL DEFAULT 1, -- 规划版本 payload JSONB NOT NULL, -- 结构化内容(frontmatter+正文的结构化形态) state VARCHAR(20) NOT NULL DEFAULT 'shadow', -- shadow/confirmed(用户确认后翻 confirmed) confirmed_at TIMESTAMP, -- 确认时间 creator VARCHAR(64) NOT NULL DEFAULT '', create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updater VARCHAR(64) NOT NULL DEFAULT '', update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted BOOLEAN NOT NULL DEFAULT FALSE, tenant_id BIGINT NOT NULL DEFAULT 0, CONSTRAINT chk_example_planning_section_type CHECK (section_type IN ('setting','outline','state','assembly','fine_outline')), CONSTRAINT chk_example_planning_section_state CHECK (state IN ('shadow','confirmed')) ); -- 唯一键用两个部分唯一索引(仿 V26 partial unique,因 target_chapter 可空) CREATE UNIQUE INDEX IF NOT EXISTS uk_example_planning_section_book ON example_planning_section(tenant_id, work_id, section_type, version) WHERE target_chapter IS NULL; CREATE UNIQUE INDEX IF NOT EXISTS uk_example_planning_section_chapter ON example_planning_section(tenant_id, work_id, section_type, target_chapter, version) WHERE target_chapter IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_example_planning_section_work ON example_planning_section(tenant_id, work_id, section_type, state); CREATE OR REPLACE TRIGGER trg_example_planning_section_updated_at BEFORE UPDATE ON example_planning_section FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ══ example_context_freeze:冻结清单(append-only;冻结一旦生成不可改)══ CREATE TABLE IF NOT EXISTS example_context_freeze ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, work_id BIGINT, -- → muse_content_work.id(软引用) target_chapter INTEGER, -- 目标章 as_of_chapter INTEGER NOT NULL, -- 冻结上界(严格 <= asOf) manifest_sha256 CHAR(64) NOT NULL, -- manifest 自身哈希(唯一) context_sha256 CHAR(64), -- WriterContext 回执哈希 = retrieval_identity(context);回执表连这一列 reference_work_id BIGINT, -- 参考作品 id(回放用) reference_version VARCHAR(64), -- 参考版本 arm_config JSONB, -- 实验臂配置(A/B/C) sections JSONB, -- 每节 sourceId/sourceVersion/sha256/charCount token_budget INTEGER, -- 上下文预算 omitted_fields JSONB, -- 字段级裁剪省略项 omitted_sources JSONB, -- 来源级省略项 creator VARCHAR(64) NOT NULL DEFAULT '', create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, tenant_id BIGINT NOT NULL DEFAULT 0, CONSTRAINT uk_example_context_freeze_manifest UNIQUE (manifest_sha256), CONSTRAINT chk_example_context_freeze_manifest_sha256 CHECK (manifest_sha256 ~ '^[0-9a-f]{64}$'), CONSTRAINT chk_example_context_freeze_context_sha256 CHECK (context_sha256 IS NULL OR context_sha256 ~ '^[0-9a-f]{64}$') ); CREATE INDEX IF NOT EXISTS idx_example_context_freeze_context ON example_context_freeze(context_sha256) WHERE context_sha256 IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_example_context_freeze_work ON example_context_freeze(work_id, target_chapter); CREATE OR REPLACE FUNCTION example_reject_truncate() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION '% 是 append-only 表,禁止 TRUNCATE', TG_TABLE_NAME; END; $$; CREATE OR REPLACE FUNCTION reject_example_context_freeze_mutation() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'example_context_freeze 是 append-only 表,禁止 UPDATE/DELETE'; END; $$; CREATE OR REPLACE TRIGGER trg_example_context_freeze_append_only BEFORE UPDATE OR DELETE ON example_context_freeze FOR EACH ROW EXECUTE FUNCTION reject_example_context_freeze_mutation(); CREATE OR REPLACE TRIGGER trg_example_context_freeze_no_truncate BEFORE TRUNCATE ON example_context_freeze FOR EACH STATEMENT EXECUTE FUNCTION example_reject_truncate();