muse-agent-example/dashboard/test_server_display.py
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

149 lines
6.7 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
"""看板展示层的中文语义与编码合同测试,不连接数据库。"""
import pathlib
import sys
import unittest
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
import server # noqa: E402
class DashboardDisplayTest(unittest.TestCase):
def test已知枚举显示中文语义和字符串编码(self):
text = server.enum_text("character", "entity_type")
self.assertIn("人物", text)
self.assertIn("<code>character</code>", text)
self.assertNotEqual(text, "character")
def test未知英文枚举不裸露(self):
text = server.enum_text("future_state", "status")
self.assertIn("未登记", text)
self.assertIn("<code>future_state</code>", text)
def test状态标签显示中文和编码(self):
text = server.chip("pending")
self.assertIn("待确认", text)
self.assertIn("<code>pending</code>", text)
def test字段标题使用中文并保留协议字段编码(self):
text = server.field_label("运行编号", "run_id")
self.assertIn("运行编号", text)
self.assertIn("<code class='field-code'>run_id</code>", text)
def test工具名称使用中文并保留_cli编码(self):
text = server._fmt_tools(["Read", "Grep"])
self.assertIn("读取", text)
self.assertIn("文本搜索", text)
self.assertIn("<code>Read</code>", text)
self.assertIn("<code>Grep</code>", text)
def test自由文本技术术语显示中文语义和编码(self):
text = server.annotated_text("writer sandbox + writer + Claude CLI + MiniMax + semantic detector + asOf")
self.assertIn("写手隔离环境", text)
self.assertIn("写手 <span", text)
self.assertIn("模型命令行客户端", text)
self.assertIn("模型服务", text)
self.assertIn("语义检测器", text)
self.assertIn("截至章", text)
self.assertIn("<code>writer sandbox</code>", text)
def test说明截断不会切断编码或误改_postgresql(self):
text = server.annotated_text(server.short_text("场景 scenario: fine_outline_replay/校验", 20))
self.assertNotIn("fine_outl", text)
self.assertNotIn("Postgre数据库", server.annotated_text("PostgreSQL 查询"))
def test详情自由文本中的运行时编码也有中文语义(self):
text = server.annotated_text(
"Claude CLI fresh/sandbox + revision journal (file_cas) + raw vault; "
"被 detect/quality-gate/continuation/replay-eval 复用"
)
self.assertIn("模型命令行客户端", text)
self.assertIn("修订日志", text)
self.assertIn("原始证据仓", text)
self.assertIn("语义检测", text)
self.assertIn("质量门", text)
self.assertIn("实验库", server.annotated_text("muse-example"))
self.assertIn("正文块", server.annotated_text("正文 block"))
self.assertIn("<code>file_cas</code>", text)
def test卡片下钻地址保留筛选条件(self):
href = server._query_href(
"/works/12", {"q": ["薄雾号"], "type": ["location"], "status": ["pending"]}, card=25438
)
self.assertIn("q=", href)
self.assertIn("type=location", href)
self.assertIn("status=pending", href)
self.assertIn("card=25438", href)
def test章节展示中文标题并保留主键编码(self):
text = server._chapter_text(17474, 2, "称量")
self.assertIn("第2章《称量》", text)
self.assertIn("<code>chapter_id=17474</code>", text)
def test运行描述包含用途角色和章节(self):
text = server._run_description(
("run-prod-work12-ch2", 12, "深渊机神", 2, "user", "completed", "时间",
17474, 2, "称量", "writer", "generation", "production", "writer", "production_generation")
)
self.assertIn("正文生产", text)
self.assertIn("写手", text)
self.assertIn("第2章《称量》", text)
def test原始_JSON_错误信息可读(self):
text = server._content_text('{"failureType":"InvalidTextRepresentation"}')
self.assertIn('"failureType": "InvalidTextRepresentation"', text)
def test规划标题和摘要从载荷提取(self):
payload = {"chapterGoal": "林深被隔离审讯", "chapterEndHook": "茧准备出击"}
self.assertEqual(server._planning_title(payload, "fine_outline", 2), "第2章目标")
self.assertEqual(server._planning_summary(payload), "林深被隔离审讯")
def test数据库AI味页面展示正式库汇总(self):
text = server.view_ai_flavor({})
self.assertIn("AI 味案例", text)
self.assertIn("788", text)
self.assertIn("8", text)
self.assertIn("PostgreSQL muse-example 正式库", text)
self.assertIn("原文片段未保存", text)
self.assertIn("不是人工确认的 AI 味结论", text)
self.assertIn("来源已验证", text)
self.assertIn("788/788", text)
self.assertIn("batch_id=", text)
def test数据库AI味页面支持作品分页与卡片详情(self):
filtered = server.view_ai_flavor({"work": ["ref:机破星河_当年离歌"], "page": ["2"]})
self.assertIn("当前第 2/6 页", filtered)
self.assertIn("ref:机破星河_当年离歌", filtered)
detail = server.view_ai_flavor({"card": ["case-0b9ed3af70a7dbf315fc"]})
self.assertIn("全文 SHA-256", detail)
self.assertIn("原文片段未保存", detail)
self.assertIn("来源已验证", detail)
def test数据库案例详情显示用途和作用域边界(self):
detail = server.view_ai_flavor({"card": ["case-0b9ed3af70a7dbf315fc"]})
self.assertIn("当前用途", detail)
self.assertIn("仅供本卡/本作品人工复核", detail)
self.assertIn("backfill", detail)
def test数据库案例页面按当前数据库作品数动态渲染(self):
original_bundle = server._ai_db_bundle
try:
report, revalidation, error = original_bundle()
report = dict(report)
report["works"] = list(report["works"][:1])
report["totals"] = dict(report.get("totals") or {})
report["totals"]["books"] = 1
report["cards"] = list(report["cards"][:1])
report["totals"]["cards"] = 1
server._ai_db_bundle = lambda: (report, revalidation, error)
text = server.view_ai_flavor({})
finally:
server._ai_db_bundle = original_bundle
self.assertIn("按作品汇总(1 本)", text)
self.assertNotIn("按作品汇总(8 本)", text)
if __name__ == "__main__":
unittest.main()