muse-agent-example/.claude/skills/read-context/scripts/test_retrieve_writer_sources.py

400 lines
14 KiB
Python

#!/usr/bin/env python3
"""卡索引驱动的冻结原文检索测试。"""
from __future__ import annotations
import copy
import pathlib
import sys
import unittest
SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "replay-eval" / "scripts"))
sys.path.insert(0, str(SCRIPT_DIR.parents[1] / "search" / "scripts"))
from load_reference_work import begin_read_snapshot # noqa: E402
from search import search_cards # noqa: E402
from retrieve_writer_sources import ( # noqa: E402
ProductionCardIndexRepository,
ReplayCardIndexRepository,
RetrievalError,
build_retrieval_plan,
freeze_card,
retrieve_writer_sources,
stable_sort_cards,
)
SOURCE_VERSION = "raw-file-v1:sha256:" + "a" * 64
def source_ref(chapter: int, block_id: int, offset: int = 0) -> dict:
"""构造可回读的冻结原文引用。"""
return {
"sourceId": f"chapter:{chapter}:block:{block_id}",
"sourceVersion": SOURCE_VERSION,
"chapter": chapter,
"blockId": block_id,
"startCodePoint": offset,
"endCodePoint": offset + 4,
}
def card(card_id: str, score: float, chapter: int = 3, offset: int = 0) -> dict:
"""构造含历史里程碑和原文指针的抽取卡索引。"""
return {
"cardId": card_id,
"type": "character",
"name": f"角色{card_id}",
"score": score,
"sourceVersion": SOURCE_VERSION,
"sourceId": f"canonical-entity:{card_id}",
"sourceOffset": offset,
"sourceRefs": [source_ref(chapter, int(card_id), offset)],
"milestones": [
{"chapter": 1, "fact": "初始状态"},
{"chapter": chapter, "fact": "冻结线内状态"},
{"chapter": 9, "fact": "未来终态"},
],
"sourceKind": "canonical_entity",
"sourceStatus": "active",
"bindingStatus": "active",
"productionRetrievalEligible": True,
}
class FakeCardRepository:
"""只返回预置卡片并记录固定计划查询。"""
def __init__(self, cards):
self.cards = cards
self.plans = []
def search(self, plan):
self.plans.append(copy.deepcopy(plan))
return copy.deepcopy(self.cards)
class FakeProseRepository:
"""按来源引用返回冻结片段,不访问数据库。"""
def __init__(self):
self.calls = []
def read_source_refs(self, *, work_id, as_of, source_refs):
self.calls.append((work_id, as_of, copy.deepcopy(source_refs)))
rows = []
for ref in source_refs:
rows.append(
{
"sourceRef": copy.deepcopy(ref),
"chapter": ref["chapter"],
"text": "历史原文",
"purpose": "card_source",
}
)
return rows
class FakeConnection:
"""记录事务声明,证明读取器先锁定只读可重复读快照。"""
def __init__(self):
self.statements = []
def execute(self, statement, params=None):
self.statements.append((" ".join(statement.split()), params))
return self
class FakeQueryResult:
"""模拟 psycopg 查询结果。"""
def __init__(self, rows):
self.rows = rows
def fetchall(self):
return self.rows
class FakeSearchConnection:
"""为 search_cards 提供字段策略与单条 Canonical 卡结果。"""
def __init__(self):
self.statements = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc, traceback):
return False
def execute(self, statement, params=None):
normalized = " ".join(statement.split())
self.statements.append((normalized, params))
if "muse_meta_schema" in normalized:
return FakeQueryResult([("character", {"秘密": False})])
return FakeQueryResult(
[
(
"entity",
11,
{
"型": "character",
"名称": "甲",
"一句话摘要": "历史角色",
"字段": {
"秘密": "不可见",
"sourceRefs": [source_ref(3, 11)],
"milestones": [{"chapter": 3, "fact": "历史事实"}],
},
},
"active",
0.9,
2,
{"sourceRefs": [source_ref(3, 11)]},
"active",
"active",
)
]
)
class RetrieveWriterSourcesTest(unittest.TestCase):
def setUp(self):
self.plan = build_retrieval_plan(
run_id="run-a",
work_id=8,
target_chapter=6,
as_of=5,
fine_outline={
"entities": [
{"id": "character:甲", "type": "character", "name": "甲"},
{"id": "item:剑", "type": "item", "name": "剑"},
],
"relations": [],
"locations": [],
"powerSystems": [],
"hardConstraints": ["甲使用剑"],
},
card_index_version="cards-v1",
prose_index_version="prose-v1",
token_budget={"maxContextChars": 20000},
)
def test_cards_use_documented_stable_order_and_ties_repeat(self):
cards = [card("3", 0.8, offset=2), card("2", 0.8, offset=1), card("1", 0.9)]
expected = ["1", "2", "3"]
for _ in range(3):
self.assertEqual([item["cardId"] for item in stable_sort_cards(cards)], expected)
def test_plan_identity_excludes_run_id(self):
other = copy.deepcopy(self.plan)
other["runId"] = "run-b"
rebuilt = build_retrieval_plan(
run_id="run-b",
work_id=8,
target_chapter=6,
as_of=5,
fine_outline={
"entities": [
{"id": "character:甲", "type": "character", "name": "甲"},
{"id": "item:剑", "type": "item", "name": "剑"},
],
"relations": [],
"locations": [],
"powerSystems": [],
"hardConstraints": ["甲使用剑"],
},
card_index_version="cards-v1",
prose_index_version="prose-v1",
token_budget={"maxContextChars": 20000},
)
self.assertEqual(self.plan["planId"], rebuilt["planId"])
def test_state_as_of_uses_only_milestones_at_or_before_freeze(self):
frozen = freeze_card(card("1", 1.0), as_of=5)
self.assertEqual([item["chapter"] for item in frozen["stateAsOf"]], [1, 3])
self.assertNotIn("未来终态", str(frozen))
def test_target_future_and_unprovable_sources_fail_closed(self):
for bad_ref in (
source_ref(6, 1),
source_ref(7, 1),
{"sourceId": "unknown", "sourceVersion": SOURCE_VERSION},
):
unsafe = card("1", 1.0)
unsafe["sourceRefs"] = [bad_ref]
with self.subTest(bad_ref=bad_ref), self.assertRaises(RetrievalError):
retrieve_writer_sources(
plan=self.plan,
card_repository=FakeCardRepository([unsafe]),
prose_repository=FakeProseRepository(),
)
def test_cards_expand_to_prose_and_authoritative_facts_keep_independent_refs(self):
prose = FakeProseRepository()
no_ref_card = card("2", 0.7)
no_ref_card["sourceRefs"] = []
result = retrieve_writer_sources(
plan=self.plan,
card_repository=FakeCardRepository([card("1", 0.9), no_ref_card]),
prose_repository=prose,
authoritative_facts=[
{
"factId": "setting:1",
"fact": "正式设定事实",
"sourceType": "formal_setting",
"sourceRef": {
"sourceId": "setting:8",
"sourceVersion": "setting-v2",
},
"riskLevel": "high",
},
{
"factId": "outline:new:1",
"fact": "细纲声明的新事实",
"sourceType": "fine_outline_declared_new",
"sourceRef": {
"sourceId": "fine-outline:6",
"sourceVersion": "outline-v3",
"chapter": 6,
},
"riskLevel": "medium",
},
],
)
self.assertEqual(len(prose.calls[0][2]), 1)
self.assertEqual(result["proseEvidence"][0]["text"], "历史原文")
self.assertEqual(
{item["sourceType"] for item in result["factEvidence"]},
{"historical_prose", "formal_setting", "fine_outline_declared_new"},
)
self.assertEqual(result["unverifiedIndexHints"][0]["cardId"], "2")
def test_snapshot_transaction_is_repeatable_read_and_read_only(self):
connection = FakeConnection()
begin_read_snapshot(connection)
self.assertEqual(
connection.statements[0][0],
"SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY",
)
def test_production_repository_only_accepts_active_canonical_binding(self):
calls = []
def search_function(**kwargs):
calls.append(kwargs)
return [card("1", 0.9)]
repository = ProductionCardIndexRepository(search_function=search_function)
result = repository.search(self.plan)
self.assertEqual(result[0]["cardId"], "1")
self.assertEqual(calls[0]["scope"], "work")
for field, value in (
("sourceKind", "eval_draft"),
("sourceStatus", "draft"),
("bindingStatus", "inactive"),
("productionRetrievalEligible", False),
):
unsafe = card("1", 0.9)
unsafe[field] = value
repository = ProductionCardIndexRepository(search_function=lambda **_: [unsafe])
with self.subTest(field=field), self.assertRaises(RetrievalError):
repository.search(self.plan)
def test_search_cards_reuses_active_entity_and_binding_sql(self):
connection = FakeSearchConnection()
result = search_cards(
"甲的历史状态",
scope="work",
work_id=8,
ttype="character",
purpose="generation",
top=5,
connection_factory=lambda _: connection,
embedder=lambda _: [0.1, 0.2],
)
sql = connection.statements[1][0]
self.assertIn("en.status='active'", sql)
self.assertIn("b.binding_status='active'", sql)
self.assertIn("en.source_action_policy='allowed'", sql)
self.assertEqual(result[0]["sourceKind"], "canonical_entity")
self.assertTrue(result[0]["productionRetrievalEligible"])
self.assertNotIn("秘密", result[0]["visibleFields"])
def test_replay_repository_is_preregistered_upgrade_book_and_never_production_eligible(self):
replay_card = card("11", 0.9)
replay_card.update(
{
"sourceKind": "eval_draft",
"evaluationStatus": "eval_draft",
"sourceType": "upgrade_book",
"productionRetrievalEligible": False,
}
)
replay_config = {
"targetChapter": 6,
"snapshot": {"asOfChapter": 5, "data": {"chapters": [{"chapter": 5, "text": "安全历史"}]}},
"sources": [
{"sourceId": "chapter:5", "sourceVersion": "chapter-v1", "chapter": 5}
],
"authorization": {
"sourceStatus": "active",
"copyrightStatus": "licensed",
"sourceVersion": SOURCE_VERSION,
"allowedPurpose": ["offline_evaluation"],
"authorizationSnapshot": {
"id": "auth-1",
"version": "v1",
"immutable": True,
"sourceVersion": SOURCE_VERSION,
"sourceStatus": "active",
"allowedPurpose": ["offline_evaluation"],
"checkedAt": "2026-07-20T00:00:00Z",
"revalidationAt": "2026-07-21T00:00:00Z",
},
},
"leakageAudit": {
"targetFacts": {"targetChapter": 6, "forbiddenFacts": []}
},
}
repository = ReplayCardIndexRepository.from_replay_config(
replay_config,
cards=[replay_card],
preregistered_card_ids=["11"],
)
self.assertFalse(repository.search(self.plan)[0]["productionRetrievalEligible"])
denied = copy.deepcopy(replay_config)
denied["authorization"] = {}
with self.assertRaises(RetrievalError):
ReplayCardIndexRepository.from_replay_config(
denied,
cards=[replay_card],
preregistered_card_ids=["11"],
)
wrong = copy.deepcopy(replay_card)
wrong["sourceType"] = "extract_chapter"
with self.assertRaises(RetrievalError):
ReplayCardIndexRepository.from_replay_config(
replay_config,
cards=[wrong],
preregistered_card_ids=["11"],
)
with self.assertRaises(RetrievalError):
ReplayCardIndexRepository.from_replay_config(
replay_config,
cards=[replay_card],
preregistered_card_ids=["12"],
)
if __name__ == "__main__":
unittest.main()