修复: 闭合正文候选丢弃与交接合同

This commit is contained in:
zizi 2026-07-20 20:59:41 +08:00
parent efb2b3e781
commit de0f870b96
3 changed files with 31 additions and 2 deletions

View File

@ -299,11 +299,27 @@ def check_writer_acceptance(
if confirmed is not True:
raise AcceptanceError("CONFIRMATION_REQUIRED", f"{decision} 必须由用户明确确认")
if decision == "discard":
try:
normalized_context = validate_writer_context(context)
except ContractError as exc:
raise AcceptanceError("CONTEXT_CONTRACT_INVALID", str(exc)) from exc
try:
normalized_candidate = validate_writer_output(candidate)
except ContractError as exc:
raise AcceptanceError("CANDIDATE_CONTRACT_INVALID", str(exc)) from exc
_validate_context_binding(normalized_context, normalized_candidate)
return {
"schemaVersion": "writer-acceptance-result-v1",
"status": "DISCARD_INTENT_READY",
"decision": decision,
"commandIntent": None,
"runId": normalized_candidate["runId"],
"candidateVersion": normalized_candidate["candidateVersion"],
"candidateSha256": normalized_candidate["candidateSha256"],
"commandIntent": {
"command": "close_shadow_candidate",
"expectedCandidateVersion": normalized_candidate["candidateVersion"],
"expectedCandidateSha256": normalized_candidate["candidateSha256"],
},
"canonicalMutationPerformed": False,
}
shadow = check_shadow_ready(

View File

@ -269,7 +269,7 @@ def _terminal_result(
) -> dict[str, Any]:
"""生成可原子落盘的稳定终态结果。"""
return {
result = {
"schemaVersion": "writer-pipeline-result-v1",
"runId": run_id,
"status": status,
@ -281,6 +281,10 @@ def _terminal_result(
"rewriteCount": rewrite_count,
"trace": [dict(item) for item in trace],
}
# 只有通过全部合同和审查门的终态才暴露可进入 Shadow 的最终候选。
if status == "PASSED" and candidate is not None:
result["candidateArtifact"] = dict(candidate)
return result
def _publish_if_requested(
@ -625,6 +629,9 @@ def run_writer_pipeline(
"mechanicalPassed": mechanical_report.get("passed", False),
"semanticStatus": semantic_report.get("status") if semantic_report else "not_run",
"failureCodes": [item.get("code") for item in candidate_failures],
# 保留接受层审计所需的原始报告;摘要字段仍供稳定绑定检查使用。
"mechanicalReport": dict(mechanical_report),
"semanticReport": dict(semantic_report) if semantic_report else None,
}
)
if not candidate_failures:

View File

@ -95,6 +95,7 @@ class RunWriterPipelineTest(unittest.TestCase):
self.assertEqual(store.latest(context["runId"]).state, "REJECTED")
self.assertEqual(raised.exception.result["status"], "REJECTED")
self.assertEqual(raised.exception.result["failureCode"], expected_code)
self.assertNotIn("candidateArtifact", raised.exception.result)
self.assertEqual(
json.loads(result_path.read_text(encoding="utf-8")),
raised.exception.result,
@ -442,6 +443,11 @@ class RunWriterPipelineTest(unittest.TestCase):
)
self.assertEqual(result["status"], "PASSED")
self.assertEqual(result["candidateArtifact"]["candidateVersion"], 2)
self.assertEqual(
result["candidateArtifact"]["candidateSha256"],
result["candidateSha256"],
)
self.assertEqual(result["evidenceRequestCount"], 1)
self.assertEqual(result["rewriteCount"], 1)
self.assertEqual(calls, [(1, 1), (2, 2)])