框架(数据库+读取器): 落库 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 增冻结落库挂点说明
This commit is contained in:
parent
bf6cbc02b1
commit
96aa1e61a3
@ -23,8 +23,9 @@ scenario 映射功能链分类:生成类为 `generation`,抽取类为 `extra
|
|||||||
3. **按指针回读原文**:在 `REPEATABLE READ READ ONLY` 事务中按 `sourceVersion/sourceRefs/stateAsOf` 回读 Canonical block。缺少可验证指针的内容只能作为未验证索引提示,不能进入事实证据。
|
3. **按指针回读原文**:在 `REPEATABLE READ READ ONLY` 事务中按 `sourceVersion/sourceRefs/stateAsOf` 回读 Canonical block。缺少可验证指针的内容只能作为未验证索引提示,不能进入事实证据。
|
||||||
4. **双证据组装**:连续前四章全文构成近期基线;卡指针召回的历史正文作为补充并按不可变来源去重。`factEvidence` 只接受冻结历史原文、正式设定、Canonical 状态或细纲明确声明的新事实,并保留各自权威引用。
|
4. **双证据组装**:连续前四章全文构成近期基线;卡指针召回的历史正文作为补充并按不可变来源去重。`factEvidence` 只接受冻结历史原文、正式设定、Canonical 状态或细纲明确声明的新事实,并保留各自权威引用。
|
||||||
5. **确定性冻结**:按 `score DESC, sourceVersion ASC, sourceId ASC, sourceOffset ASC` 排序,生成完整 `WriterContext v1`、RetrievalManifest、来源省略原因和上下文 hash。预算裁剪必须可审计,不能静默丢失硬约束或连续正文基线。
|
5. **确定性冻结**:按 `score DESC, sourceVersion ASC, sourceId ASC, sourceOffset ASC` 排序,生成完整 `WriterContext v1`、RetrievalManifest、来源省略原因和上下文 hash。预算裁剪必须可审计,不能静默丢失硬约束或连续正文基线。
|
||||||
|
6. **冻结落库(assemble 之后,调用方负责)**:`assemble_context()` 算出冻结后,调用方(生产编排 / 回放编排)调 `scripts/persist_context_freeze.py`,把冻结清单(manifestSha256/contextSha256/纳入来源/省略项/预算)落一行 `example_context_freeze`(append-only,manifestSha256 幂等)。组装器本身不碰库(合同如此),落库是调用方的事;回执表的 `context_sha256` 连本表 `context_sha256` 列。
|
||||||
|
|
||||||
对应机械实现:`scripts/writer_contract.py`、`scripts/retrieve_writer_sources.py`、`scripts/assemble_writer_context.py`。其中 `retrieve_writer_sources.py` 的冻结来源校验、内容泄漏审计与冻结原文只读装载复用 **snapshot** skill(`build_snapshot.py`/`check_snapshot.py`/`audit_leakage.py`/`load_reference_work.py`),单向向下依赖,不复制 SQL 或第二套权限语义。
|
对应机械实现:`scripts/writer_contract.py`、`scripts/retrieve_writer_sources.py`、`scripts/assemble_writer_context.py`、`scripts/persist_context_freeze.py`(冻结落库)。其中 `retrieve_writer_sources.py` 的冻结来源校验、内容泄漏审计与冻结原文只读装载复用 **snapshot** skill(`build_snapshot.py`/`check_snapshot.py`/`audit_leakage.py`/`load_reference_work.py`),单向向下依赖,不复制 SQL 或第二套权限语义。
|
||||||
|
|
||||||
## 四层稳定语义
|
## 四层稳定语义
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ scenario 映射功能链分类:生成类为 `generation`,抽取类为 `extra
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| writer | `WriterCreativeInput v2`:fineOutline、narrativeState、factConstraints、proseExcerpts、patternReferences、lengthContract、styleConstraints | runId、权限、manifest、hash、候选版本、实验臂、oracle、评审结论、检索工具 |
|
| writer | `WriterCreativeInput v2`:fineOutline、narrativeState、factConstraints、proseExcerpts、patternReferences、lengthContract、styleConstraints | runId、权限、manifest、hash、候选版本、实验臂、oracle、评审结论、检索工具 |
|
||||||
| semantic detector | `semantic-detector-input-v3` 的模型投影:当前候选、细纲/硬约束、事实证据、历史原文证据、asOf | 真实实验臂、raw 路径、oracle、其他候选/评委、输入与候选 hash、receipt |
|
| semantic detector | `semantic-detector-input-v3` 的模型投影:当前候选、细纲/硬约束、事实证据、历史原文证据、asOf | 真实实验臂、raw 路径、oracle、其他候选/评委、输入与候选 hash、receipt |
|
||||||
| blind judge | `writer-blind-input-v3` 的模型投影:匿名候选正文、共同细纲、oracle 断言、rubric | 真实 A/B/C、卡注入、证据策略、候选 hash、reviewer 身份、raw、其他评委结果 |
|
| blind judge | `writer-blind-input-v4` 的模型投影:匿名候选正文、共同细纲、oracle 断言、通用指标与当前场景评分策略 | 真实 A/B/C、卡注入、证据策略、候选 hash、reviewer 身份、raw、其他评委结果 |
|
||||||
| planner | 冻结规划视图;只在 planning 授权下读取全局方向和未来规划 | 未授权原文、目标章之后的参考事实、评测答案 |
|
| planner | 冻结规划视图;只在 planning 授权下读取全局方向和未来规划 | 未授权原文、目标章之后的参考事实、评测答案 |
|
||||||
| extractor | 待处理原文、schema 合同和判重所需的既有知识 | 范式参考、未来内容、无来源推断 |
|
| extractor | 待处理原文、schema 合同和判重所需的既有知识 | 范式参考、未来内容、无来源推断 |
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ writer 的 `factConstraints=[]` 在冻结读取确实没有可确认事实时合
|
|||||||
|
|
||||||
- writer 只返回 `WriterDraft v2` 的 `candidateBody`;adapter 生成 CandidateEnvelope v2,并绑定运行身份、候选版本、正文 hash 与上下文 hash。
|
- writer 只返回 `WriterDraft v2` 的 `candidateBody`;adapter 生成 CandidateEnvelope v2,并绑定运行身份、候选版本、正文 hash 与上下文 hash。
|
||||||
- semantic detector 和 blind judge 只返回各自模型草稿;字符 offset、输入/报告 hash、模型回执和最终状态均由 adapter 计算。
|
- semantic detector 和 blind judge 只返回各自模型草稿;字符 offset、输入/报告 hash、模型回执和最终状态均由 adapter 计算。
|
||||||
- A/C 单变量回执比较 `build_writer_creative_input()` 的结果,只允许预注册证据策略导致 `factConstraints` 与 `proseExcerpts` 差异;细纲、叙事状态、篇幅、风格和公共参考必须一致。
|
- A/C 单变量回执比较 `build_writer_creative_input()` 的结果,只允许预注册卡处理与证据策略导致 `factConstraints`、`proseExcerpts` 与 `patternReferences` 差异;A 的 `patternReferences` 恒空,B/C 使用同一组冻结公共范式卡,细纲、叙事状态、篇幅和风格必须一致。
|
||||||
|
|
||||||
## 回显与存储
|
## 回显与存储
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""上下文冻结落库 —— 在 assemble_context() 之后把冻结清单落一行 example_context_freeze。
|
||||||
|
|
||||||
|
落库设计 §2.6 的落点:assemble_writer_context.assemble_context() 是纯函数不碰库(合同如此),
|
||||||
|
本模块由调用方(生产编排 / 回放编排)在 assemble 之后调用,算出的 contextSha256/manifest
|
||||||
|
不再"即弃",落一行冻结记录(append-only)。组装器保持 DB-free。
|
||||||
|
|
||||||
|
哈希归一:assemble 产出的哈希带 "sha256:" 前缀,入库剥成裸 64 hex(列 CHECK 要求)。
|
||||||
|
--dry-run 试跑:插入后回滚,校验但不落库。
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 复用 db skill 锁死的 DSN(.claude/skills/db/scripts)
|
||||||
|
DB_SCRIPTS = Path(__file__).resolve().parents[2] / "db" / "scripts"
|
||||||
|
sys.path.insert(0, str(DB_SCRIPTS))
|
||||||
|
from db import connect # noqa: E402
|
||||||
|
|
||||||
|
CREATOR = "read-context"
|
||||||
|
|
||||||
|
|
||||||
|
def _bare_sha(h):
|
||||||
|
"""剥 'sha256:' 前缀成裸 64 hex(列 CHECK 要求 ^[0-9a-f]{64}$)。"""
|
||||||
|
if not h:
|
||||||
|
return None
|
||||||
|
return h[len("sha256:"):] if str(h).startswith("sha256:") else str(h)
|
||||||
|
|
||||||
|
|
||||||
|
def persist_freeze(assemble_result, *, reference_work_id=None, reference_version=None,
|
||||||
|
arm_config=None, dry_run=False):
|
||||||
|
"""把 assemble_context 的返回落一行 example_context_freeze。返回 {status, id, manifest_sha256, context_sha256}。"""
|
||||||
|
ctx = assemble_result["context"]
|
||||||
|
manifest_sha = _bare_sha(ctx["contextSnapshot"]["manifestId"])
|
||||||
|
context_sha = _bare_sha(ctx["contextSnapshot"]["contextSha256"])
|
||||||
|
sections = ctx["retrievalManifest"].get("sources", [])
|
||||||
|
omitted = ctx.get("omittedSources", [])
|
||||||
|
used_chars = ctx.get("tokenBudget", {}).get("usedContextChars")
|
||||||
|
work_id = ctx.get("workId")
|
||||||
|
target_chapter = ctx.get("targetChapter")
|
||||||
|
as_of = ctx.get("asOf")
|
||||||
|
if as_of is None:
|
||||||
|
raise ValueError("assemble 结果缺 asOf,无法落冻结")
|
||||||
|
with connect() as conn:
|
||||||
|
try:
|
||||||
|
# manifest_sha256 唯一:同一冻结重放幂等(ON CONFLICT 返回既有行)
|
||||||
|
row = conn.execute(
|
||||||
|
"INSERT INTO example_context_freeze(work_id, target_chapter, as_of_chapter, manifest_sha256, "
|
||||||
|
"context_sha256, reference_work_id, reference_version, arm_config, sections, token_budget, "
|
||||||
|
"omitted_sources, creator) "
|
||||||
|
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s::jsonb,%s::jsonb,%s,%s::jsonb,%s) "
|
||||||
|
"ON CONFLICT (manifest_sha256) DO UPDATE SET creator=EXCLUDED.creator "
|
||||||
|
"RETURNING id, manifest_sha256, context_sha256",
|
||||||
|
(work_id, target_chapter, as_of, manifest_sha, context_sha, reference_work_id,
|
||||||
|
reference_version, json.dumps(arm_config, ensure_ascii=False) if arm_config is not None else None,
|
||||||
|
json.dumps(sections, ensure_ascii=False), used_chars,
|
||||||
|
json.dumps(omitted, ensure_ascii=False), CREATOR)).fetchone()
|
||||||
|
if dry_run:
|
||||||
|
conn.rollback()
|
||||||
|
return {"status": "dry_run_ok", "id": row[0], "manifest_sha256": row[1],
|
||||||
|
"context_sha256": row[2], "note": "试跑已回滚,未落库"}
|
||||||
|
conn.commit()
|
||||||
|
return {"status": "frozen", "id": row[0], "manifest_sha256": row[1], "context_sha256": row[2]}
|
||||||
|
except Exception:
|
||||||
|
conn.rollback()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
ap = argparse.ArgumentParser(description="上下文冻结落库(assemble_context 之后调用)")
|
||||||
|
ap.add_argument("result_json", nargs="?", default="-",
|
||||||
|
help="assemble_context 返回的 JSON 文件路径(默认 stdin)")
|
||||||
|
ap.add_argument("--reference-work-id", type=int, default=None)
|
||||||
|
ap.add_argument("--reference-version", default=None)
|
||||||
|
ap.add_argument("--arm-config", default=None, help="实验臂配置 JSON 字符串")
|
||||||
|
ap.add_argument("--dry-run", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
raw = sys.stdin.read() if args.result_json == "-" else Path(args.result_json).read_text(encoding="utf-8")
|
||||||
|
result = json.loads(raw)
|
||||||
|
arm = json.loads(args.arm_config) if args.arm_config else None
|
||||||
|
out = persist_freeze(result, reference_work_id=args.reference_work_id,
|
||||||
|
reference_version=args.reference_version, arm_config=arm, dry_run=args.dry_run)
|
||||||
|
print(json.dumps(out, ensure_ascii=False))
|
||||||
90
db/ddl/100-example规划与冻结.sql
Normal file
90
db/ddl/100-example规划与冻结.sql
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
-- 规划与上下文冻结两表: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();
|
||||||
Loading…
x
Reference in New Issue
Block a user