497 lines
26 KiB
Python
497 lines
26 KiB
Python
"""Build and validate the immutable W4 multiscene evaluation plan."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
from hashlib import sha256
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path, PurePath
|
|
from typing import Any, Mapping
|
|
|
|
from yuqei_contract_api.contract_templates import template_document_kind
|
|
from yuqei_contract_api.drafting_workspace import (
|
|
DraftingCandidateRequest, DraftingCreateRequest, DraftingEvaluationCreateRequest,
|
|
DraftingWorkspaceStore,
|
|
)
|
|
from yuqei_contract_api.in_memory_repository import InMemoryContractRepository
|
|
from yuqei_sdk.contract_drafting import ContractDraftRequest, draft_digest
|
|
|
|
from multiscene_eval_cases import build_cases
|
|
from multiscene_supplemental_cases import build_supplemental_cases
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SCHEMA_VERSION = "multiscene-evaluation-plan.v2"
|
|
CANDIDATE_SCHEMA_VERSION = "multiscene-candidate-binding.v1"
|
|
RELEASE_CONTROLS_SCHEMA_VERSION = "multiscene-release-controls.v1"
|
|
REQUIRED_IMAGE_ROLES = (
|
|
"contract-web", "contract-api", "ai-platform-api", "ai-worker",
|
|
)
|
|
CANARY_CASE_IDS = (
|
|
"base--TPL-AGENCY-DISTRIBUTION-001--normal",
|
|
"base--TPL-DEMAND-LETTER-001--normal",
|
|
"base--TPL-PURCHASE-GOODS-001--normal",
|
|
"scene--TPL-AMENDMENT-GENERAL-001--opposite-adversarial",
|
|
"specialist-trade_procurement-first",
|
|
"intake-tpl-purchase-goods-001",
|
|
)
|
|
_SHA256_RE = re.compile(r"[0-9a-f]{64}\Z")
|
|
_COMMIT_RE = re.compile(r"[0-9a-f]{40}\Z")
|
|
_TAG_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._/-]{0,127}\Z")
|
|
EXPECTED_OPERATIONS = {
|
|
"draft": 120, "semantic_review": 30, "repair_draft": 8,
|
|
"repair_review": 8, "specialist_review": 106, "intake": 8,
|
|
}
|
|
EXPECTED_BUDGET = 280
|
|
PIN_ROOTS = (
|
|
"yuqei-ai-sdk-python/src/yuqei_sdk",
|
|
"yuqei-ai-platform/services/ai-platform-api/src/yuqei_ai_platform_api",
|
|
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api",
|
|
)
|
|
PIN_FILES = (
|
|
"yuqei-infra/scripts/multiscene_eval_cases.py",
|
|
"yuqei-infra/scripts/multiscene_supplemental_cases.py",
|
|
"yuqei-infra/scripts/multiscene_call_ledger.py",
|
|
"yuqei-infra/scripts/multiscene_eval_plan.py",
|
|
"yuqei-infra/scripts/multiscene_eval_runner.py",
|
|
"yuqei-infra/scripts/run-core-drafting-smoke.py",
|
|
"yuqei-contract-platform/services/contract-api/tests/test_semantic_review_cross_service_flow.py",
|
|
"yuqei-ai-sdk-python/pyproject.toml",
|
|
"yuqei-ai-platform/services/ai-platform-api/pyproject.toml",
|
|
"yuqei-contract-platform/services/contract-api/pyproject.toml",
|
|
)
|
|
PIN_SUFFIXES = {".py", ".json", ".md", ".toml", ".yaml", ".yml"}
|
|
|
|
|
|
def _hash_bytes(value: bytes) -> str:
|
|
return sha256(value).hexdigest()
|
|
|
|
|
|
def _canonical(value: Any) -> str:
|
|
return json.dumps(value, ensure_ascii=False, sort_keys=True,
|
|
separators=(",", ":"), allow_nan=False)
|
|
|
|
|
|
def _strict_json(raw: bytes, label: str) -> Any:
|
|
def pairs(items):
|
|
result = {}
|
|
for key, value in items:
|
|
if key in result:
|
|
raise ValueError(f"{label} contains a duplicate JSON key")
|
|
result[key] = value
|
|
return result
|
|
|
|
def constant(value):
|
|
raise ValueError(f"{label} contains non-finite JSON value {value}")
|
|
|
|
try:
|
|
return json.loads(raw, object_pairs_hook=pairs, parse_constant=constant)
|
|
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
raise ValueError(f"{label} is not valid strict JSON") from error
|
|
|
|
|
|
def _require_sha(value: object, label: str) -> str:
|
|
if not isinstance(value, str) or not _SHA256_RE.fullmatch(value):
|
|
raise ValueError(f"{label} must be a lowercase SHA-256 value")
|
|
return value
|
|
|
|
|
|
def _artifact_bytes(path: Path, root: Path, label: str) -> bytes:
|
|
supplied = Path(path)
|
|
if not supplied.is_absolute():
|
|
raise ValueError(f"{label} path must be absolute")
|
|
absolute = supplied.absolute()
|
|
current = absolute
|
|
while True:
|
|
junction = getattr(current, "is_junction", lambda: False)
|
|
if current.is_symlink() or junction():
|
|
raise ValueError(f"{label} path must not traverse links or junctions")
|
|
if current.parent == current:
|
|
break
|
|
current = current.parent
|
|
if not absolute.is_file() or absolute.resolve().is_relative_to(root.resolve()):
|
|
raise ValueError(f"{label} must be a regular file outside the source worktree")
|
|
return absolute.read_bytes()
|
|
|
|
|
|
def _git(root: Path, *args: str, binary: bool = False):
|
|
environment = {**os.environ, "GIT_CONFIG_NOSYSTEM": "1",
|
|
"GIT_CONFIG_GLOBAL": os.devnull, "GIT_OPTIONAL_LOCKS": "0",
|
|
"GIT_NO_REPLACE_OBJECTS": "1"}
|
|
completed = subprocess.run(
|
|
["git", "-C", str(root), *args], capture_output=True,
|
|
text=not binary, check=False, env=environment,
|
|
)
|
|
if completed.returncode:
|
|
error = completed.stderr if isinstance(completed.stderr, str) else completed.stderr.decode("utf-8", "replace")
|
|
raise ValueError(f"git candidate verification failed: {error.strip()}")
|
|
return completed.stdout if binary else completed.stdout.strip()
|
|
|
|
|
|
def _verify_git_candidate(root: Path, commit: str, tag: str, stage: str) -> None:
|
|
if not _COMMIT_RE.fullmatch(commit):
|
|
raise ValueError("candidate commit must be a full lowercase SHA-1 object ID")
|
|
if (not _TAG_RE.fullmatch(tag) or ".." in tag or "//" in tag
|
|
or tag.endswith((".", "/")) or tag.endswith(".lock")):
|
|
raise ValueError("candidate tag is unsafe")
|
|
head = _git(root, "rev-parse", "--verify", "HEAD^{commit}")
|
|
tagged = _git(root, "rev-parse", "--verify", f"refs/tags/{tag}^{{commit}}")
|
|
if head != commit or tagged != commit:
|
|
raise ValueError(f"candidate HEAD/tag mismatch {stage}")
|
|
if _git(root, "status", "--porcelain=v1", "-z", "--untracked-files=all"):
|
|
raise ValueError(f"candidate worktree is not clean {stage}")
|
|
|
|
|
|
def _source_manifest(payload: Any, commit: str) -> tuple[dict[str, str], str]:
|
|
expected = {"schema_version", "candidate", "files", "summaries",
|
|
"claims_not_made", "manifest_payload_sha256"}
|
|
if not isinstance(payload, dict) or set(payload) != expected:
|
|
raise ValueError("source manifest shape is invalid")
|
|
if payload["schema_version"] != "yuqei.release-source-manifest.v1":
|
|
raise ValueError("unsupported source manifest schema")
|
|
embedded = _require_sha(payload["manifest_payload_sha256"], "source manifest payload SHA-256")
|
|
projection = {key: value for key, value in payload.items()
|
|
if key != "manifest_payload_sha256"}
|
|
if _hash_bytes(_canonical(projection).encode("utf-8")) != embedded:
|
|
raise ValueError("source manifest embedded payload SHA-256 mismatch")
|
|
candidate = payload["candidate"]
|
|
if (not isinstance(candidate, dict) or candidate.get("commit") != commit
|
|
or candidate.get("worktree_was_clean") is not True
|
|
or candidate.get("identity_policy") != "full_commit_oid_equals_HEAD"):
|
|
raise ValueError("source manifest candidate identity mismatch")
|
|
files = payload["files"]
|
|
if not isinstance(files, dict) or not isinstance(files.get("items"), list):
|
|
raise ValueError("source manifest file inventory is invalid")
|
|
if files.get("count") != len(files["items"]):
|
|
raise ValueError("source manifest file count is inconsistent")
|
|
index: dict[str, str] = {}
|
|
for row in files["items"]:
|
|
if not isinstance(row, dict) or set(row) != {
|
|
"path", "mode", "git_blob_oid", "size", "sha256",
|
|
}:
|
|
raise ValueError("source manifest file row is invalid")
|
|
relative = row["path"]
|
|
if (not isinstance(relative, str) or relative.startswith(("/", "\\"))
|
|
or ".." in PurePath(relative).parts or relative in index):
|
|
raise ValueError("source manifest contains an unsafe or duplicate path")
|
|
index[relative] = _require_sha(row["sha256"], f"source manifest {relative}")
|
|
return index, embedded
|
|
|
|
|
|
def _image_identities(payload: Any, commit: str, tag: str) -> tuple[list[dict[str, str]], str, str]:
|
|
if not isinstance(payload, dict):
|
|
raise ValueError("image identities must be an object")
|
|
schema = payload.get("schema_version")
|
|
if schema == "yuqei.rc06-image-identities.v1":
|
|
if set(payload) != {"schema_version", "commit", "tag", "images"}:
|
|
raise ValueError("image identities shape is invalid")
|
|
document_commit, document_tag, images = payload["commit"], payload["tag"], payload["images"]
|
|
elif schema == "yuqei.installable-release-manifest.v1":
|
|
candidate = payload.get("candidate")
|
|
if not isinstance(candidate, dict):
|
|
raise ValueError("installable image candidate is missing")
|
|
document_commit, document_tag, images = candidate.get("commit"), candidate.get("tag"), payload.get("images")
|
|
else:
|
|
raise ValueError("unsupported image identities schema")
|
|
if document_commit != commit or document_tag != tag:
|
|
raise ValueError("image identities candidate does not match commit/tag")
|
|
if not isinstance(images, list) or len(images) != len(REQUIRED_IMAGE_ROLES):
|
|
raise ValueError("image identities must contain four application images")
|
|
normalized, roles, manifests = [], set(), set()
|
|
for row in images:
|
|
if not isinstance(row, dict):
|
|
raise ValueError("image identity row is invalid")
|
|
if (schema == "yuqei.rc06-image-identities.v1"
|
|
and set(row) != {"role", "repository", "manifest_digest", "config_digest"}):
|
|
raise ValueError("compact image identity row shape is invalid")
|
|
role = row.get("role")
|
|
repository = row.get("repository") if schema == "yuqei.rc06-image-identities.v1" else (row.get("image_name") or row.get("registry_reference"))
|
|
manifest, config = row.get("manifest_digest"), row.get("config_digest")
|
|
if (not isinstance(role, str) or not isinstance(repository, str) or not repository
|
|
or not isinstance(manifest, str) or not manifest.startswith("sha256:")
|
|
or not isinstance(config, str) or not config.startswith("sha256:")):
|
|
raise ValueError("image identity row is incomplete")
|
|
_require_sha(manifest.removeprefix("sha256:"), f"image {role} manifest digest")
|
|
_require_sha(config.removeprefix("sha256:"), f"image {role} config digest")
|
|
if role in roles or manifest in manifests:
|
|
raise ValueError("duplicate image role or manifest digest")
|
|
roles.add(role)
|
|
manifests.add(manifest)
|
|
normalized.append({"role": role, "repository": repository,
|
|
"manifest_digest": manifest, "config_digest": config})
|
|
if roles != set(REQUIRED_IMAGE_ROLES):
|
|
raise ValueError("image roles mismatch")
|
|
normalized.sort(key=lambda item: REQUIRED_IMAGE_ROLES.index(item["role"]))
|
|
return normalized, _hash_bytes(_canonical(normalized).encode("utf-8")), schema
|
|
|
|
|
|
def load_candidate_binding(runner, *, commit: str, tag: str,
|
|
source_manifest_path: Path, source_manifest_sha256: str,
|
|
source_manifest_payload_sha256: str, image_identities_path: Path,
|
|
image_identities_sha256: str, image_identity_set_sha256: str,
|
|
stage: str = "candidate preflight") -> tuple[dict[str, Any], dict[str, str]]:
|
|
"""Load and reverify a built immutable candidate without creating evidence."""
|
|
root = Path(runner.ROOT).resolve()
|
|
_verify_git_candidate(root, commit, tag, stage)
|
|
source_raw = _artifact_bytes(source_manifest_path, root, "source manifest")
|
|
image_raw = _artifact_bytes(image_identities_path, root, "image identities")
|
|
if _hash_bytes(source_raw) != _require_sha(source_manifest_sha256, "source manifest raw SHA-256"):
|
|
raise ValueError("source manifest raw SHA-256 mismatch")
|
|
if _hash_bytes(image_raw) != _require_sha(image_identities_sha256, "image identities raw SHA-256"):
|
|
raise ValueError("image identities raw SHA-256 mismatch")
|
|
source_index, embedded = _source_manifest(_strict_json(source_raw, "source manifest"), commit)
|
|
if embedded != _require_sha(source_manifest_payload_sha256, "source manifest payload SHA-256"):
|
|
raise ValueError("source manifest payload SHA-256 argument mismatch")
|
|
images, identity_set, image_schema = _image_identities(
|
|
_strict_json(image_raw, "image identities"), commit, tag)
|
|
if identity_set != _require_sha(image_identity_set_sha256, "image identity-set SHA-256"):
|
|
raise ValueError("image identity-set SHA-256 mismatch")
|
|
binding = {
|
|
"schema_version": CANDIDATE_SCHEMA_VERSION, "commit": commit, "tag": tag,
|
|
"source_manifest": {"schema_version": "yuqei.release-source-manifest.v1",
|
|
"raw_sha256": source_manifest_sha256,
|
|
"payload_sha256": source_manifest_payload_sha256},
|
|
"image_identities": {"schema_version": image_schema,
|
|
"raw_sha256": image_identities_sha256,
|
|
"identity_set_sha256": image_identity_set_sha256,
|
|
"images": images},
|
|
}
|
|
return binding, source_index
|
|
|
|
|
|
def _candidate_binding(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
if not isinstance(value, Mapping) or value.get("schema_version") != CANDIDATE_SCHEMA_VERSION:
|
|
raise ValueError("candidate binding is missing or unsupported")
|
|
result = json.loads(_canonical(dict(value)))
|
|
if set(result) != {"schema_version", "commit", "tag", "source_manifest", "image_identities"}:
|
|
raise ValueError("candidate binding shape is invalid")
|
|
if not _COMMIT_RE.fullmatch(str(result.get("commit", ""))):
|
|
raise ValueError("candidate binding commit is invalid")
|
|
if (not isinstance(result.get("tag"), str) or not _TAG_RE.fullmatch(result["tag"])
|
|
or ".." in result["tag"] or "//" in result["tag"]):
|
|
raise ValueError("candidate binding tag is invalid")
|
|
source, images = result.get("source_manifest"), result.get("image_identities")
|
|
if not isinstance(source, dict) or not isinstance(images, dict):
|
|
raise ValueError("candidate binding artifacts are invalid")
|
|
if set(source) != {"schema_version", "raw_sha256", "payload_sha256"}:
|
|
raise ValueError("candidate source binding shape is invalid")
|
|
if source["schema_version"] != "yuqei.release-source-manifest.v1":
|
|
raise ValueError("candidate source binding schema is invalid")
|
|
if set(images) != {"schema_version", "raw_sha256", "identity_set_sha256", "images"}:
|
|
raise ValueError("candidate image binding shape is invalid")
|
|
if images["schema_version"] not in {
|
|
"yuqei.rc06-image-identities.v1", "yuqei.installable-release-manifest.v1",
|
|
}:
|
|
raise ValueError("candidate image binding schema is invalid")
|
|
_require_sha(source.get("raw_sha256"), "candidate source raw SHA-256")
|
|
_require_sha(source.get("payload_sha256"), "candidate source payload SHA-256")
|
|
_require_sha(images.get("raw_sha256"), "candidate image raw SHA-256")
|
|
_require_sha(images.get("identity_set_sha256"), "candidate image set SHA-256")
|
|
if not isinstance(images.get("images"), list) or len(images["images"]) != 4:
|
|
raise ValueError("candidate binding must include four image identities")
|
|
normalized, roles, manifests = [], set(), set()
|
|
for row in images["images"]:
|
|
if not isinstance(row, dict) or set(row) != {
|
|
"role", "repository", "manifest_digest", "config_digest",
|
|
}:
|
|
raise ValueError("candidate normalized image row is invalid")
|
|
role, manifest, config = row["role"], row["manifest_digest"], row["config_digest"]
|
|
if role not in REQUIRED_IMAGE_ROLES or role in roles or manifest in manifests:
|
|
raise ValueError("candidate image role or manifest digest is invalid")
|
|
if not isinstance(row["repository"], str) or not row["repository"]:
|
|
raise ValueError("candidate image repository is invalid")
|
|
if not isinstance(manifest, str) or not manifest.startswith("sha256:"):
|
|
raise ValueError("candidate image manifest digest is invalid")
|
|
if not isinstance(config, str) or not config.startswith("sha256:"):
|
|
raise ValueError("candidate image config digest is invalid")
|
|
_require_sha(manifest.removeprefix("sha256:"), "candidate image manifest digest")
|
|
_require_sha(config.removeprefix("sha256:"), "candidate image config digest")
|
|
roles.add(role); manifests.add(manifest); normalized.append(row)
|
|
if roles != set(REQUIRED_IMAGE_ROLES):
|
|
raise ValueError("candidate image roles mismatch")
|
|
normalized.sort(key=lambda item: REQUIRED_IMAGE_ROLES.index(item["role"]))
|
|
if images["images"] != normalized:
|
|
raise ValueError("candidate image identities are not canonically ordered")
|
|
if _hash_bytes(_canonical(normalized).encode("utf-8")) != images["identity_set_sha256"]:
|
|
raise ValueError("candidate image identity-set digest is invalid")
|
|
return result
|
|
|
|
|
|
def verify_source_pins_at_candidate(runner, source_pins: Mapping[str, str],
|
|
source_index: Mapping[str, str], commit: str) -> None:
|
|
root = Path(runner.ROOT).resolve()
|
|
for relative, digest in source_pins.items():
|
|
if source_index.get(relative) != digest:
|
|
raise ValueError(f"source pin is absent from or differs from manifest: {relative}")
|
|
committed = _git(root, "show", f"{commit}:{relative}", binary=True)
|
|
if _hash_bytes(committed) != digest:
|
|
raise ValueError(f"source pin differs from candidate commit: {relative}")
|
|
|
|
|
|
def draft_request_projection(request: ContractDraftRequest | dict[str, Any]) -> dict[str, Any]:
|
|
"""Return stable model facts and template identities; omit workspace/lifecycle data."""
|
|
request = ContractDraftRequest.model_validate(request)
|
|
data = request.model_dump(mode="json")
|
|
snapshot = data["template_snapshot"]
|
|
return {
|
|
"document_kind": data["document_kind"], "title": data["contract_title"],
|
|
"contract_type": data["contract_type"], "industry": data["industry"],
|
|
"party_position": data["party_position"], "jurisdiction": data["jurisdiction"],
|
|
"transaction_stage": data["transaction_stage"], "facts": data["facts"],
|
|
"missing_facts": data["missing_facts"], "base_document": data["base_document"],
|
|
"instructions": data["drafting_instructions"],
|
|
"template_content_sha256": snapshot.get("content_sha256"),
|
|
"template_schema_sha256": snapshot.get("schema_sha256"),
|
|
}
|
|
|
|
|
|
def _source_pins(runner) -> dict[str, str]:
|
|
paths: set[Path] = set()
|
|
for relative in PIN_ROOTS:
|
|
root = ROOT / relative
|
|
paths.update(path for path in root.rglob("*") if path.is_file()
|
|
and path.suffix.lower() in PIN_SUFFIXES
|
|
and "__pycache__" not in path.parts)
|
|
paths.update(ROOT / relative for relative in PIN_FILES)
|
|
runner_file = Path(getattr(runner, "__file__", ""))
|
|
if runner_file.is_file():
|
|
paths.add(runner_file.resolve())
|
|
cross = getattr(runner, "CROSS", None)
|
|
cross_file = Path(getattr(cross, "__file__", ""))
|
|
if cross_file.is_file():
|
|
paths.add(cross_file.resolve())
|
|
missing = [str(path.relative_to(ROOT)) for path in paths if not path.is_file()]
|
|
if missing:
|
|
raise ValueError(f"source pin file missing: {missing}")
|
|
return {path.relative_to(ROOT).as_posix(): _hash_bytes(path.read_bytes())
|
|
for path in sorted(paths)}
|
|
|
|
|
|
def _prepared_projection(case: dict[str, Any], repository: InMemoryContractRepository) -> tuple[dict[str, Any], Any]:
|
|
# Base cases need the real contract policy (including auto repair); scene
|
|
# release-gate cases must remain in the isolated evaluation entry point.
|
|
evaluation = case["mode"] == "scene_evaluation"
|
|
store = DraftingWorkspaceStore(repository, mode="scene_evaluation" if evaluation else "contract")
|
|
template, _ = (store.evaluation_source(case["template_id"]) if evaluation
|
|
else repository.get_template_source(case["template_id"]))
|
|
request_type = DraftingEvaluationCreateRequest if evaluation else DraftingCreateRequest
|
|
context = case.get("scene_request_context", {})
|
|
created = request_type(
|
|
template_id=case["template_id"], expected_template_content_sha256=template.content_sha256,
|
|
title=case["title"], values=case["values"], industry=context.get("industry", "未指定"),
|
|
party_position=context.get("party_position", "中立"),
|
|
jurisdiction=context.get("jurisdiction", "中国大陆"),
|
|
transaction_stage=context.get("transaction_stage", "drafting"),
|
|
instructions=case["instructions"], **({"synthetic_only": True} if evaluation else {}),
|
|
)
|
|
record = store.create(created, tenant="w4-isolated", owner="w4-builder",
|
|
key=f"create-{case['case_id']}")
|
|
policy = {"track_changes": True, "semantic_review": not evaluation,
|
|
"auto_repair": case["auto_repair"]}
|
|
_, candidate = store.prepare(record.id, DraftingCandidateRequest(
|
|
expected_revision=record.revision, idempotency_key=f"candidate-{draft_digest(case['case_id'])}",
|
|
**policy), tenant="w4-isolated", owner="w4-builder")
|
|
projection = draft_request_projection(candidate.model_request)
|
|
if projection["document_kind"] != case["document_kind"]:
|
|
raise ValueError(f"document kind drift: {case['case_id']}")
|
|
actual_missing = [item["field"] for item in projection["missing_facts"]]
|
|
expected_missing = case["expectation_profile"]["expected_missing_fields"]
|
|
actual_status = "incomplete" if actual_missing else "candidate_ready"
|
|
if actual_missing != expected_missing or actual_status != case["expectation_profile"]["expected_status"]:
|
|
raise ValueError(f"prepared missing/status drift: {case['case_id']}")
|
|
return projection, template
|
|
|
|
|
|
def _normalized_cases() -> list[dict[str, Any]]:
|
|
repository = InMemoryContractRepository(contracts=[])
|
|
repository.allow_internal_trial_templates = True
|
|
drafting = []
|
|
for case in build_cases():
|
|
projection, template = _prepared_projection(case, repository)
|
|
policy = {"track_changes": True, "semantic_review": case["mode"] == "contract",
|
|
"auto_repair": case["auto_repair"]}
|
|
failure_class = ("scene_drafting" if case["mode"] == "scene_evaluation"
|
|
else "contract_drafting")
|
|
drafting.append({**case, "failure_class": failure_class,
|
|
"candidate_policy": policy,
|
|
"template_content_sha256": projection["template_content_sha256"],
|
|
"template_input_schema_sha256": projection["template_schema_sha256"],
|
|
"values_sha256": draft_digest(case["values"]),
|
|
"draft_request_projection": projection})
|
|
supplemental = []
|
|
for case in build_supplemental_cases():
|
|
supplemental.append({
|
|
"case_id": case["case_id"], "source_case_id": case["case_id"],
|
|
"kind": case["operation"], "mode": case["operation"], "operation": case["operation"],
|
|
"operations": [case["operation"]], "provider_call_budget": 1,
|
|
"failure_class": case["operation"],
|
|
"request": case["request"].model_dump(mode="json"),
|
|
"request_sha256": case["request_sha256"], "expectations": case["expectations"],
|
|
**({"module": case["module"], "party_position": case["party_position"]}
|
|
if case["operation"] == "specialist_review" else {}),
|
|
})
|
|
return [*drafting, *supplemental]
|
|
|
|
|
|
def _operation_counts(cases: list[dict[str, Any]]) -> dict[str, int]:
|
|
result: dict[str, int] = {}
|
|
for case in cases:
|
|
for operation in case["operations"]:
|
|
result[operation] = result.get(operation, 0) + 1
|
|
return result
|
|
|
|
|
|
def build_plan(runner, candidate_binding: Mapping[str, Any]) -> dict[str, Any]:
|
|
cases = _normalized_cases()
|
|
counts = _operation_counts(cases)
|
|
if len(cases) != 144 or counts != EXPECTED_OPERATIONS:
|
|
raise ValueError(f"case or operation inventory mismatch: {len(cases)}, {counts}")
|
|
if sum(case["provider_call_budget"] for case in cases) != EXPECTED_BUDGET:
|
|
raise ValueError("global provider call budget mismatch")
|
|
inventory = {case["case_id"]: case for case in cases}
|
|
if any(case_id not in inventory for case_id in CANARY_CASE_IDS):
|
|
raise ValueError("predefined RC-05 canary inventory drift")
|
|
return {
|
|
"schema_version": SCHEMA_VERSION, "prepared_at": datetime.now(UTC).isoformat(),
|
|
"model": "gpt-5.6-sol", "reasoning_effort": "medium", "timeout_seconds": 300,
|
|
"case_count": 144, "drafting_case_count": 120, "supplemental_case_count": 24,
|
|
"operation_limits": counts, "total_provider_call_budget": EXPECTED_BUDGET,
|
|
"candidate": _candidate_binding(candidate_binding),
|
|
"release_controls": {
|
|
"schema_version": RELEASE_CONTROLS_SCHEMA_VERSION,
|
|
"canary_case_ids": list(CANARY_CASE_IDS),
|
|
"failure_classes": {case["case_id"]: case["failure_class"] for case in cases},
|
|
},
|
|
"source_pins": _source_pins(runner), "cases": cases,
|
|
"limitations": ["合成隔离评测,不是生产发布或法律批准。",
|
|
"deterministic projection equality does not establish semantic or legal correctness."],
|
|
}
|
|
|
|
|
|
def validate_plan(runner, path: Path, expected_sha: str | None = None,
|
|
candidate_binding: Mapping[str, Any] | None = None,
|
|
source_manifest_index: Mapping[str, str] | None = None) -> tuple[dict[str, Any], str]:
|
|
raw = Path(path).read_bytes()
|
|
actual = _hash_bytes(raw)
|
|
if expected_sha is not None and actual != expected_sha:
|
|
raise ValueError("plan raw SHA-256 mismatch")
|
|
plan = _strict_json(raw, "multiscene plan")
|
|
if plan.get("schema_version") != SCHEMA_VERSION:
|
|
raise ValueError("legacy/unsupported multiscene plan is read-only diagnostic material; execution refused")
|
|
frozen_candidate = _candidate_binding(plan.get("candidate"))
|
|
if candidate_binding is not None and _canonical(frozen_candidate) != _canonical(
|
|
_candidate_binding(candidate_binding)):
|
|
raise ValueError("multiscene plan candidate binding mismatch")
|
|
expected = build_plan(runner, frozen_candidate)
|
|
left = {key: value for key, value in plan.items() if key != "prepared_at"}
|
|
right = {key: value for key, value in expected.items() if key != "prepared_at"}
|
|
if _canonical(left) != _canonical(right):
|
|
raise ValueError("multiscene plan content or source pins drift")
|
|
if source_manifest_index is not None:
|
|
verify_source_pins_at_candidate(
|
|
runner, plan["source_pins"], source_manifest_index,
|
|
frozen_candidate["commit"])
|
|
return plan, actual
|