fix(scene): bind release gate to current readiness

This commit is contained in:
2026-10-06 13:06:11 +08:00
parent a051131d3c
commit a601bc46a9
4 changed files with 112 additions and 1 deletions
@@ -681,6 +681,9 @@ class DraftingWorkspaceStore:
summary.update({
"source_applicability_verified": readiness.get("source_applicability_verified") is True,
"published_prompt_alignment_verified": readiness.get("published_prompt_alignment_verified") is True,
# This is derived from the complete server-side readiness check.
# No edit, runtime, or API request field is accepted as a shortcut.
"release_readiness_prerequisites_satisfied": readiness.get("prerequisites_satisfied") is True,
"release_readiness": readiness,
})
return summary
@@ -30,6 +30,8 @@ def scene_template_release_gate(
) -> dict:
"""Recompute release readiness from bound evidence, never cached approval flags."""
evidence = model_evidence if isinstance(model_evidence, dict) else {}
readiness = evidence.get("release_readiness")
readiness = readiness if isinstance(readiness, dict) else {}
blockers: list[dict[str, str]] = []
def block(code: str, message: str) -> None:
@@ -89,6 +91,19 @@ def scene_template_release_gate(
block("RELEASE_SOURCE_APPLICABILITY", "场景法源适用性尚未形成独立可核验结论。")
if evidence.get("published_prompt_alignment_verified") is not True:
block("RELEASE_RUNTIME_PROMPT_ALIGNMENT", "评测 Prompt 与拟发布运行时 Prompt 尚未完成哈希对齐。")
readiness_prerequisites_satisfied = (
evidence.get("release_readiness_prerequisites_satisfied") is True
and readiness.get("schema_version") == "scene-release-readiness.v1"
and readiness.get("prerequisites_satisfied") is True
and readiness.get("issues") == []
and readiness.get("source_applicability_verified") is True
and readiness.get("published_prompt_alignment_verified") is True
and readiness.get("missing_draft_variants") == []
and readiness.get("missing_review_variants") == []
and readiness.get("missing_holdout_draft_variants") == []
)
if not readiness_prerequisites_satisfied:
block("RELEASE_READINESS_PREREQUISITES", "服务端当前发布就绪条件尚未全部满足,历史 Prompt 覆盖不能替代当前运行证据。")
bound_payload = {
"policy_version": RELEASE_GATE_POLICY_VERSION,
@@ -103,6 +118,8 @@ def scene_template_release_gate(
for group in complete_review],
"source_applicability_verified": evidence.get("source_applicability_verified") is True,
"published_prompt_alignment_verified": evidence.get("published_prompt_alignment_verified") is True,
"release_readiness_evidence_sha256": draft_digest(readiness) if readiness else None,
"release_readiness_prerequisites_satisfied": readiness_prerequisites_satisfied,
"blocker_codes": [item["code"] for item in blockers],
}
return {
@@ -463,7 +463,9 @@ def test_scene_release_gate_recomputes_all_evidence_and_ignores_cached_approval_
)
assert passed["policy_version"] == RELEASE_GATE_POLICY_VERSION
assert not passed["passed"] and not passed["authorizes_publication"]
assert {item["code"] for item in passed["blockers"]} == {"RELEASE_HOLDOUT_EVIDENCE_MISSING"}
assert {item["code"] for item in passed["blockers"]} == {
"RELEASE_HOLDOUT_EVIDENCE_MISSING", "RELEASE_READINESS_PREREQUISITES",
}
assert len(passed["evidence_sha256"]) == 64
stale = scene_template_release_gate(
@@ -477,6 +479,93 @@ def test_scene_release_gate_recomputes_all_evidence_and_ignores_cached_approval_
})
def test_final_publication_validation_rejects_complete_coverage_from_stale_prompts(monkeypatch):
from yuqei_contract_api.template_evaluation_evidence import (
POLICY_VERSION,
VARIANTS,
deterministic_scene_evidence,
scene_template_release_gate,
)
monkeypatch.setattr(
"yuqei_contract_api.holdout_scene_evidence_registry.verify_scene_holdout_evidence",
lambda evidence, *, current_template_content_sha256: True,
)
repository = InMemoryContractRepository(contracts=[])
template = repository.get_template(SCENE)
draft = repository.get_template_edit_draft(SCENE)
deterministic = deterministic_scene_evidence(template, draft.body_content)
stale_draft_prompt = {
"name": "contract_drafting_v1",
"version": "stale-version",
"prompt_sha256": "c" * 64,
}
stale_review_prompt = {
"name": "contract_review_transport",
"version": "stale-version",
"content_sha256": "d" * 64,
}
stale_readiness = {
"schema_version": "scene-release-readiness.v1",
"issues": [
{"code": "SCENE_DRAFT_COVERAGE_PENDING", "message": "旧 Prompt 起草覆盖不计入当前运行版本。"},
{"code": "SCENE_REVIEW_COVERAGE_PENDING", "message": "旧 Prompt 审查覆盖不计入当前运行版本。"},
],
"source_applicability_verified": True,
"published_prompt_alignment_verified": True,
"missing_draft_variants": list(VARIANTS),
"missing_review_variants": list(VARIANTS),
"missing_holdout_draft_variants": [],
"prerequisites_satisfied": False,
}
evidence = {
"policy_version": POLICY_VERSION,
"template_content_sha256": deterministic["template_content_sha256"],
"current_records": len(VARIANTS),
"failed_or_unverified_records": 0,
"truncated": False,
"coverage_by_prompt": [{
"prompt": stale_draft_prompt,
"passed_variants": list(VARIANTS),
"missing_variants": [],
}],
"review_coverage_by_prompt": [{
"draft_prompt": stale_draft_prompt,
"prompt": stale_review_prompt,
"passed_variants": list(VARIANTS),
"missing_variants": [],
}],
"holdout_registry_evidence": {
"evidence_sha256": "e" * 64,
"current_template_match": True,
"missing_or_failed_variants": [],
},
"source_applicability_verified": True,
"published_prompt_alignment_verified": True,
# A cached or forged top-level claim cannot override server readiness.
"release_readiness_prerequisites_satisfied": True,
"release_readiness": stale_readiness,
"authorizes_publication": True,
}
gate = scene_template_release_gate(
template_content_sha256=deterministic["template_content_sha256"],
deterministic=deterministic,
model_evidence=evidence,
)
assert {item["code"] for item in gate["blockers"]} == {"RELEASE_READINESS_PREREQUISITES"}
assert gate["release_readiness_prerequisites_satisfied"] is False
assert not gate["passed"] and not gate["authorizes_publication"]
repository.get_template_evaluation_summary = lambda template_id: deepcopy(evidence)
validation = repository.validate_template_before_publish(SCENE)
assert not validation.valid
assert validation.evaluation_summary["release_gate"]["blocker_codes"] == [
"RELEASE_READINESS_PREREQUISITES"
]
assert "SCENE_EVALUATION_PENDING" in {issue.code for issue in validation.issues}
def test_evidence_recomputed_against_payload_not_cached_flags(stores):
_, trial = stores
workspace_id, _, _ = attach_stubbed_evaluation(trial)
@@ -106,6 +106,7 @@ def test_summary_top_level_release_flags_follow_only_verified_server_readiness(m
summary = trial.template_evaluation_summary(SCENE, tenant="default")
assert summary["source_applicability_verified"] is True
assert summary["published_prompt_alignment_verified"] is True
assert summary["release_readiness_prerequisites_satisfied"] is summary["release_readiness"]["prerequisites_satisfied"]
assert summary["source_applicability_verified"] is summary["release_readiness"]["source_applicability_verified"]
assert summary["published_prompt_alignment_verified"] is summary["release_readiness"]["published_prompt_alignment_verified"]
@@ -122,6 +123,7 @@ def test_summary_top_level_release_flags_reject_cached_claims_and_bad_runtime_ha
summary = trial.template_evaluation_summary(SCENE, tenant="default")
assert summary["source_applicability_verified"] is False
assert summary["published_prompt_alignment_verified"] is False
assert summary["release_readiness_prerequisites_satisfied"] is False
assert "SCENE_RUNTIME_UNAVAILABLE" in codes(summary["release_readiness"])