zizi 1d32a96399 修复: 收紧回放评测失败关闭与盲化边界
复用目录先原子落盘非成功态,并为三类外部 runner 增加可配置超时。detector 改为真正盲检,安全报告与嵌套响应统一执行严格闭集校验。
2026-07-19 21:17:34 +08:00

125 lines
4.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
"""细纲 detector 的机器可读输入输出合同。"""
from __future__ import annotations
from typing import Any, Mapping
DETECTOR_PROTOCOL = "fine_outline_detector_v0"
ALLOWED_SEVERITIES = frozenset({"high", "medium", "low"})
ALLOWED_FINDING_CATEGORIES = frozenset(
{
"candidate_structure",
"causal_chain",
"entity_state",
"foreshadowing_action",
"source_reference",
"unknowns_discipline",
}
)
ALLOWED_COVERAGE_CATEGORIES = frozenset({"frozen_context_gap"})
FINDING_FIELDS = frozenset({"category", "severity", "location", "evidenceSummary"})
COVERAGE_FINDING_FIELDS = frozenset({"category", "summary"})
def build_detector_request(
*,
candidate_id: str,
candidate: Mapping[str, Any],
as_of_chapter: int,
frozen_snapshot: Mapping[str, Any],
common_context: Mapping[str, Any],
sources: list[Any],
) -> dict[str, Any]:
"""只用公共冻结事实和匿名候选构造真正不识别评测臂的盲检输入。"""
return {
"protocol": DETECTOR_PROTOCOL,
"candidateId": candidate_id,
"asOfChapter": as_of_chapter,
"candidate": dict(candidate),
"planningContext": {
"frozenSnapshot": dict(frozen_snapshot),
"commonContext": dict(common_context),
"sources": list(sources),
},
"rules": {
"referenceProxyVisible": False,
"mayJudgeTargetRoleCardCoverage": False,
"highSeverityBlocksGroup": True,
},
}
def validate_detector_report(
report: Mapping[str, Any], expected_candidate_id: str
) -> dict[str, Any]:
"""机械校验 detector JSON;合同不完整时失败关闭。"""
errors: list[str] = []
if not isinstance(report, Mapping):
return {"ok": False, "errors": ["detector 报告必须是对象"], "highSeverityCount": 0}
if report.get("protocol") != DETECTOR_PROTOCOL:
errors.append(f"protocol 必须是 {DETECTOR_PROTOCOL}")
if report.get("candidateId") != expected_candidate_id:
errors.append("candidateId 与盲检输入不一致")
unexpected = sorted(
set(report) - {"protocol", "candidateId", "findings", "coverageFindings"}
)
if unexpected:
errors.append(f"detector 报告包含未登记字段: {','.join(unexpected)}")
findings = report.get("findings")
coverage_findings = report.get("coverageFindings")
if not isinstance(findings, list):
errors.append("findings 必须是数组")
findings = []
if not isinstance(coverage_findings, list):
errors.append("coverageFindings 必须是数组")
coverage_findings = []
sections = (
("findings", findings, ALLOWED_FINDING_CATEGORIES),
("coverageFindings", coverage_findings, ALLOWED_COVERAGE_CATEGORIES),
)
for section, items, allowed_categories in sections:
for index, finding in enumerate(items):
if not isinstance(finding, Mapping):
errors.append(f"{section}[{index}] 必须是对象")
continue
allowed_fields = FINDING_FIELDS if section == "findings" else COVERAGE_FINDING_FIELDS
unexpected_fields = sorted(set(finding) - allowed_fields)
if unexpected_fields:
errors.append(
f"{section}[{index}] 包含未登记字段: {','.join(unexpected_fields)}"
)
category = finding.get("category")
if not isinstance(category, str) or category not in allowed_categories:
errors.append(f"{section}[{index}].category 未登记")
if section == "findings":
severity = finding.get("severity")
if not isinstance(severity, str) or severity not in ALLOWED_SEVERITIES:
errors.append(f"findings[{index}].severity 无效")
for field in ("category", "location", "evidenceSummary"):
value = finding.get(field)
if not isinstance(value, str) or not value.strip():
errors.append(f"findings[{index}].{field} 必须是非空字符串")
else:
summary = finding.get("summary")
if not isinstance(summary, str) or not summary.strip():
errors.append(f"coverageFindings[{index}].summary 必须是非空字符串")
high_count = sum(
1
for finding in findings
if isinstance(finding, Mapping) and finding.get("severity") == "high"
)
return {
"ok": not errors,
"errors": errors,
"findingCount": len(findings),
"coverageFindingCount": len(coverage_findings),
"highSeverityCount": high_count,
}