fix(scene): bind release gate to current readiness
This commit is contained in:
+3
@@ -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
|
||||
|
||||
+17
@@ -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"])
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user