范围(不含 design-story-foundation、docs/design、docs/write-chapter、
craft/、humanization/README.md 等进行中改动):
1. humanization 规则/样例运行时数据库权威
- db/ddl/111:example_ai_flavor_rule / example_ai_flavor_sample /
example_ai_flavor_rule_event(append-only 生命周期留痕),已应用到 muse-example
- deai/load_db.py:数据库装载器,激活门/重复检测/指纹与文件装载器同源;
数据库失败关闭,不静默回退 Git 文件资产
- humanization/tools/seed_rules_db.py:YAML 种子单事务同步,幂等、
变化留痕、--strict 对 db-only 行失败关闭;真实库已种入 26 规则/107 样例
- prevent/diagnose/revise 生产路径切到数据库读取(--offline 显式读文件),
落库前新鲜度检查与合同声明来源一致;四个 SKILL.md 数据库合同同步
- 真实库验证:规则库指纹与文件种子一致(v-609bc40e21d0b5db),
三个生产脚本端到端从库装载通过
2. PostgreSQL 集成:显式授权后 9/9 通过
- 此前被依赖门阻断的 6 个 _db/smoke 测试全部通过
- extract rollback 冒烟改为回滚事务内自给夹具(pending 窗/草稿缺失时自建),
不再依赖瞬时生产状态;夹具残留核验为 0
3. Skill 行为评测脚手架(真实执行数量仍为 0)
- harness/evals/skill_eval.py:场景合同、六类评测范畴、适配器和结构化裁决报告
- diagnose-ai-flavor 参考场景 4 条 + 管道自测 7 项通过
- 真实模型适配器未授权时以稳定码 EVAL_ADAPTER_UNAVAILABLE 失败关闭;
清单登记 skill_behavior_eval 条目,默认被依赖门阻断
4. evaluate-frozen-replay raw 存储边界冲突
- docs/2026-08-19 备忘录:平台 DB-first 合同(创始人批准)与回放链
仓外 vault 强制的冲突事实、两个选项和裁决前约束;运行时合同未单方面改写
5. harness 自身修复
- runner 对账语义:行为评测入口不参与测试资产双向等值,但登记文件必须存在;
manifest 保留 skill_behavior_eval 布尔字段并校验类型
- 新增 2 条对账回归用例
验证证据: harness 三组自测 15+15+7 通过;静态审计 32 Skill / 0 问题;
76 个非数据库条目通过;9 个 PostgreSQL 集成条目显式授权后通过;
行为评测条目默认阻断;py_compile 与 git diff --check 通过。
未调用真实模型、embedding 或额度;真实行为评测执行数量仍为 0。
204 lines
9.2 KiB
Python
204 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
||
"""技能 3「诊断」确定性脚本层(专题-09 §5.3)。
|
||
|
||
职责边界:只查不改——对目标正文跑 humanization/rules 的 active 规则,
|
||
产出诊断产物(产物头 + 发现清单)。语义层判定由外部模型/人产出后经
|
||
merge_model_findings 注入,同样过合同校验;诊断不修改任何正文。
|
||
|
||
落库合同(检测完成即落库):
|
||
- example_run:一次诊断一行(run_id 由 作品+文本哈希+规则库版本 决定,重跑幂等);
|
||
- example_quality_result:judge_kind=detection,绑被诊断文本的 sha256;
|
||
- 只有显式 --offline 才不写库(仅产文件,供回放)。
|
||
"""
|
||
import argparse
|
||
import hashlib
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# 共享执行骨架(humanization 副本)与数据库通道(access-database)的引导
|
||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||
AGENT_ROOT = SCRIPT_DIR.parents[3]
|
||
for _p in (AGENT_ROOT / "humanization" / "src",
|
||
AGENT_ROOT / ".claude" / "skills" / "access-database" / "scripts"):
|
||
if str(_p) not in sys.path:
|
||
sys.path.insert(0, str(_p))
|
||
|
||
from deai import diagnose, load # noqa: E402
|
||
|
||
TENANT_ID = 1
|
||
CREATOR = "1"
|
||
|
||
|
||
class DiagnoseContractError(ValueError):
|
||
"""诊断合同失败:下游修订/前置预防必须失败关闭。"""
|
||
|
||
|
||
def rule_library_version(rules: dict) -> str:
|
||
"""兼容入口;真实指纹由共享装载器覆盖完整规则内容。"""
|
||
return load.rule_library_version(rules)
|
||
|
||
|
||
def load_active_library(from_db: bool = False) -> tuple[dict, str]:
|
||
"""装载规则库并强制激活门:样例不齐的规则装载器直接拒绝(专题-09 §4.1)。
|
||
|
||
生产(from_db=True)从 muse-example 读取运行时权威,数据库失败关闭,
|
||
不静默回退 Git 文件资产;只有显式 --offline 才读文件。
|
||
"""
|
||
if from_db:
|
||
from db import connect
|
||
from deai import load_db
|
||
|
||
with connect(readonly=True) as conn:
|
||
samples = load_db.load_samples_from_db(conn)
|
||
rules = load_db.load_rules_from_db(conn, samples=samples)
|
||
return rules, rule_library_version(rules)
|
||
samples = load.load_samples()
|
||
rules = load.load_rules(samples=samples)
|
||
return rules, rule_library_version(rules)
|
||
|
||
|
||
def run_diagnosis(text: str, *, work_ref: str, chapter_ref: str | None = None,
|
||
external_findings: list | None = None, mode: str = "Audit",
|
||
from_db: bool = False) -> dict:
|
||
"""产出完整诊断产物;产物头缺项由 validate_artifact 兜底拒绝。"""
|
||
if not text:
|
||
raise DiagnoseContractError("诊断文本为空")
|
||
if not work_ref:
|
||
raise DiagnoseContractError("诊断必须带 work_ref")
|
||
rules, lib_version = load_active_library(from_db)
|
||
artifact = diagnose.run_deterministic_rules(text, load.active_rules(rules), lib_version, mode)
|
||
if external_findings:
|
||
diagnose.merge_model_findings(artifact, external_findings, rules=rules, text=text)
|
||
diagnose.validate_artifact(artifact)
|
||
artifact["work_ref"] = work_ref
|
||
if chapter_ref:
|
||
artifact["chapter_ref"] = chapter_ref
|
||
return artifact
|
||
|
||
|
||
def _run_id(*parts: str) -> str:
|
||
return "diag-" + hashlib.sha256("|".join(parts).encode("utf-8")).hexdigest()[:40]
|
||
|
||
|
||
def persist_diagnosis(artifact: dict, *, text: str, from_db: bool = False,
|
||
creator: str = CREATOR, tenant_id: int = TENANT_ID) -> dict:
|
||
"""诊断运行落库:example_run(幂等 upsert)+ example_quality_result(append-only)。"""
|
||
from db import connect
|
||
|
||
if not isinstance(text, str) or not text:
|
||
raise DiagnoseContractError("落库诊断文本为空")
|
||
try:
|
||
diagnose.validate_artifact(artifact)
|
||
except (ValueError, KeyError, TypeError) as exc:
|
||
raise DiagnoseContractError(f"落库诊断产物不可读: {exc}") from exc
|
||
expected_hash = diagnose.text_hash(text)
|
||
if artifact["text_hash"] != expected_hash:
|
||
raise DiagnoseContractError("落库诊断产物 text_hash 与正文不一致")
|
||
if not artifact.get("work_ref"):
|
||
raise DiagnoseContractError("落库诊断必须带 work_ref")
|
||
_, current_library_version = load_active_library(from_db)
|
||
if artifact["rule_library_version"] != current_library_version:
|
||
raise DiagnoseContractError("落库诊断产物使用了过期规则库")
|
||
|
||
run_id = _run_id(artifact["work_ref"], artifact["text_hash"], artifact["rule_library_version"])
|
||
text_sha = hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||
per_rule: dict[str, int] = {}
|
||
for f in artifact["findings"]:
|
||
per_rule[f["rule_id"]] = per_rule.get(f["rule_id"], 0) + 1
|
||
per_layer: dict[str, int] = {}
|
||
per_decision: dict[str, int] = {}
|
||
for finding in artifact["findings"]:
|
||
per_layer[finding["layer"]] = per_layer.get(finding["layer"], 0) + 1
|
||
decision = finding["decision_proposal"]
|
||
per_decision[decision] = per_decision.get(decision, 0) + 1
|
||
detail = {
|
||
"text_hash": artifact["text_hash"],
|
||
"rule_library_version": artifact["rule_library_version"],
|
||
"mode": artifact["mode"],
|
||
"work_ref": artifact["work_ref"],
|
||
"chapter_ref": artifact.get("chapter_ref"),
|
||
"findings_total": len(artifact["findings"]),
|
||
"findings_per_rule": per_rule,
|
||
"findings_per_layer": per_layer,
|
||
"decision_proposals": per_decision,
|
||
}
|
||
run_sql = (
|
||
"INSERT INTO example_run (run_id, work_id, trigger_source, trigger_detail, "
|
||
"terminal_state, finished_at, creator, tenant_id) "
|
||
"VALUES (%s, NULL, 'diagnostic', %s::jsonb, 'completed', CURRENT_TIMESTAMP, %s, %s) "
|
||
"ON CONFLICT (run_id) DO UPDATE SET terminal_state='completed', "
|
||
"finished_at=CURRENT_TIMESTAMP, trigger_detail=EXCLUDED.trigger_detail, "
|
||
"updater=EXCLUDED.creator, update_time=CURRENT_TIMESTAMP"
|
||
)
|
||
quality_sql = (
|
||
"INSERT INTO example_quality_result "
|
||
"(run_id, candidate_sha256, judge_kind, dimension, scale_version, conclusion, detail, creator, tenant_id) "
|
||
"VALUES (%s, %s, 'detection', NULL, %s, %s, %s::jsonb, %s, %s)"
|
||
)
|
||
with connect() as conn:
|
||
with conn.transaction():
|
||
conn.execute(run_sql, (run_id, json.dumps(detail, ensure_ascii=False), creator, tenant_id))
|
||
# append-only 表不能 ON CONFLICT 更新;按幂等键预检,重复运行不重复记账
|
||
exists = conn.execute(
|
||
"SELECT 1 FROM example_quality_result WHERE tenant_id=%s AND run_id=%s "
|
||
"AND judge_kind='detection' AND COALESCE(candidate_sha256,'')=%s",
|
||
(tenant_id, run_id, text_sha),
|
||
).fetchone()
|
||
if exists is None:
|
||
conn.execute(quality_sql, (
|
||
run_id, text_sha, artifact["rule_library_version"],
|
||
"has_findings" if artifact["findings"] else "clean",
|
||
json.dumps(detail, ensure_ascii=False), creator, tenant_id,
|
||
))
|
||
return {"run_id": run_id, "text_sha256": text_sha, "findings": len(artifact["findings"])}
|
||
|
||
|
||
def _parser() -> argparse.ArgumentParser:
|
||
parser = argparse.ArgumentParser(description="技能 3 诊断:只查不改,产出诊断产物并落库")
|
||
sub = parser.add_subparsers(dest="command", required=True)
|
||
run = sub.add_parser("run")
|
||
run.add_argument("--text-file", type=Path, required=True)
|
||
run.add_argument("--work-ref", required=True)
|
||
run.add_argument("--chapter-ref")
|
||
run.add_argument("--external-findings", type=Path,
|
||
help="语义层外部判定(JSON 数组),注入前逐条过 finding 合同")
|
||
run.add_argument("--mode", default="Audit", choices=["Audit", "Patch"])
|
||
run.add_argument("--output", type=Path, required=True)
|
||
run.add_argument("--offline", action="store_true", help="只产文件,不写 muse-example")
|
||
return parser
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
args = _parser().parse_args(argv)
|
||
try:
|
||
text = args.text_file.read_text(encoding="utf-8")
|
||
external = None
|
||
if args.external_findings:
|
||
external = json.loads(args.external_findings.read_text(encoding="utf-8"))
|
||
artifact = run_diagnosis(text, work_ref=args.work_ref, chapter_ref=args.chapter_ref,
|
||
external_findings=external, mode=args.mode,
|
||
from_db=not args.offline)
|
||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||
args.output.write_text(json.dumps(artifact, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||
if args.offline:
|
||
persistence = {"status": "offline", "reason": "显式 --offline,未写 muse-example"}
|
||
else:
|
||
persistence = persist_diagnosis(artifact, text=text, from_db=True)
|
||
print(json.dumps({
|
||
"findings": len(artifact["findings"]),
|
||
"text_hash": artifact["text_hash"],
|
||
"rule_library_version": artifact["rule_library_version"],
|
||
"output": str(args.output),
|
||
"persistence": persistence,
|
||
}, ensure_ascii=False))
|
||
return 0
|
||
except (DiagnoseContractError, diagnose.ArtifactIncomplete, load.LoadError,
|
||
ValueError, OSError, UnicodeError) as exc:
|
||
print(f"DIAGNOSE_CONTRACT_FAILED: {exc}")
|
||
return 2
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|