muse-agent-example/db/ddl/109-example候选状态CAS.sql
zizi b0bc7a8745 框架: 技能按动作-对象重组 + 先审后入创作闭环
一、技能重组(动作-对象命名)
- 旧目录 clean/confirm/continuation/db/detect/embed/… 重组为
  clean-book-text/decide-candidate/write-next-chapter/access-database/
  check-content-consistency/embed-knowledge/…(git 识别为 rename,内容保持)
- agents/*.md、AGENTS.md/CLAUDE.md 收编、example_skill 登记表同步新名

二、先审后入创作闭环(本次核心)
正文接受从"机械门一过就写正典"改为"机械门+语义审查双通过+用户批准+单事务原子提交",
DB 级兜底,编排层跳步即被硬拒。
- candidate_cas.py + example_candidate_cas(109):持久化 CAS 状态链
- fact_delta.py + example_fact_delta/example_fact_ledger(106):结构化事实增量,
  模型只提六型闭集增量+正文证据引文,仅用户批准的增量随正文同事务入账本
- projection_registry.py + example_projection_run(107):投影登记与恢复
- acceptance_state.py:接受前置实时状态重读
- lesson_registry.py + example_lesson(108):经验升格链,禁止自动升格
- DDL 105:example_candidate 增 semantic_status/semantic_report_sha256
- write_canonical.accept:语义兜底+同事务合并增量+登记投影;
  run_writer_pipeline/persist_writer_run/run_writer_semantic_detector/step2 接入全链
- claude_runtime:兼容新 CLI modelUsage 信息字段

三、审查修复(独立子代理四维审查后)
- 事实增量 propose→approve 翻态正道,不撞唯一键
- 冻结配置探针重刷(CLI 2.1.211→2.1.231 漂移),profileSha256/adapterVersion 再登记
- 可视化合同悬空路径/五六空间矛盾、 SoT 旧技能名漂移、行尾空白清理

测试:离线 65 套 + 真实库集成 5 套(CAS/接受故障注入/事实增量/投影/经验升格)+ 回放 79 项全绿。
创作内容(docs/design、生成正文 artifacts)按"框架与创作分开"未入本提交。
2026-08-14 10:24:08 +08:00

98 lines
5.1 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.

-- 候选 CAS 状态链表:example_candidate_cas(可变注册表,一次运行一条链)。
-- 目的:把 write-next-chapter/scripts/run_writer_pipeline.py 的内存 InMemoryCasStateStore
-- 持久化到 PostgreSQL,让重试、崩溃、旧候选写回和并发写可判断(05 §3 CAS 合同)。
-- 链语义:run_id 唯一一条链;DRAFT→CHECKING→PASSED/REJECTED;
-- REJECTED 之后可由 start_next 开严格递增 attempt/candidate_version 的新一轮 DRAFT;
-- PASSED 是终态,不得再迁移。
-- CAS 语义:所有迁移由写路径带期望 revision 的条件 UPDATE 完成,行计数 != 1 即竞争(返回 None,
-- 调用方收敛为 CAS_CONFLICT);DB 触发器再锁死合法迁移方向、revision 单调 +1 与身份不可变,
-- 即使调用方代码有缺陷也无法把链改成非法形状。
-- 可变注册表带公共列六件套 + updated_at 触发器;全部对象 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。
CREATE TABLE IF NOT EXISTS example_candidate_cas (
run_id VARCHAR(64) PRIMARY KEY, -- 运行标识(= example_run.run_id,一次运行一条 CAS 链)
work_id BIGINT, -- → muse_content_work.id(软引用,看板定位用)
target_chapter INTEGER, -- 目标章序(可空:非章级运行)
attempt INTEGER NOT NULL, -- 当前轮尝试号(start_next 严格递增)
candidate_version INTEGER NOT NULL, -- 当前轮候选版本(start_next 严格递增)
state VARCHAR(20) NOT NULL, -- DRAFT/CHECKING/PASSED/REJECTED
revision INTEGER NOT NULL DEFAULT 1, -- 链内单调号,每次迁移恰好 +1(CAS 期望值)
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_candidate_cas_state
CHECK (state IN ('DRAFT','CHECKING','PASSED','REJECTED')),
CONSTRAINT chk_example_candidate_cas_counters
CHECK (revision >= 1 AND attempt >= 1 AND candidate_version >= 1)
);
CREATE INDEX IF NOT EXISTS idx_example_candidate_cas_work
ON example_candidate_cas(tenant_id, work_id, target_chapter);
CREATE OR REPLACE TRIGGER trg_example_candidate_cas_updated_at
BEFORE UPDATE ON example_candidate_cas FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- 初始行必须从 DRAFT / revision=1 开始,不允许凭空建中间态
CREATE OR REPLACE FUNCTION enforce_example_candidate_cas_initial()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.state <> 'DRAFT' OR NEW.revision <> 1 THEN
RAISE EXCEPTION 'CAS 链必须以 DRAFT/revision=1 开始,得到 state=% revision=%', NEW.state, NEW.revision;
END IF;
IF NEW.attempt < 1 OR NEW.candidate_version < 1 THEN
RAISE EXCEPTION 'CAS 链初始 attempt/candidate_version 必须 >= 1';
END IF;
RETURN NEW;
END;
$$;
CREATE OR REPLACE TRIGGER trg_example_candidate_cas_initial
BEFORE INSERT ON example_candidate_cas
FOR EACH ROW EXECUTE FUNCTION enforce_example_candidate_cas_initial();
-- 迁移规则:身份不可变、revision 恰好 +1、方向闭集、新一轮必须严格递增
CREATE OR REPLACE FUNCTION enforce_example_candidate_cas_transition()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.run_id <> OLD.run_id THEN
RAISE EXCEPTION 'CAS 链 run_id 不可变: %', OLD.run_id;
END IF;
IF OLD.deleted THEN
RAISE EXCEPTION 'CAS 链已软删,不得迁移: %', OLD.run_id;
END IF;
IF NEW.revision <> OLD.revision + 1 THEN
RAISE EXCEPTION 'CAS revision 必须恰好 +1: 当前 %,得到 %', OLD.revision, NEW.revision;
END IF;
IF OLD.state = 'PASSED' THEN
RAISE EXCEPTION 'PASSED 是 CAS 终态,不得再迁移';
END IF;
IF OLD.state = 'DRAFT' AND NEW.state <> 'CHECKING' THEN
RAISE EXCEPTION 'DRAFT 只能迁移到 CHECKING,得到 %', NEW.state;
END IF;
IF OLD.state = 'CHECKING' AND NEW.state NOT IN ('PASSED','REJECTED') THEN
RAISE EXCEPTION 'CHECKING 只能迁移到 PASSED/REJECTED,得到 %', NEW.state;
END IF;
IF OLD.state = 'REJECTED' THEN
IF NEW.state <> 'DRAFT' THEN
RAISE EXCEPTION 'REJECTED 只能开新一轮 DRAFT,得到 %', NEW.state;
END IF;
IF NEW.attempt <= OLD.attempt OR NEW.candidate_version <= OLD.candidate_version THEN
RAISE EXCEPTION '新一轮 DRAFT 要求 attempt 与 candidate_version 严格递增: (% , %) -> (% , %)',
OLD.attempt, OLD.candidate_version, NEW.attempt, NEW.candidate_version;
END IF;
ELSIF NEW.attempt <> OLD.attempt OR NEW.candidate_version <> OLD.candidate_version THEN
RAISE EXCEPTION '同轮迁移不得改变 attempt/candidate_version';
END IF;
RETURN NEW;
END;
$$;
CREATE OR REPLACE TRIGGER trg_example_candidate_cas_transition
BEFORE UPDATE ON example_candidate_cas
FOR EACH ROW EXECUTE FUNCTION enforce_example_candidate_cas_transition();