- 模型锁定完整 ID 等值:role_policy 废弃子串匹配,前置校验+事后 MODEL_POLICY_VIOLATION 熔断+回执 modelMatch,治理链角色放行 BUDGET_CHAIN - 工具白名单只读机械强制:任务包 allowlist ⊆ 只读注册表,TOOL_NOT_READONLY - 本地向量检索 truthful 化:aiContext 裁剪、指针行跳过、资格失败关闭 (bindingStatus/productionRetrievalEligible 不再伪造 active) - 角色提示词 name 回归英文系统 ID;planner 补 fine_outline 绑定; writer 数据契约对齐 writer-candidate-body-v1 - 测试账本隔离:sqlite_path 三层透传(bridge/two_phase),9 处派发测试 改用临时库;清除 muse.db 测试残留 5 runs+26 events+reviews 5/6(有备份) - test-inventory 补 3 条登记并机械重算 summary;评测场景补 output_contract/fail_closed 与 stability 两类;死代码清理 (project_paths.py、offline_only 死参数、harness/harness 残骸) - 新增 metaphysical-diff-review 技能(红线 4.2 载体)并登记,共 59 技能
177 lines
7.0 KiB
Python
177 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
||
"""生产写手框架派发桥的离线测试(不连真实框架、不连真实模型)。
|
||
|
||
固定合同:任务包装配(角色/冻结输入/探索白名单)、会话按章稳定、
|
||
成功路径绑定候选信封与证据引用、派发失败与证据缺失一律失败关闭。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import pathlib
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
|
||
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
||
SCRIPT_DIR = PROJECT_ROOT / "muse" / "content" / "work" / "skills" / "generate" / "write-next-chapter" / "scripts"
|
||
DISPATCH_TEST_DIR = PROJECT_ROOT / "tests" / "skills" / "dispatch-agent-task"
|
||
CHECK_TEST_DIR = PROJECT_ROOT / "tests" / "skills" / "check-content-consistency"
|
||
WNC_TEST_DIR = PROJECT_ROOT / "tests" / "skills" / "write-next-chapter"
|
||
for path in (SCRIPT_DIR, DISPATCH_TEST_DIR, CHECK_TEST_DIR, WNC_TEST_DIR):
|
||
if str(path) not in sys.path:
|
||
sys.path.insert(0, str(path))
|
||
|
||
import dispatch_writer_bridge as bridge # noqa: E402
|
||
from dispatch_agent_task import DEFAULT_RUN_DIR_ROOT # noqa: E402
|
||
from dispatch_writer_bridge import ( # noqa: E402
|
||
DispatchWriterError,
|
||
build_writer_dispatch_spec,
|
||
run_writer_via_dispatch,
|
||
writer_session_paths,
|
||
)
|
||
from read_tools import TOOL_REGISTRY # noqa: E402
|
||
from test_check_writer_candidate import _valid_pair # noqa: E402
|
||
from test_dispatch_agent_task import FIXED_MODEL, RecordingConnect, fake_launcher, pi_stream_lines # noqa: E402
|
||
|
||
|
||
class BridgeConnect(RecordingConnect):
|
||
"""派发桥专用假连接:补证证据引用查询返回 raw_row(可置 None 模拟缺证)。"""
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.raw_row = (9001, 7777)
|
||
|
||
def __call__(self, *args, **kwargs):
|
||
outer = self
|
||
|
||
class _Cursor:
|
||
def execute(self, sql, params=None):
|
||
outer.log.append((sql, params))
|
||
return self
|
||
|
||
def fetchone(self):
|
||
sql = outer.log[-1][0]
|
||
if sql.startswith("SELECT run_id, work_id"):
|
||
return ("row", None, None, "running")
|
||
if sql.startswith("SELECT id, raw_content_id"):
|
||
return outer.raw_row
|
||
if sql.startswith("INSERT INTO example_run"):
|
||
return ("row",)
|
||
if sql.startswith("UPDATE example_run"):
|
||
state = "failed" if "failed" in (outer.log[-1][1] or ()) else "completed"
|
||
return ("row", state, None)
|
||
RecordingConnect.next_id += 1
|
||
return (RecordingConnect.next_id,)
|
||
|
||
def fetchall(self):
|
||
return []
|
||
|
||
def commit(self):
|
||
outer.log.append(("COMMIT", None))
|
||
|
||
def rollback(self):
|
||
outer.log.append(("ROLLBACK", None))
|
||
|
||
class _Ctx:
|
||
def __enter__(self):
|
||
return _Cursor()
|
||
|
||
def __exit__(self, *exc):
|
||
return False
|
||
|
||
return _Ctx()
|
||
|
||
|
||
def _candidate_body() -> str:
|
||
return "茧撕开舱门的一瞬,林深听见了深渊的回声。" * 8
|
||
|
||
|
||
class SpecBuildTest(unittest.TestCase):
|
||
def test_spec_freezes_creative_input_and_read_tools(self):
|
||
context, _ = _valid_pair()
|
||
spec = build_writer_dispatch_spec(
|
||
context, target_chapter=3, human_instruction="让恐惧落在身体上", candidate_version=2,
|
||
)
|
||
self.assertEqual(spec["role"], "writer")
|
||
self.assertEqual(spec["input"]["workId"], context["workId"])
|
||
self.assertEqual(spec["input"]["targetChapter"], 3)
|
||
self.assertEqual(spec["input"]["candidateVersion"], 2)
|
||
self.assertIn("让恐惧落在身体上", spec["taskPrompt"])
|
||
self.assertIn("creativeInput", spec["input"])
|
||
# 探索白名单 = 工具 server 登记表(单一事实源,防漂移)。
|
||
self.assertEqual(spec["toolAllowlist"], sorted(TOOL_REGISTRY))
|
||
self.assertEqual(
|
||
spec["outputSchema"]["required"], ["candidateBody"])
|
||
|
||
def test_session_paths_are_stable_per_chapter(self):
|
||
sid_a, dir_a = writer_session_paths(12, 3)
|
||
sid_b, dir_b = writer_session_paths(12, 3)
|
||
sid_c, dir_c = writer_session_paths(12, 4)
|
||
self.assertEqual((sid_a, dir_a), (sid_b, dir_b))
|
||
self.assertNotEqual(sid_a, sid_c)
|
||
self.assertNotEqual(dir_a, dir_c)
|
||
|
||
|
||
class DispatchRoundTripTest(unittest.TestCase):
|
||
def setUp(self):
|
||
self.tmp = pathlib.Path(tempfile.mkdtemp())
|
||
self.context, _ = _valid_pair()
|
||
# 运行目录按派发运行号固定;测试前清理,避免审计根冲突。
|
||
self.dispatch_run_id = f"{self.context['runId']}-writer-v1"
|
||
import shutil
|
||
|
||
shutil.rmtree(DEFAULT_RUN_DIR_ROOT / self.dispatch_run_id, ignore_errors=True)
|
||
|
||
def _run(self, lines, connect):
|
||
return run_writer_via_dispatch(
|
||
self.context,
|
||
candidate_version=1,
|
||
repo_root=PROJECT_ROOT,
|
||
provider="p",
|
||
model=FIXED_MODEL,
|
||
thinking="low",
|
||
human_instruction="",
|
||
spec_path=self.tmp / "task.json",
|
||
launcher=fake_launcher(lines),
|
||
connect_factory=connect,
|
||
sqlite_path=self.tmp / "ledger.db",
|
||
)
|
||
|
||
def test_success_binds_envelope_and_evidence_ref(self):
|
||
final_text = json.dumps({"candidateBody": _candidate_body()}, ensure_ascii=False)
|
||
connect = BridgeConnect()
|
||
envelope, receipt, raw_ref = self._run(
|
||
pi_stream_lines(final_text, model=FIXED_MODEL), connect
|
||
)
|
||
# 身份、哈希、版本由桥绑定,不来自模型。
|
||
self.assertEqual(envelope["runId"], self.context["runId"])
|
||
self.assertEqual(envelope["candidateVersion"], 1)
|
||
self.assertTrue(envelope["candidateSha256"].startswith("sha256:"))
|
||
self.assertEqual(envelope["candidateBody"], _candidate_body())
|
||
# 回执适配供生产账本消费;证据引用来自派发运行的调用账。
|
||
self.assertEqual(receipt.requested_model_id, f"p/{FIXED_MODEL}")
|
||
self.assertTrue(receipt.dispatch_run_id.startswith(self.context["runId"]))
|
||
self.assertEqual(raw_ref, (9001, 7777))
|
||
# 任务包落盘可审计。
|
||
saved_spec = json.loads((self.tmp / "task.json").read_text(encoding="utf-8"))
|
||
self.assertEqual(saved_spec["role"], "writer")
|
||
|
||
def test_dispatch_failure_fails_closed(self):
|
||
connect = BridgeConnect()
|
||
with self.assertRaises(DispatchWriterError) as caught:
|
||
# 空事件流 -> 框架层失败关闭。
|
||
self._run([], connect)
|
||
self.assertNotEqual(caught.exception.code, "")
|
||
|
||
def test_missing_raw_reference_fails_closed(self):
|
||
final_text = json.dumps({"candidateBody": _candidate_body()}, ensure_ascii=False)
|
||
connect = BridgeConnect()
|
||
connect.raw_row = None
|
||
with self.assertRaises(DispatchWriterError) as caught:
|
||
self._run(pi_stream_lines(final_text, model=FIXED_MODEL), connect)
|
||
self.assertEqual(caught.exception.code, "DISPATCH_EVIDENCE_MISSING")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|