506 lines
18 KiB
Python
506 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""refresh_runtime_probe 的 provider-neutral 纯离线测试。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
import pathlib
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from typing import Any, Mapping
|
|
from unittest import mock
|
|
|
|
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
|
|
SKILLS_DIR = PROJECT_ROOT / ".agent" / "skills"
|
|
SCRIPT_DIR = PROJECT_ROOT / "muse" / "platform" / "llm" / "skills" / "验证角色运行能力" / "scripts"
|
|
WRITER_REPLAY_DIR = PROJECT_ROOT / "muse" / "lifecycle" / "quality" / "skills" / "replay" / "回放评估正文质量" / "scripts"
|
|
GATE_ADJUDICATION_DIR = PROJECT_ROOT / "muse" / "lifecycle" / "quality" / "skills" / "mechanical" / "判定质量是否合格" / "scripts"
|
|
for _import_dir in (SCRIPT_DIR, WRITER_REPLAY_DIR, GATE_ADJUDICATION_DIR):
|
|
if str(_import_dir) not in sys.path:
|
|
sys.path.insert(0, str(_import_dir))
|
|
|
|
import refresh_runtime_probe as refresh_module # noqa: E402
|
|
from refresh_runtime_probe import ( # noqa: E402
|
|
PROBE_BUSINESS_INPUT,
|
|
PROBE_SCHEMA_VERSION,
|
|
ProbeRefreshError,
|
|
apply_probe,
|
|
build_probe_record,
|
|
build_writer_profile,
|
|
make_dry_run_invoker,
|
|
refresh_runtime_probe,
|
|
verify_probe_result,
|
|
)
|
|
import run_writer_replay as replay_module # noqa: E402
|
|
from run_writer_replay import ( # noqa: E402
|
|
WriterReplayProductionAdapters,
|
|
_validate_execute_authorization,
|
|
profile_from_mapping,
|
|
)
|
|
from gate_input_builder import ( # noqa: E402
|
|
GateInputBuildError,
|
|
_verify_self_hash,
|
|
canonical_sha256,
|
|
)
|
|
from muse_role import ( # noqa: E402
|
|
HASH_PATTERN,
|
|
MODEL_POLICY_ALIAS,
|
|
MODEL_POLICY_VERSION,
|
|
RUNTIME_ADAPTER,
|
|
RUNTIME_ADAPTER_VERSION,
|
|
RoleExecutionProfile,
|
|
RoleExecutionReceipt,
|
|
RoleInvocationResult,
|
|
RoleRuntimeError,
|
|
sha256_json,
|
|
)
|
|
|
|
CONFIG_PATH = WRITER_REPLAY_DIR.parent / "configs" / "writer-gate-a-deep-space-v1-cn-skills.json"
|
|
CHECKED_AT = "2026-08-21T00:00:00+00:00"
|
|
EXPECTED_PROBE_FIELDS = {
|
|
"schemaVersion",
|
|
"status",
|
|
"checkedAt",
|
|
"runtimeAdapter",
|
|
"runtimeAdapterVersion",
|
|
"modelPolicyVersion",
|
|
"role",
|
|
"profileVersion",
|
|
"modelAlias",
|
|
"resolvedModelId",
|
|
"executionProfileSha256",
|
|
"jsonSchemaId",
|
|
"jsonSchemaSha256",
|
|
"systemPromptId",
|
|
"systemPromptSha256",
|
|
"inputSha256",
|
|
"requestedModelId",
|
|
"actualModelId",
|
|
"modelMatch",
|
|
"executionReceiptSha256",
|
|
"structuredOutputSha256",
|
|
"terminalReason",
|
|
"totalCostUsd",
|
|
"receiptSha256",
|
|
}
|
|
|
|
|
|
def _load_base_config() -> dict[str, Any]:
|
|
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _current_writer_identity_hash(config: Mapping[str, Any]) -> str:
|
|
return build_writer_profile(config).execution_profile_sha256
|
|
|
|
|
|
def _make_stale_profile_hash(current_hash: str) -> str:
|
|
prefix, _, hex_part = current_hash.partition(":")
|
|
flipped_last = "0" if hex_part[-1] != "0" else "1"
|
|
return f"{prefix}:{hex_part[:-1]}{flipped_last}"
|
|
|
|
|
|
def _build_result(
|
|
profile: RoleExecutionProfile,
|
|
*,
|
|
candidate_body: str = "黎明前,守塔人划亮火柴,灯芯燃起一小团光。",
|
|
total_cost: str | None = "0.003000",
|
|
model_match: bool = True,
|
|
requested_model_id: str | None = None,
|
|
actual_model_id: str | None = None,
|
|
is_error: bool = False,
|
|
exit_code: int | None = None,
|
|
terminal_reason: str = "completed",
|
|
api_error_status: Any = None,
|
|
structured_output: Mapping[str, Any] | None = None,
|
|
structured_output_sha256: str | None = None,
|
|
execution_profile_sha256: str | None = None,
|
|
input_sha256: str | None = None,
|
|
json_schema_sha256: str | None = None,
|
|
) -> RoleInvocationResult:
|
|
if structured_output is None:
|
|
structured_output = {"candidateBody": candidate_body}
|
|
if structured_output_sha256 is None:
|
|
structured_output_sha256 = sha256_json(structured_output)
|
|
actual_model_id = actual_model_id or profile.resolved_model_id
|
|
receipt = RoleExecutionReceipt(
|
|
adapter_role=profile.adapter_role,
|
|
invocation_id="test-probe",
|
|
execution_profile_sha256=(
|
|
execution_profile_sha256 or profile.execution_profile_sha256
|
|
),
|
|
requested_model_id=requested_model_id or profile.model_alias,
|
|
actual_model_id=actual_model_id,
|
|
model_match=model_match,
|
|
effort="governed",
|
|
max_budget_usd_per_call=format(profile.max_budget_usd_per_call, "f"),
|
|
total_cost_usd=total_cost,
|
|
usage={"prompt_tokens": 1, "completion_tokens": 1},
|
|
model_usage=(
|
|
{actual_model_id: {"costUSD": total_cost}}
|
|
if total_cost is not None
|
|
else None
|
|
),
|
|
stop_reason="stop" if not is_error else None,
|
|
terminal_reason=terminal_reason,
|
|
is_error=is_error,
|
|
api_error_status=api_error_status,
|
|
exit_code=exit_code,
|
|
duration_ms=1,
|
|
input_sha256=input_sha256 or sha256_json(PROBE_BUSINESS_INPUT),
|
|
structured_output_sha256=structured_output_sha256,
|
|
json_schema_sha256=json_schema_sha256 or profile.json_schema_sha256,
|
|
)
|
|
return RoleInvocationResult(
|
|
structured_output=structured_output,
|
|
receipt=receipt,
|
|
)
|
|
|
|
|
|
def _invoker_factory(**overrides: Any):
|
|
def _invoker(
|
|
profile: RoleExecutionProfile,
|
|
_business_input: Mapping[str, Any],
|
|
) -> RoleInvocationResult:
|
|
return _build_result(profile, **overrides)
|
|
|
|
return _invoker
|
|
|
|
|
|
def _make_stale_config(config: Mapping[str, Any]) -> tuple[dict[str, Any], str]:
|
|
current_hash = _current_writer_identity_hash(config)
|
|
valid = refresh_runtime_probe(
|
|
config,
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
).refreshed_config
|
|
stale_hash = _make_stale_profile_hash(current_hash)
|
|
probe = valid["executionAuthorization"]["runtimeProbe"]
|
|
probe["executionProfileSha256"] = stale_hash
|
|
probe["receiptSha256"] = canonical_sha256(
|
|
{key: value for key, value in probe.items() if key != "receiptSha256"}
|
|
)
|
|
return valid, stale_hash
|
|
|
|
|
|
def _adapters_from_config(config: Mapping[str, Any]) -> WriterReplayProductionAdapters:
|
|
profiles = config["executionProfiles"]
|
|
return WriterReplayProductionAdapters(
|
|
writer_runner=None,
|
|
writer_profile=profile_from_mapping(profiles["writer"], role="writer"),
|
|
semantic_model_runner=object(),
|
|
semantic_profile=profile_from_mapping(
|
|
profiles["semantic_detector"], role="semantic_detector"
|
|
),
|
|
judge_model_runner=object(),
|
|
judge_profile=profile_from_mapping(
|
|
profiles["blind_judge"], role="blind_judge"
|
|
),
|
|
oracle_truth_packs={},
|
|
)
|
|
|
|
|
|
class ProbeSuccessPathTest(unittest.TestCase):
|
|
def test_probe_and_refreshed_config_self_hash_pass_gate_verifier(self):
|
|
config = _load_base_config()
|
|
result = refresh_runtime_probe(
|
|
config,
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
_verify_self_hash(result.probe, "receiptSha256", "runtimeProbe")
|
|
refreshed = result.refreshed_config["executionAuthorization"]["runtimeProbe"]
|
|
_verify_self_hash(refreshed, "receiptSha256", "runtimeProbe")
|
|
self.assertEqual(refreshed, result.probe)
|
|
|
|
def test_stale_probe_replaced_by_current_profile_identity(self):
|
|
config = _load_base_config()
|
|
current_hash = _current_writer_identity_hash(config)
|
|
stale_config, stale_hash = _make_stale_config(config)
|
|
result = refresh_runtime_probe(
|
|
stale_config,
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
self.assertNotEqual(stale_hash, current_hash)
|
|
self.assertEqual(result.probe["executionProfileSha256"], current_hash)
|
|
self.assertEqual(
|
|
result.probe["executionProfileSha256"],
|
|
config["executionAuthorization"]["profileSha256"]["writer"],
|
|
)
|
|
|
|
def test_probe_field_set_and_runtime_bindings_are_exact(self):
|
|
result = refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
probe = result.probe
|
|
self.assertEqual(set(probe), EXPECTED_PROBE_FIELDS)
|
|
self.assertEqual(probe["schemaVersion"], PROBE_SCHEMA_VERSION)
|
|
self.assertEqual(probe["runtimeAdapter"], RUNTIME_ADAPTER)
|
|
self.assertEqual(probe["runtimeAdapterVersion"], RUNTIME_ADAPTER_VERSION)
|
|
self.assertEqual(probe["modelPolicyVersion"], MODEL_POLICY_VERSION)
|
|
self.assertEqual(probe["modelAlias"], MODEL_POLICY_ALIAS)
|
|
self.assertTrue(HASH_PATTERN.fullmatch(probe["structuredOutputSha256"]))
|
|
|
|
def test_refresh_does_not_mutate_input_config(self):
|
|
config = _load_base_config()
|
|
before = json.dumps(config, ensure_ascii=False, sort_keys=True)
|
|
refresh_runtime_probe(
|
|
config,
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
self.assertEqual(json.dumps(config, ensure_ascii=False, sort_keys=True), before)
|
|
|
|
def test_summary_excludes_prompt_response_and_paths(self):
|
|
result = refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
rendered = json.dumps(result.summary, ensure_ascii=False)
|
|
for forbidden in ("candidateBody", "systemPrompt", "rawResponse", "rawPath"):
|
|
self.assertNotIn(forbidden, rendered)
|
|
|
|
|
|
class ProbeGateIntegrationTest(unittest.TestCase):
|
|
def test_refreshed_config_passes_execute_authorization_gate(self):
|
|
result = refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
adapters = _adapters_from_config(result.refreshed_config)
|
|
gate_result, blocked_status, blocked_code = _validate_execute_authorization(
|
|
result.refreshed_config,
|
|
adapters,
|
|
)
|
|
self.assertIsNone(blocked_status)
|
|
self.assertIsNone(blocked_code)
|
|
self.assertIsNotNone(gate_result)
|
|
|
|
def test_stale_probe_is_blocked_at_contract_binding(self):
|
|
stale_config, _stale_hash = _make_stale_config(_load_base_config())
|
|
gate_result, blocked_status, blocked_code = _validate_execute_authorization(
|
|
stale_config,
|
|
_adapters_from_config(stale_config),
|
|
)
|
|
self.assertIsNone(gate_result)
|
|
self.assertEqual(blocked_status, "blocked_execute_probe_contract")
|
|
self.assertEqual(blocked_code, "EXECUTE_PROBE_CONTRACT_MISMATCH")
|
|
|
|
def test_budget_and_raw_authorizations_are_unchanged(self):
|
|
config = _load_base_config()
|
|
result = refresh_runtime_probe(
|
|
config,
|
|
invoker=_invoker_factory(),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
for name in ("budget", "rawRetention"):
|
|
self.assertEqual(
|
|
result.refreshed_config["executionAuthorization"][name],
|
|
config["executionAuthorization"][name],
|
|
)
|
|
|
|
|
|
class ProbeFailClosedTest(unittest.TestCase):
|
|
def _assert_fail_closed(self, **overrides: Any) -> ProbeRefreshError:
|
|
with self.assertRaises(ProbeRefreshError) as caught:
|
|
refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=_invoker_factory(**overrides),
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
return caught.exception
|
|
|
|
def test_schema_invalid_empty_body_fails_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(structured_output={"candidateBody": ""}).code,
|
|
"PROBE_SCHEMA_INVALID",
|
|
)
|
|
|
|
def test_schema_invalid_extra_field_fails_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(
|
|
structured_output={"candidateBody": "正文", "runId": "leak"}
|
|
).code,
|
|
"PROBE_SCHEMA_INVALID",
|
|
)
|
|
|
|
def test_over_budget_and_missing_cost_fail_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(total_cost="9.000000").code,
|
|
"PROBE_BUDGET_EXCEEDED",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(total_cost=None).code,
|
|
"PROBE_BUDGET_EXCEEDED",
|
|
)
|
|
|
|
def test_model_policy_mismatch_fails_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(model_match=False).code,
|
|
"PROBE_MODEL_MISMATCH",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(actual_model_id="unapproved-model").code,
|
|
"PROBE_MODEL_MISMATCH",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(requested_model_id="direct-model").code,
|
|
"PROBE_MODEL_MISMATCH",
|
|
)
|
|
|
|
def test_receipt_error_and_transport_fields_fail_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(is_error=True).code,
|
|
"PROBE_RECEIPT_ERROR",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(exit_code=1).code,
|
|
"PROBE_TRANSPORT_INVALID",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(api_error_status=529).code,
|
|
"PROBE_API_ERROR",
|
|
)
|
|
|
|
def test_terminal_reason_fails_closed(self):
|
|
self.assertEqual(
|
|
self._assert_fail_closed(terminal_reason="failed").code,
|
|
"PROBE_TERMINAL_REASON_INVALID",
|
|
)
|
|
|
|
def test_profile_schema_input_and_output_bindings_fail_closed(self):
|
|
current = _current_writer_identity_hash(_load_base_config())
|
|
self.assertEqual(
|
|
self._assert_fail_closed(
|
|
execution_profile_sha256=_make_stale_profile_hash(current)
|
|
).code,
|
|
"PROBE_PROFILE_BINDING_MISMATCH",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(json_schema_sha256="sha256:" + "1" * 64).code,
|
|
"PROBE_SCHEMA_BINDING_MISMATCH",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(input_sha256="sha256:" + "2" * 64).code,
|
|
"PROBE_INPUT_HASH_INVALID",
|
|
)
|
|
self.assertEqual(
|
|
self._assert_fail_closed(
|
|
structured_output_sha256="sha256:" + "3" * 64
|
|
).code,
|
|
"PROBE_OUTPUT_HASH_INVALID",
|
|
)
|
|
|
|
def test_role_runtime_error_propagates_to_cli_boundary(self):
|
|
def raising_invoker(_profile, _business_input):
|
|
raise RoleRuntimeError("WRITER_RUNTIME_FAILED", "受控失败")
|
|
|
|
with self.assertRaises(RoleRuntimeError):
|
|
refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=raising_invoker,
|
|
checked_at=CHECKED_AT,
|
|
)
|
|
|
|
|
|
class ProbeDryRunAndCliTest(unittest.TestCase):
|
|
def test_dry_run_invoker_produces_valid_probe(self):
|
|
result = refresh_runtime_probe(
|
|
_load_base_config(),
|
|
invoker=make_dry_run_invoker(),
|
|
checked_at=CHECKED_AT,
|
|
dry_run=True,
|
|
)
|
|
verify_probe_result(build_writer_profile(_load_base_config()), make_dry_run_invoker()(
|
|
build_writer_profile(_load_base_config()), PROBE_BUSINESS_INPUT
|
|
))
|
|
_verify_self_hash(result.probe, "receiptSha256", "runtimeProbe")
|
|
self.assertEqual(result.probe["status"], "dry_run")
|
|
self.assertTrue(result.summary["dryRun"])
|
|
|
|
def test_probe_input_is_synthetic_and_has_no_real_work_or_raw(self):
|
|
text = json.dumps(PROBE_BUSINESS_INPUT, ensure_ascii=False)
|
|
self.assertIn("运行探针合成任务", text)
|
|
for forbidden in ("林澈", "圣蒂曼", "深空", "raw", "/raw", "vault"):
|
|
self.assertNotIn(forbidden, text)
|
|
self.assertEqual(PROBE_BUSINESS_INPUT["proseExcerpts"], [])
|
|
self.assertEqual(PROBE_BUSINESS_INPUT["factConstraints"], [])
|
|
|
|
def test_cli_dry_run_writes_new_file_and_leaves_source_untouched(self):
|
|
source_before = CONFIG_PATH.read_text(encoding="utf-8")
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
output = pathlib.Path(directory) / "refreshed.json"
|
|
rc = refresh_module.main([
|
|
"--config", str(CONFIG_PATH),
|
|
"--output", str(output),
|
|
"--dry-run",
|
|
"--checked-at", CHECKED_AT,
|
|
])
|
|
self.assertEqual(rc, 0)
|
|
refreshed = json.loads(output.read_text(encoding="utf-8"))
|
|
self.assertEqual(
|
|
refreshed["executionAuthorization"]["runtimeProbe"]["status"],
|
|
"dry_run",
|
|
)
|
|
_verify_self_hash(
|
|
refreshed["executionAuthorization"]["runtimeProbe"],
|
|
"receiptSha256",
|
|
"runtimeProbe",
|
|
)
|
|
self.assertEqual(CONFIG_PATH.read_text(encoding="utf-8"), source_before)
|
|
|
|
def test_cli_refuses_in_place_output(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
config_path = pathlib.Path(directory) / "config.json"
|
|
config_path.write_text(CONFIG_PATH.read_text(encoding="utf-8"), encoding="utf-8")
|
|
self.assertEqual(
|
|
refresh_module.main([
|
|
"--config", str(config_path),
|
|
"--output", str(config_path),
|
|
"--dry-run",
|
|
]),
|
|
2,
|
|
)
|
|
|
|
def test_cli_runtime_failure_returns_nonzero_and_writes_nothing(self):
|
|
def raising_invoker(_profile, _business_input, **_kwargs):
|
|
raise RoleRuntimeError("WRITER_RUNTIME_FAILED", "受控失败")
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
output = pathlib.Path(directory) / "refreshed.json"
|
|
with mock.patch.object(refresh_module, "run_role", raising_invoker):
|
|
rc = refresh_module.main([
|
|
"--config", str(CONFIG_PATH),
|
|
"--output", str(output),
|
|
])
|
|
self.assertEqual(rc, 1)
|
|
self.assertFalse(output.exists())
|
|
|
|
def test_cli_schema_failure_returns_nonzero_and_writes_nothing(self):
|
|
profile = build_writer_profile(_load_base_config())
|
|
bad_result = _build_result(profile, structured_output={"candidateBody": ""})
|
|
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
output = pathlib.Path(directory) / "refreshed.json"
|
|
with mock.patch.object(
|
|
refresh_module,
|
|
"run_role",
|
|
lambda _profile, _business_input, **_kwargs: bad_result,
|
|
):
|
|
rc = refresh_module.main([
|
|
"--config", str(CONFIG_PATH),
|
|
"--output", str(output),
|
|
])
|
|
self.assertEqual(rc, 1)
|
|
self.assertFalse(output.exists())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|