muse-agent-example/tests/skills/评估内容质量/test_run_writer_blind_judge.py

600 lines
28 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
"""blind judge v4 输入/报告、v3 模型输出和 fresh panel 测试。"""
from __future__ import annotations
import copy
import hashlib
import json
import pathlib
import sys
import unittest
from collections.abc import Mapping
from typing import Any
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
SCRIPT_DIR = PROJECT_ROOT / "muse" / "lifecycle" / "quality" / "skills" / "judge" / "评估内容质量" / "scripts"
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
from run_writer_blind_judge import ( # noqa: E402
BLIND_JUDGE_REPORT_JSON_SCHEMA,
BlindJudgeContractError,
_quote_location,
canonical_sha256,
run_writer_blind_judge,
run_writer_blind_judge_panel,
validate_blind_judge_input,
validate_blind_judge_report,
)
from writer_rubric import ( # noqa: E402
COMMON_DIMENSIONS,
DIMENSIONS,
RUBRIC_POLICY_VERSION,
SCENARIO_DIMENSION,
)
def _candidate_hash(body: str) -> str:
return "sha256:" + hashlib.sha256(body.encode("utf-8")).hexdigest()
def _truth_pack() -> dict[str, Any]:
pack: dict[str, Any] = {
"schemaVersion": "oracle-truth-pack-v1",
"evaluationSetVersion": "writer-gate-a-v1",
"sampleId": "sample-489",
"workId": 8,
"asOf": 488,
"sourceSnapshotSha256": "sha256:" + "1" * 64,
"authorizationSnapshotId": "authorization-offline-1",
"historicalAssertions": [{
"assertionId": "assertion-history-1", "text": "林澈在城门", "sourceVersion": "v488",
"chapterStart": 488, "chapterEnd": 488, "contentSha256": "sha256:" + "2" * 64,
}],
"targetAssertions": [],
}
pack["packSha256"] = canonical_sha256(pack)
return pack
def blind_input(reviewer_id: str, *, order: tuple[str, ...] = ("blind-1", "blind-2")) -> dict[str, Any]:
bodies = {"blind-1": "林澈守住城门。", "blind-2": "林澈退回城内。"}
pack = _truth_pack()
payload: dict[str, Any] = {
"schemaVersion": "writer-blind-input-v4",
"runId": "run-judge-1",
"sampleId": "sample-489",
"scenario": "turning_point",
"rubricPolicyVersion": RUBRIC_POLICY_VERSION,
"reviewerInvocationId": reviewer_id,
"candidateOrder": list(order),
"candidates": [{"blindCandidateId": item, "candidateSha256": _candidate_hash(bodies[item]), "candidateBody": bodies[item]} for item in order],
"fineOutline": {
"sourceRef": {"sourceId": "fine-outline:489", "sourceVersion": "v1", "chapter": 489},
"hardConstraints": [{"constraintId": "constraint-1", "text": "必须守住城门"}],
"adjustableBeats": [],
"declaredNewFacts": [],
},
"oracleTruthPack": pack,
"oracleTruthPackSha256": pack["packSha256"],
"authorizationSnapshotId": "authorization-offline-1",
}
payload["blindInputSha256"] = canonical_sha256(payload)
return payload
def blind_draft(payload: Mapping[str, Any], *, score: float = 8.0) -> dict[str, Any]:
ids = list(payload["candidateOrder"])
quotes = {item["blindCandidateId"]: item["candidateBody"][:2] for item in payload["candidates"]}
return {
"schemaVersion": "blind-judge-draft-v3",
"candidateScores": [{
"blindCandidateId": blind_id,
"scores": {dimension: {
"score": score,
"reason": "候选正文支持该维判断",
"candidateQuote": quotes[blind_id],
"evidenceRefs": [{"sourceType": "candidate", "sourceId": blind_id}],
} for dimension in DIMENSIONS},
} for blind_id in ids],
"dimensionPreferences": [{"dimension": dimension, "orderedCandidateIds": ids, "reason": "第一候选更符合共同目标"} for dimension in DIMENSIONS],
"oracleAssertionVerdicts": [{
"assertionId": "assertion-history-1", "blindCandidateId": blind_id,
"verdict": "pass", "reason": "未违背冻结事实", "candidateQuote": quotes[blind_id],
"evidenceRefs": [{"sourceType": "oracle_assertion", "sourceId": "assertion-history-1"}],
} for blind_id in ids],
"hardConstraintVerdicts": [{
"constraintId": "constraint-1", "blindCandidateId": blind_id,
"verdict": "pass", "reason": "满足守城目标", "candidateQuote": quotes[blind_id],
"evidenceRefs": [{"sourceType": "fine_outline", "sourceId": "constraint-1"}],
} for blind_id in ids],
}
class SequenceRunner:
def __init__(self, outputs: list[Any]) -> None:
self.outputs = [
item if isinstance(item, BaseException) else copy.deepcopy(dict(item))
for item in outputs
]
self.calls: list[dict[str, Any]] = []
def run(self, *, adapter_role: str, model_input: Mapping[str, Any], output_schema: Mapping[str, Any]) -> Mapping[str, Any]:
self.calls.append({"role": adapter_role, "input": copy.deepcopy(dict(model_input)), "schema": output_schema})
output = self.outputs[len(self.calls) - 1]
if isinstance(output, BaseException):
raise output
return {"structuredOutput": output, "modelReceiptSha256": "sha256:" + str(len(self.calls)) * 64}
class BlindJudgeV3Test(unittest.TestCase):
def test_model_schema_excludes_hash_offset_identity_and_receipt(self) -> None:
schema = BLIND_JUDGE_REPORT_JSON_SCHEMA
self.assertFalse(schema["additionalProperties"])
forbidden = {"runId", "sampleId", "reviewerInvocationId", "candidateSha256", "blindInputSha256", "modelReceiptSha256", "reportSha256", "startCodePoint", "endCodePoint"}
self.assertTrue(forbidden.isdisjoint(schema["properties"]))
forged = blind_draft(blind_input("reviewer-1"))
forged["candidateSha256"] = "sha256:" + "f" * 64
result = run_writer_blind_judge(
blind_input("reviewer-1"),
model_runner=SequenceRunner([forged]),
max_corrections=0,
)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID")
def test_adapter_sends_anonymous_common_input_and_binds_report(self) -> None:
payload = blind_input("reviewer-1")
runner = SequenceRunner([blind_draft(payload)])
result = run_writer_blind_judge(payload, model_runner=runner)
self.assertTrue(result["ok"])
report = result["report"]
self.assertEqual(report["schemaVersion"], "blind-judge-report-v4")
self.assertEqual(report["scenario"], "turning_point")
self.assertEqual(report["rubricPolicyVersion"], RUBRIC_POLICY_VERSION)
self.assertEqual(report["reviewerInvocationId"], "reviewer-1")
self.assertEqual(report["candidateScores"][0]["candidateSha256"], payload["candidates"][0]["candidateSha256"])
self.assertEqual(report["reportSha256"], canonical_sha256({k: v for k, v in report.items() if k != "reportSha256"}))
model_input = runner.calls[0]["input"]
self.assertEqual(
set(model_input),
{
"candidates",
"fineOutline",
"oracleTruthPack",
"rubric",
"outputContract",
"candidateQuoteHints",
},
)
self.assertEqual(
model_input["outputContract"]["expectedCounts"],
{
"candidateScores": 2,
"dimensionPreferences": len(DIMENSIONS),
"oracleAssertionVerdicts": 2,
"hardConstraintVerdicts": 2,
},
)
self.assertEqual(
model_input["candidateQuoteHints"]["blind-1"], ["林澈守住城门。"]
)
self.assertEqual(model_input["rubric"]["policyVersion"], RUBRIC_POLICY_VERSION)
self.assertEqual(model_input["rubric"]["scenarioType"], "turning_point")
self.assertEqual(
[item["dimensionId"] for item in model_input["rubric"]["commonDimensions"]],
list(COMMON_DIMENSIONS),
)
self.assertEqual(
model_input["rubric"]["scenarioDimension"]["dimensionId"],
SCENARIO_DIMENSION,
)
self.assertEqual(
model_input["rubric"]["scenarioDimension"]["displayName"],
"转折执行",
)
serialized = str(model_input)
for forbidden in ("runId", "sampleId", "reviewerInvocationId", "candidateSha256", "blindInputSha256", "authorizationSnapshotId"):
self.assertNotIn(forbidden, serialized)
def test_unregistered_scenario_and_policy_drift_fail_before_model_call(self) -> None:
for field, value in (
("scenario", "romance"),
("rubricPolicyVersion", "writer-replay-rubric-v1"),
):
payload = blind_input("reviewer-1")
payload[field] = value
payload["blindInputSha256"] = canonical_sha256(
{key: item for key, item in payload.items() if key != "blindInputSha256"}
)
runner = SequenceRunner([])
result = run_writer_blind_judge(payload, model_runner=runner)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_INPUT_SCHEMA_INVALID")
self.assertEqual(runner.calls, [])
def test_adapter_computes_quote_offsets(self) -> None:
payload = blind_input("reviewer-1")
result = run_writer_blind_judge(payload, model_runner=SequenceRunner([blind_draft(payload)]))
evidence = result["report"]["candidateScores"][0]["scores"][DIMENSIONS[0]]["evidence"][0]
self.assertEqual(evidence["sourceRef"], "candidate:0-2")
self.assertEqual(evidence["excerpt"], "林澈")
def test_model_id_order_is_normalized_without_relaxing_coverage(self) -> None:
"""完整 ID 集乱序时确定性重排,缺失/重复仍失败关闭。"""
payload = blind_input("reviewer-1")
draft = blind_draft(payload)
draft["candidateScores"].reverse()
draft["dimensionPreferences"].reverse()
draft["oracleAssertionVerdicts"].reverse()
draft["hardConstraintVerdicts"].reverse()
result = run_writer_blind_judge(payload, model_runner=SequenceRunner([draft]))
self.assertTrue(result["ok"], result)
self.assertEqual(
[item["blindCandidateId"] for item in result["report"]["candidateScores"]],
list(payload["candidateOrder"]),
)
self.assertEqual(
[item["dimension"] for item in result["report"]["dimensionPreferences"]],
list(DIMENSIONS),
)
def test_invalid_quote_gets_one_bounded_correction(self) -> None:
"""首轮编造引文时回喂原稿与错误,第二轮合法即可完成。"""
payload = blind_input("reviewer-1")
invalid = blind_draft(payload)
invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][
"candidateQuote"
] = "候选正文中不存在的引文"
runner = SequenceRunner([invalid, blind_draft(payload)])
result = run_writer_blind_judge(
payload,
model_runner=runner,
max_corrections=1,
)
self.assertTrue(result["ok"], result)
self.assertEqual(result["attemptCount"], 2)
self.assertEqual(len(runner.calls), 2)
self.assertNotIn("correction", runner.calls[0]["input"])
correction = runner.calls[1]["input"]["correction"]
self.assertEqual(correction["previousDraft"], invalid)
self.assertIn("引文未在对应候选中出现", correction["error"])
self.assertIn("林澈守住城门。", correction["validCandidateQuoteHints"]["blind-1"])
def test_invalid_quote_correction_exhaustion_fails_closed(self) -> None:
"""一次纠错仍编造引文时保留稳定错误码,不继续第三次调用。"""
payload = blind_input("reviewer-1")
invalid = blind_draft(payload)
invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][
"candidateQuote"
] = "候选正文中不存在的引文"
runner = SequenceRunner([invalid, invalid])
result = run_writer_blind_judge(
payload,
model_runner=runner,
max_corrections=1,
)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_QUOTE_NOT_FOUND")
self.assertEqual(result["attemptCount"], 2)
self.assertEqual(len(runner.calls), 2)
diagnostic = result["safeDiagnostic"]
self.assertEqual(diagnostic["errorCode"], "BLIND_JUDGE_QUOTE_NOT_FOUND")
self.assertEqual(diagnostic["arrayCounts"]["candidateScores"], 2)
self.assertNotIn("候选正文中不存在的引文", json.dumps(diagnostic, ensure_ascii=False))
def test_judge_inference_refs_bind_to_current_dimension_or_stable_id(self) -> None:
"""推断证据必须引用当前维度/断言/约束 ID,不能引用字段名占位符。"""
payload = blind_input("reviewer-1")
draft = blind_draft(payload)
for candidate in draft["candidateScores"]:
for dimension in DIMENSIONS:
candidate["scores"][dimension]["evidenceRefs"] = [
{"sourceType": "judge_inference", "sourceId": dimension}
]
for verdict in draft["oracleAssertionVerdicts"]:
verdict["evidenceRefs"] = [
{"sourceType": "judge_inference", "sourceId": verdict["assertionId"]}
]
for verdict in draft["hardConstraintVerdicts"]:
verdict["evidenceRefs"] = [
{"sourceType": "judge_inference", "sourceId": verdict["constraintId"]}
]
result = run_writer_blind_judge(
payload, model_runner=SequenceRunner([draft]), max_corrections=0
)
self.assertTrue(result["ok"], result)
invalid = copy.deepcopy(draft)
invalid["oracleAssertionVerdicts"][0]["evidenceRefs"][0]["sourceId"] = (
"assertionId"
)
failed = run_writer_blind_judge(
payload, model_runner=SequenceRunner([invalid]), max_corrections=0
)
self.assertFalse(failed["ok"])
self.assertEqual(failed["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID")
def test_transient_api_error_retries_same_blind_input_once(self) -> None:
"""API 瞬时错误与格式纠错共用两次总调用上限,不伪造 correction。"""
payload = blind_input("reviewer-1")
runner = SequenceRunner(
[
BlindJudgeContractError("BLIND_JUDGE_API_ERROR", "瞬时 API 错误"),
blind_draft(payload),
]
)
result = run_writer_blind_judge(payload, model_runner=runner)
self.assertTrue(result["ok"], result)
self.assertEqual(result["attemptCount"], 2)
self.assertEqual(len(runner.calls), 2)
self.assertNotIn("correction", runner.calls[0]["input"])
self.assertNotIn("correction", runner.calls[1]["input"])
def test_old_v3_v2_and_v1_reports_fail_closed(self) -> None:
payload = blind_input("reviewer-1")
for version in (
"blind-judge-report-v3",
"blind-judge-report-v2",
"blind-judge-report-v1",
):
with self.subTest(version=version), self.assertRaises(BlindJudgeContractError):
validate_blind_judge_report({"schemaVersion": version}, payload)
def test_input_rejects_real_arm_and_stale_candidate_hash(self) -> None:
leaked = blind_input("reviewer-1")
leaked["candidateOrder"][0] = "A"
leaked["candidates"][0]["blindCandidateId"] = "A"
leaked["blindInputSha256"] = canonical_sha256({k: v for k, v in leaked.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError):
validate_blind_judge_input(leaked)
stale = blind_input("reviewer-1")
stale["candidates"][0]["candidateBody"] += "旧"
stale["blindInputSha256"] = canonical_sha256({k: v for k, v in stale.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError) as caught:
validate_blind_judge_input(stale)
self.assertEqual(caught.exception.code, "BLIND_JUDGE_CANDIDATE_HASH_MISMATCH")
def test_fine_outline_and_oracle_freeze_validation_remain_fail_closed(self) -> None:
raw_source = blind_input("reviewer-1")
raw_source["fineOutline"]["sourceRef"]["sourceId"] = "/private/tmp/raw/outline.json"
raw_source["blindInputSha256"] = canonical_sha256({k: v for k, v in raw_source.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError) as caught:
validate_blind_judge_input(raw_source)
self.assertEqual(caught.exception.code, "BLIND_JUDGE_LEAKAGE_DETECTED")
future = blind_input("reviewer-1")
future["oracleTruthPack"]["historicalAssertions"][0]["chapterEnd"] = 489
future["oracleTruthPack"]["packSha256"] = canonical_sha256({k: v for k, v in future["oracleTruthPack"].items() if k != "packSha256"})
future["oracleTruthPackSha256"] = future["oracleTruthPack"]["packSha256"]
future["blindInputSha256"] = canonical_sha256({k: v for k, v in future.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError):
validate_blind_judge_input(future)
nested_extra = blind_input("reviewer-1")
nested_extra["fineOutline"]["hardConstraints"][0]["debug"] = True
nested_extra["blindInputSha256"] = canonical_sha256({k: v for k, v in nested_extra.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError):
validate_blind_judge_input(nested_extra)
def test_model_must_cover_full_verdict_cartesian_product(self) -> None:
payload = blind_input("reviewer-1")
draft = blind_draft(payload)
draft["oracleAssertionVerdicts"].pop()
result = run_writer_blind_judge(
payload,
model_runner=SequenceRunner([draft]),
max_corrections=0,
)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_MODEL_OUTPUT_INVALID")
def test_two_reviewers_are_fresh_reversed_and_share_no_state(self) -> None:
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
runner = SequenceRunner([blind_draft(first), blind_draft(second)])
result = run_writer_blind_judge_panel(first, second, model_runner=runner, persist_lesson=False)
self.assertTrue(result["ok"])
self.assertEqual(result["reviewCount"], 2)
self.assertEqual(len(runner.calls), 2)
self.assertEqual(runner.calls[0]["input"]["candidates"][0]["blindCandidateId"], "blind-1")
self.assertEqual(runner.calls[1]["input"]["candidates"][0]["blindCandidateId"], "blind-2")
self.assertNotIn("previousReport", runner.calls[1]["input"])
def test_panel_allows_two_corrections_per_reviewer(self) -> None:
"""面板必须把 Gate A 的三次总调用合同传给每位评委。"""
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
invalid = blind_draft(first)
invalid["candidateScores"][0]["scores"][DIMENSIONS[0]][
"candidateQuote"
] = "候选正文中不存在的引文"
runner = SequenceRunner([
invalid,
invalid,
blind_draft(first),
blind_draft(second),
])
result = run_writer_blind_judge_panel(first, second, model_runner=runner, persist_lesson=False)
self.assertTrue(result["ok"], result)
self.assertEqual(result["reviewCount"], 2)
self.assertEqual(len(runner.calls), 4)
self.assertIn("correction", runner.calls[1]["input"])
self.assertIn("correction", runner.calls[2]["input"])
self.assertNotIn("correction", runner.calls[3]["input"])
def test_reviewer_identity_reuse_fails_before_call(self) -> None:
first = blind_input("reviewer-1")
reused = blind_input("reviewer-1", order=("blind-2", "blind-1"))
runner = SequenceRunner([])
result = run_writer_blind_judge_panel(first, reused, model_runner=runner, persist_lesson=False)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "JUDGE_INVALID")
self.assertEqual(runner.calls, [])
def test_reviewers_with_different_scenarios_fail_before_call(self) -> None:
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
second["scenario"] = "battle"
second["blindInputSha256"] = canonical_sha256(
{key: value for key, value in second.items() if key != "blindInputSha256"}
)
runner = SequenceRunner([])
result = run_writer_blind_judge_panel(first, second, model_runner=runner, persist_lesson=False)
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "JUDGE_INVALID")
self.assertEqual(runner.calls, [])
def test_pre_call_input_failure_preserves_specific_contract_code(self) -> None:
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
first["candidates"][0]["candidateBody"] += "篡改"
first["blindInputSha256"] = canonical_sha256(
{key: value for key, value in first.items() if key != "blindInputSha256"}
)
runner = SequenceRunner([])
result = run_writer_blind_judge_panel(first, second, model_runner=runner, persist_lesson=False)
self.assertFalse(result["ok"])
self.assertEqual(result["status"], "failed_judge_invalid")
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_CANDIDATE_HASH_MISMATCH")
self.assertEqual(result["reviewCount"], 0)
self.assertEqual(runner.calls, [])
def test_score_instability_triggers_exactly_one_third_reviewer(self) -> None:
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
third = blind_input("reviewer-3")
runner = SequenceRunner([blind_draft(first, score=8.0), blind_draft(second, score=6.5), blind_draft(third, score=8.0)])
result = run_writer_blind_judge_panel(first, second, third_input=third, model_runner=runner, persist_lesson=False)
self.assertTrue(result["ok"])
self.assertEqual(result["reviewCount"], 3)
self.assertEqual(len(runner.calls), 3)
def test_runner_must_supply_receipt_hash(self) -> None:
class DirectRunner:
def run(self, **_kwargs: Any) -> Mapping[str, Any]:
return blind_draft(blind_input("reviewer-1"))
result = run_writer_blind_judge(blind_input("reviewer-1"), model_runner=DirectRunner())
self.assertFalse(result["ok"])
self.assertEqual(result["primaryCode"], "BLIND_JUDGE_RECEIPT_BINDING_MISMATCH")
def test_ellipsis_in_free_text_not_killed_but_real_traversal_blocked(self) -> None:
# 候选正文、评委理由、引文均含省略号 `...`:旧的 `".." in value` 子串判定会把它们误判为
# 路径穿越而冤杀;改用精确路径分量判定后应正常通过校验,不报 BLIND_JUDGE_LEAKAGE_DETECTED。
bodies = {"blind-1": "林澈说:等等...别走。", "blind-2": "林澈说:慢着...且听我说完。"}
payload = blind_input("reviewer-1")
for item in payload["candidates"]:
item["candidateBody"] = bodies[item["blindCandidateId"]]
item["candidateSha256"] = _candidate_hash(bodies[item["blindCandidateId"]])
payload["blindInputSha256"] = canonical_sha256({k: v for k, v in payload.items() if k != "blindInputSha256"})
# 输入校验放行:含省略号的正文不被当路径穿越。
normalized = validate_blind_judge_input(payload)
self.assertEqual(normalized["_candidateBodies"]["blind-1"], bodies["blind-1"])
# 评委理由与引文也含省略号:完整跑通盲评,不报泄露、正常出报告。
ellipsis_quotes = {"blind-1": "等等...", "blind-2": "慢着..."}
draft = blind_draft(payload)
for cand in draft["candidateScores"]:
for dimension in DIMENSIONS:
cand["scores"][dimension]["reason"] = "节奏张力足够...情绪递进自然"
cand["scores"][dimension]["candidateQuote"] = ellipsis_quotes[cand["blindCandidateId"]]
result = run_writer_blind_judge(payload, model_runner=SequenceRunner([draft]))
self.assertTrue(result["ok"])
self.assertNotEqual(result.get("primaryCode"), "BLIND_JUDGE_LEAKAGE_DETECTED")
first_dim = DIMENSIONS[0]
self.assertEqual(result["report"]["candidateScores"][0]["scores"][first_dim]["evidence"][0]["excerpt"], "等等...")
# 反例:sourceId 含真实路径穿越 `a/../b`(`..` 作为独立路径分量)仍被精确判定拦下。
traversal = blind_input("reviewer-1")
traversal["fineOutline"]["sourceRef"]["sourceId"] = "a/../b"
traversal["blindInputSha256"] = canonical_sha256({k: v for k, v in traversal.items() if k != "blindInputSha256"})
with self.assertRaises(BlindJudgeContractError) as caught:
validate_blind_judge_input(traversal)
self.assertEqual(caught.exception.code, "BLIND_JUDGE_LEAKAGE_DETECTED")
def test_three_reviewers_pairwise_unstable_fail_closed_without_winner(self) -> None:
# 三评评分两两差 >0.5(7.0 / 8.0 / 9.0),无任何稳定配对:必须失败关闭为
# JUDGE_UNSTABLE / failed_judge_unstable,不得强行给出报告、胜负或方向结论。
first = blind_input("reviewer-1")
second = blind_input("reviewer-2", order=("blind-2", "blind-1"))
third = blind_input("reviewer-3")
runner = SequenceRunner([
blind_draft(first, score=7.0),
blind_draft(second, score=8.0),
blind_draft(third, score=9.0),
])
result = run_writer_blind_judge_panel(first, second, third_input=third, model_runner=runner, persist_lesson=False)
self.assertFalse(result["ok"])
self.assertFalse(result["acceptanceEligible"])
self.assertEqual(result["primaryCode"], "JUDGE_UNSTABLE")
self.assertEqual(result["status"], "failed_judge_unstable")
self.assertEqual(result["reviewCount"], 3)
self.assertEqual(len(runner.calls), 3)
# 失败关闭:不产出报告,也没有任何稳定仲裁/方向结论字段。
self.assertNotIn("report", result)
self.assertNotIn("candidateScores", result)
self.assertNotEqual(result.get("status"), "adjudicated_report")
class QuoteLocationTest(unittest.TestCase):
"""盲评 _quote_location 的引文定位:0 次失败关闭,≥1 次绑定首次出现。"""
def test_quote_appearing_once_returns_exact_offsets(self) -> None:
# 情形①:引文在候选正文里恰好出现 1 次 → 正常返回精确 start/end。
body = "林澈守住城门。"
text, start, end = _quote_location(body, "城门", "$.candidateScores[0].scores.x.candidateQuote")
self.assertEqual(text, "城门")
self.assertEqual(start, body.index("城门"))
self.assertEqual((start, end), (4, 6))
self.assertEqual(body[start:end], "城门")
def test_quote_appearing_multiple_times_binds_to_first_occurrence(self) -> None:
# 情形②:引文出现 ≥2 次 → 不再报错,由确定性代码绑定到首次出现位置。
body = "城门连着城门。"
text, start, end = _quote_location(body, "城门", "$.candidateScores[0].scores.x.candidateQuote")
self.assertEqual(text, "城门")
self.assertEqual(start, body.index("城门")) # 首次出现处
self.assertEqual((start, end), (0, 2))
self.assertEqual(body[start:end], "城门")
def test_quote_absent_fails_closed_as_fabricated_evidence(self) -> None:
# 情形③:引文 0 次 = 编造证据 → 失败关闭,码 BLIND_JUDGE_QUOTE_NOT_FOUND。
body = "林澈守住城门。"
with self.assertRaises(BlindJudgeContractError) as caught:
_quote_location(body, "退路", "$.candidateScores[0].scores.x.candidateQuote")
self.assertEqual(caught.exception.code, "BLIND_JUDGE_QUOTE_NOT_FOUND")
if __name__ == "__main__":
unittest.main()