release: freeze RC-05-RC-08 closeout candidate

This commit is contained in:
2026-09-21 14:39:17 +08:00
parent cac183b2ea
commit c4af0c7862
97 changed files with 21394 additions and 177 deletions
@@ -7,9 +7,10 @@ from __future__ import annotations
import hashlib
import json
from typing import Any, Literal
import re
from typing import Annotated, Any, Literal
from pydantic import BaseModel, ConfigDict, Field, model_serializer, model_validator
from pydantic import AfterValidator, BaseModel, ConfigDict, Field, model_serializer, model_validator
from .contract_drafting import DraftQualityFinding
from .draft_change_evidence import DraftChangeDeclarations, _diff_ranges
@@ -17,6 +18,23 @@ from .draft_change_evidence import DraftChangeDeclarations, _diff_ranges
DRAFT_SEMANTIC_REVIEW_POLICY = "draft-semantic-review.v1"
RELATION_CATEGORIES = ("party", "duty", "trigger", "deadline", "written_form", "exception", "liability", "remedy")
ADVISORY_FIELDS = ("assumptions", "risk_notes", "change_explanations", "quality_findings")
SEMANTIC_ENTITY_ID_PATTERN = r"^[a-z][a-z0-9_-]{0,63}$"
SemanticEntityId = Annotated[
str,
Field(pattern=SEMANTIC_ENTITY_ID_PATTERN),
]
def _validate_semantic_entity_reference(value: str) -> str:
if re.fullmatch(SEMANTIC_ENTITY_ID_PATTERN, value) is None:
raise ValueError("Semantic entity references must use canonical lowercase identifiers.")
return value
SemanticEntityReference = Annotated[
str,
AfterValidator(_validate_semantic_entity_reference),
]
def _canonical(value: Any) -> str:
@@ -174,7 +192,7 @@ class DraftSemanticTargetCheck(_Strict):
base_occurrence: int | None = Field(ge=1, le=1000)
candidate_excerpt: str | None = Field(max_length=4000)
candidate_occurrence: int | None = Field(ge=1, le=1000)
finding_ids: list[str] = Field(max_length=100)
finding_ids: list[SemanticEntityReference] = Field(max_length=100)
@model_validator(mode="after")
def nonblank(self):
@@ -187,7 +205,7 @@ class DraftSemanticCandidateCheck(_Strict):
target_id: str
candidate_excerpt: str = Field(min_length=1, max_length=4000)
candidate_occurrence: int | None = Field(ge=1, le=1000)
finding_ids: list[str] = Field(max_length=100)
finding_ids: list[SemanticEntityReference] = Field(max_length=100)
@model_validator(mode="after")
def nonblank(self):
@@ -200,7 +218,7 @@ class DraftSemanticAdvisoryCheck(_Strict):
field: Literal["assumptions", "risk_notes", "change_explanations", "quality_findings"]
index: int = Field(ge=0, le=99)
exact_excerpt: str = Field(min_length=1, max_length=10000)
metadata_finding_ids: list[str] = Field(max_length=100)
metadata_finding_ids: list[SemanticEntityReference] = Field(max_length=100)
class DraftSemanticCoverage(_Strict):
@@ -213,7 +231,7 @@ class DraftSemanticCoverage(_Strict):
class DraftSemanticFinding(_Strict):
id: str = Field(pattern=r"^[a-z][a-z0-9_-]{0,63}$")
id: SemanticEntityId
category: Literal["party", "duty", "trigger", "deadline", "written_form", "exception", "liability", "remedy"]
disposition: Literal["preserved", "supported_change", "omitted", "reversed", "unsupported_change", "ambiguous"]
severity: Literal["info", "warning", "error"]
@@ -238,8 +256,8 @@ class DraftSemanticFinding(_Strict):
class DraftSemanticRepairInstruction(_Strict):
finding_ids: list[str] = Field(max_length=50)
metadata_finding_ids: list[str] = Field(max_length=50)
finding_ids: list[SemanticEntityReference] = Field(max_length=50)
metadata_finding_ids: list[SemanticEntityReference] = Field(max_length=50)
action: str = Field(min_length=1, max_length=2000)
constraints: list[str] = Field(min_length=1, max_length=50)
@@ -251,7 +269,7 @@ class DraftSemanticRepairInstruction(_Strict):
class DraftSemanticMetadataFinding(_Strict):
id: str = Field(pattern=r"^[a-z][a-z0-9_-]{0,63}$")
id: SemanticEntityId
field: Literal["assumptions", "risk_notes", "change_explanations", "quality_findings"]
index: int = Field(ge=0, le=99)
exact_excerpt: str = Field(min_length=1, max_length=10000)
@@ -262,7 +280,7 @@ class DraftSemanticMetadataFinding(_Strict):
class DraftRepairResolutionCheck(_Strict):
finding_id: str = Field(pattern=r"^[a-z][a-z0-9_-]{0,63}$")
finding_id: SemanticEntityReference = Field(pattern=SEMANTIC_ENTITY_ID_PATTERN)
status: Literal["resolved", "unresolved", "uncertain"]
candidate_excerpt: str = Field(min_length=1, max_length=4000)
candidate_occurrence: int | None = Field(ge=1, le=1000)
@@ -58,6 +58,17 @@ def semantic_wire_output_schema(
node = schema["$defs"][definition]
node["properties"].pop(leaf)
node["required"] = [name for name in node["required"] if name != leaf]
identifier_pattern = schema["$defs"]["DraftSemanticFinding"]["properties"]["id"]["pattern"]
for definition, field in (
("DraftSemanticTargetCheck", "finding_ids"),
("DraftSemanticCandidateCheck", "finding_ids"),
("DraftSemanticRepairInstruction", "finding_ids"),
("DraftSemanticAdvisoryCheck", "metadata_finding_ids"),
("DraftSemanticRepairInstruction", "metadata_finding_ids"),
):
schema["$defs"][definition]["properties"][field]["items"]["pattern"] = (
identifier_pattern
)
return schema
@@ -1,4 +1,5 @@
import pytest
from test_support.external_qa import require_external_qa
import json
from pathlib import Path
from yuqei_sdk.contract_intake import (ContractIntakeRequest, ContractIntakeOutput,
@@ -77,6 +78,10 @@ def test_ambiguous_money_rejects_empty_or_invalid_candidates(value_json):
assert validate_intake_output(request(), result)
def test_postfix_regression_replays_run01_failure_without_mutating_artifact():
require_external_qa(
"template-prompt-audit-20260828/tp06-11-intake/run-01/INTAKE-003-ambiguous-injection.json",
"template-prompt-audit-20260828/tp06-11-intake/frozen-v1.json",
)
root=Path(__file__).resolve().parents[2]
artifact_path=root/"docs/qa/template-prompt-audit-20260828/tp06-11-intake/run-01/INTAKE-003-ambiguous-injection.json"
frozen_path=root/"docs/qa/template-prompt-audit-20260828/tp06-11-intake/frozen-v1.json"
@@ -88,6 +93,10 @@ def test_postfix_regression_replays_run01_failure_without_mutating_artifact():
assert artifact_path.read_bytes()==before and artifact["assessment"]["passed"] is False
def test_question_completion_replays_known_postfix_missing_failure_without_rewriting_evidence():
require_external_qa(
"template-prompt-audit-20260828/tp06-12/intake-postfix-run-01/INTAKE-002-missing.json",
"template-prompt-audit-20260828/tp06-11-intake/frozen-v1.json",
)
root=Path(__file__).resolve().parents[2]
artifact_path=root/"docs/qa/template-prompt-audit-20260828/tp06-12/intake-postfix-run-01/INTAKE-002-missing.json"
frozen_path=root/"docs/qa/template-prompt-audit-20260828/tp06-11-intake/frozen-v1.json"
@@ -4,6 +4,7 @@ from pathlib import Path
import pytest
from pydantic import ValidationError
from test_support.external_qa import require_external_qa
from yuqei_sdk.contract_drafting import (
ContractDraftOutput, ContractDraftRequest, draft_digest, draft_output_schema,
@@ -87,6 +88,10 @@ def test_whole_paragraph_replace_accepts_internal_pure_insert_or_delete(candidat
def test_saved_real_whole_paragraph_replacements_bind_to_their_internal_edits():
require_external_qa(
"multiscene-closeout-20260910/draft-timeout-diagnostic-real-v1/01-draft-before.json",
"multiscene-closeout-20260910/draft-timeout-diagnostic-real-v1/01-draft-after.json",
)
root = Path(__file__).resolve().parents[2]
evidence_dir = root / "docs/qa/multiscene-closeout-20260910/draft-timeout-diagnostic-real-v1"
before = json.loads((evidence_dir / "01-draft-before.json").read_text(encoding="utf-8"))
@@ -182,6 +182,58 @@ def test_provider_schema_is_strict_required_nullable_and_runtime_rejects_invalid
DraftSemanticReviewOutput.model_validate({"schema_version": "draft-semantic-review.v1", "status": "passed"})
def test_finding_reference_runtime_rejects_case_changed_aliases_without_schema_drift():
schema = draft_semantic_review_output_schema()
for definition, field in (
("DraftSemanticTargetCheck", "finding_ids"),
("DraftSemanticCandidateCheck", "finding_ids"),
("DraftSemanticRepairInstruction", "finding_ids"),
("DraftSemanticAdvisoryCheck", "metadata_finding_ids"),
("DraftSemanticRepairInstruction", "metadata_finding_ids"),
):
assert schema["$defs"][definition]["properties"][field]["items"] == {
"type": "string"
}
req = request()
payload = output(req, findings=[finding(id="find-001")]).model_dump(mode="json")
payload["coverage"]["target_checks"][0]["finding_ids"] = ["F-001"]
payload["coverage"]["candidate_checks"][0]["finding_ids"] = ["F-001"]
with pytest.raises(ValidationError):
DraftSemanticReviewOutput.model_validate(payload)
metadata_payload = output(request()).model_dump(mode="json")
metadata_payload["coverage"]["advisory_checks"][0]["metadata_finding_ids"] = ["M-001"]
with pytest.raises(ValidationError):
DraftSemanticReviewOutput.model_validate(metadata_payload)
def test_lowercase_but_unknown_finding_references_remain_fail_closed():
req = request()
out = output(req, findings=[finding(id="find-001")])
out.coverage.target_checks[0].finding_ids = ["find-002"]
out.coverage.candidate_checks[0].finding_ids = ["find-002"]
assessment = assess_draft_semantic_review(req, out)
assert assessment.status == "invalid"
assert "target_check_finding_invalid" in assessment.errors
assert "candidate_check_finding_invalid" in assessment.errors
def test_exact_case_sensitive_finding_references_remain_valid():
req = request()
out = output(req, findings=[finding(id="find-001")])
out.coverage.target_checks[0].finding_ids = ["find-001"]
out.coverage.candidate_checks[0].finding_ids = ["find-001"]
assessment = assess_draft_semantic_review(req, out)
assert assessment.status == "valid"
assert "target_check_finding_invalid" not in assessment.errors
assert "candidate_check_finding_invalid" not in assessment.errors
def test_passed_cannot_omit_or_forge_target_and_advisory_item_coverage():
req = request(); out = output(req)
out.coverage.target_checks = []
@@ -144,6 +144,36 @@ def test_v2_schema_removes_only_three_mechanical_leaves_and_fixes_version():
assert "candidate_excerpt" in schema["$defs"]["DraftSemanticTargetCheck"]["properties"]
def test_v2_schema_and_decoder_reject_case_changed_finding_aliases():
req = request()
schema = semantic_wire_output_schema(req, WIRE_V2)
identifier_pattern = schema["$defs"]["DraftSemanticFinding"]["properties"]["id"]["pattern"]
assert schema["$defs"]["DraftSemanticTargetCheck"]["properties"]["finding_ids"]["items"]["pattern"] \
== identifier_pattern
assert schema["$defs"]["DraftSemanticCandidateCheck"]["properties"]["finding_ids"]["items"]["pattern"] \
== identifier_pattern
payload = wire(req)
payload["findings"] = [{
"id": "find-001", "category": "duty", "disposition": "preserved", "severity": "info",
"base_excerpt": req.base_document, "base_occurrence": 1,
"candidate_excerpt": req.candidate_document, "candidate_occurrence": 1,
"fact_fields": ["付款安排"], "clause_ids": [], "change_declaration_ids": [],
"instruction_excerpt": "保留付款义务。", "explanation": "付款义务已保留。",
"question_field": None,
}]
payload["coverage"]["target_checks"][0]["finding_ids"] = ["F-001"]
payload["coverage"]["candidate_checks"][0]["finding_ids"] = ["F-001"]
with pytest.raises(ValueError):
decode_semantic_wire(req, json.dumps(payload, ensure_ascii=False), WIRE_V2)
payload["coverage"]["target_checks"][0]["finding_ids"] = ["find-001"]
payload["coverage"]["candidate_checks"][0]["finding_ids"] = ["find-001"]
decoded = decode_semantic_wire(req, json.dumps(payload, ensure_ascii=False), WIRE_V2)
assert decoded.coverage.target_checks[0].finding_ids == ["find-001"]
assert decoded.coverage.candidate_checks[0].finding_ids == ["find-001"]
def test_v2_hydrates_only_exact_frozen_leaf_values_and_preserves_judgment():
req = request()
payload = wire(req)
@@ -17,6 +17,43 @@ def test_real_catalog_has_bounded_batch_a_and_complete_batch_b_coverage():
assert entry["complete"] is (len(entry["cases"]) == 3)
def test_catalog_embeds_variant_and_hash_provenance_without_external_evidence():
catalog_path = Path(coverage_module.__file__).with_name("scene_review_coverage.json")
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
expected_variants = {"normal", "missing-key-fact", "opposite-adversarial"}
def is_sha256(value):
return (
isinstance(value, str)
and len(value) == 64
and set(value) <= set("0123456789abcdef")
)
assert catalog["schema_version"] == "scene-review-coverage-catalog.v1"
assert len(catalog["entries"]) == 30
for template_id, entry in catalog["entries"].items():
assert entry["template_id"] == template_id
assert set(entry["required_variants"]) == expected_variants
assert set(entry["passed_variants"]) | set(entry["missing_variants"]) == expected_variants
assert set(entry["passed_variants"]).isdisjoint(entry["missing_variants"])
assert entry["source_hashes"]
assert all(is_sha256(value) for value in entry["source_hashes"].values())
assert is_sha256(entry["template_content_sha256"])
for prompt_key in ("draft_prompt", "review_prompt"):
assert is_sha256(entry[prompt_key]["content_sha256"])
for case in entry["cases"]:
assert case["variant"] in entry["passed_variants"]
for hash_key in (
"request_sha256",
"artifact_raw_sha256",
"artifact_canonical_sha256",
"template_content_sha256",
):
assert is_sha256(case[hash_key])
for prompt_key in ("draft_prompt", "review_prompt"):
assert is_sha256(case[prompt_key]["content_sha256"])
def test_lookup_returns_defensive_copies():
first = coverage_module.scene_review_coverage("TPL-TRANSPORT-ORDINARY-001")
assert first is not None