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

129 lines
7.0 KiB
Python
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.

#!/usr/bin/env python3
"""A3:23 型结构本体 YAML → meta 表行(W1 种子演练,幂等可重跑)。
映射约定(详见 db/表映射.md 与本次收口记录):
- muse_meta_schema 一型一行;schema_key=target_type;状态 启用→active / 待启用→inactive
- muse_meta_schema_version 每型 v1,active_flag=TRUE;field_contract_snapshot=完整 YAML(判据/说明/设计发现全在)
- muse_meta_field 规范化投影行:基础字段 sort_order 1–9,特有字段 11 起(段位即基础/特有约定)
- muse_meta_visibility_policy 版本级一行:ai_context=是否存在 AI 可见字段;
字段级细则进 policy_snapshot.fieldAiContext(主仓无字段级列——设计发现,待回填)
- 幂等:schema/version/policy upsert;field 行重跑=删旧插新(meta 种子行豁免软删约定,见表映射.md)
"""
import json
import pathlib
import sys
import psycopg
import yaml
from psycopg.types.json import Jsonb
DSN = "postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example"
SCHEMA_DIR = pathlib.Path(__file__).resolve().parents[4] / "meta" / "schemas"
TENANT, ACTOR = 1, "1" # 实验写入约定:系统主账号
# 基础字段(所有型共有,README §使用规则;aiContext 为实验设计判断,可在 B4 优化环调整):
# 名称/别名/摘要/标签全用途可见;来源(出处)生成不可见防抄袭腔;状态是系统轨道值不进上下文(授权过滤在查询层)
BASE_FIELDS = [
("名称", "text", True, None, True),
("别名", "array", False, None, True),
("一句话摘要", "text", True, None, True),
("标签", "array", False, None, True),
("来源", "text", False, None, ["planning", "detection", "extraction"]),
("状态", "enum", True, ["草稿", "已确认"], False),
]
def seed_one(conn, doc: dict, src_name: str):
"""单型入库:schema → version(v1) → fields → visibility_policy;返回审查摘要。"""
key = doc["target_type"]
status = "active" if doc.get("状态", "启用") == "启用" else "inactive"
# 1) schema 行 upsert(uk: tenant_id+domain+scope+target_type+schema_key)
row = conn.execute(
"""INSERT INTO muse_meta_schema (schema_key, domain, scope, target_type, display_name, status,
creator, updater, tenant_id)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (tenant_id, domain, scope, target_type, schema_key)
DO UPDATE SET display_name=EXCLUDED.display_name, status=EXCLUDED.status, updater=EXCLUDED.updater
RETURNING id""",
(key, doc["domain"], doc["scope"], key, doc.get("中文名", key), status,
ACTOR, ACTOR, TENANT)).fetchone()
schema_id = row[0]
# 2) version v1 upsert(uk: tenant_id+schema_key+version_no);快照=完整 YAML
row = conn.execute(
"""INSERT INTO muse_meta_schema_version (schema_id, schema_key, version_no, status, active_flag,
field_contract_snapshot, change_note, published_by, activated_by, creator, updater, tenant_id)
VALUES (%s,%s,1,'published',TRUE,%s,%s,%s,%s,%s,%s,%s)
ON CONFLICT (tenant_id, schema_key, version_no)
DO UPDATE SET field_contract_snapshot=EXCLUDED.field_contract_snapshot,
change_note=EXCLUDED.change_note, updater=EXCLUDED.updater
RETURNING id""",
(schema_id, key, Jsonb(doc), f"A3 种子入库(源:{src_name})", ACTOR, ACTOR, ACTOR, ACTOR, TENANT)).fetchone()
version_id = row[0]
conn.execute("UPDATE muse_meta_schema SET active_version_id=%s WHERE id=%s", (version_id, schema_id))
# 3) field 行:删旧插新(幂等重建)
conn.execute("DELETE FROM muse_meta_field WHERE tenant_id=%s AND schema_version_id=%s", (TENANT, version_id))
ai_map = {}
n_special = 0
for i, (fk, ftype, req, enum, ai) in enumerate(BASE_FIELDS, start=1):
conn.execute(
"""INSERT INTO muse_meta_field (schema_version_id, field_key, display_name, field_type,
is_required, enum_values, sort_order, creator, updater, tenant_id)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""",
(version_id, fk, fk, ftype, req, Jsonb(enum) if enum else None, i, ACTOR, ACTOR, TENANT))
ai_map[fk] = ai
for i, f in enumerate(doc.get("特有字段") or [], start=11):
conn.execute(
"""INSERT INTO muse_meta_field (schema_version_id, field_key, display_name, field_type,
is_required, sort_order, creator, updater, tenant_id)
VALUES (%s,%s,%s,'text',FALSE,%s,%s,%s,%s)""",
(version_id, f["key"], f["key"], i, ACTOR, ACTOR, TENANT))
ai_map[f["key"]] = f.get("aiContext", True)
n_special += 1
# 4) visibility_policy:版本级一行 upsert(无 uk,先查后写);字段级细则进 policy_snapshot
any_ai = any(v is not False for v in ai_map.values())
snap = Jsonb({"fieldAiContext": ai_map,
"用途枚举": ["planning", "generation", "detection", "extraction"],
"语义": "true=任何用途可入AI上下文;false=一律不入;[用途]=仅列出的用途可入"})
exist = conn.execute(
"SELECT id FROM muse_meta_visibility_policy WHERE tenant_id=%s AND schema_version_id=%s",
(TENANT, version_id)).fetchone()
if exist:
conn.execute("UPDATE muse_meta_visibility_policy SET ai_context=%s, policy_snapshot=%s, updater=%s WHERE id=%s",
(any_ai, snap, ACTOR, exist[0]))
else:
conn.execute(
"""INSERT INTO muse_meta_visibility_policy (schema_version_id, ui_visible, ai_context,
user_editable, user_searchable, exportable, policy_snapshot, creator, updater, tenant_id)
VALUES (%s,TRUE,%s,TRUE,FALSE,FALSE,%s,%s,%s,%s)""",
(version_id, any_ai, snap, ACTOR, ACTOR, TENANT))
full_open = sum(1 for v in ai_map.values() if v is True)
scoped = sum(1 for v in ai_map.values() if isinstance(v, list))
closed = sum(1 for v in ai_map.values() if v is False)
return (key, doc.get("中文名", ""), doc["domain"], doc["scope"], status,
len(BASE_FIELDS) + n_special, full_open, scoped, closed)
def main():
files = sorted(p for p in SCHEMA_DIR.glob("*.yaml"))
if not files:
sys.exit(f"未找到 schema YAML: {SCHEMA_DIR}")
rows = []
with psycopg.connect(DSN) as conn:
for p in files:
doc = yaml.safe_load(p.read_text())
rows.append(seed_one(conn, doc, p.name))
conn.commit()
# 审查面:逐型卡片行
print(f"{'型':<20}{'中文名':<10}{'domain':<11}{'scope':<10}{'状态':<9}{'字段':<5}{'AI全开':<7}{'限用途':<7}{'关闭'}")
for r in rows:
print(f"{r[0]:<20}{r[1]:<10}{r[2]:<11}{r[3]:<10}{r[4]:<9}{r[5]:<5}{r[6]:<7}{r[7]:<7}{r[8]}")
print(f"\n共 {len(rows)} 型入库(基础字段 {len(BASE_FIELDS)} + 各型特有;快照含判据/说明/设计发现全文)")
if __name__ == "__main__":
main()