muse-agent-example/db/ddl/100-example规划与冻结.sql
zizi 96aa1e61a3 框架(数据库+读取器): 落库 100 规划+冻结表,建冻结落库模块
- example_planning_section(规划/细纲/设定,书级/章级双部分唯一索引,shadow→confirmed)
  + example_context_freeze(冻结清单,append-only,manifestSha256 幂等)
- persist_context_freeze.py:assemble_context 之后由调用方落冻结(manifest/context哈希+纳入来源+省略项+预算),
  组装器保持不碰库(合同如此);dry-run 已验证
- read-context SKILL.md 增冻结落库挂点说明
2026-07-31 22:29:55 +08:00

91 lines
5.6 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 规划与上下文冻结两表: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();