- 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 增冻结落库挂点说明
91 lines
5.6 KiB
PL/PgSQL
91 lines
5.6 KiB
PL/PgSQL
-- 规划与上下文冻结两表: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();
|