test_config_case_matrix.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """config x case matrix.
  2. Replays the same captured case under different configurations to prove the
  3. "foolproof config" safety net: changing config changes the case outcome,
  4. visibly, without breaking the pipeline.
  5. """
  6. from __future__ import annotations
  7. import json
  8. import shutil
  9. from pathlib import Path
  10. import pytest
  11. from content_agent.integrations.policy_json import JsonPolicyBundleStore
  12. from tests.replay_harness import replay_case
  13. ROOT = Path(__file__).resolve().parents[1]
  14. _RULE_PACK_REL = "product_documents/规则包/douyin_rule_packs.v1.json"
  15. _WALK_REL = "product_documents/抖音游走策略/douyin_walk_strategy.v1.json"
  16. _EDGE_CATALOG_REL = "tech_documents/数据接口与来源/walk_edge_catalog.json"
  17. def _judge_ok_block_store(root: Path) -> JsonPolicyBundleStore:
  18. """Flip the existing judge_failed gate so judge_status == ok blocks by config alone."""
  19. (root / _RULE_PACK_REL).parent.mkdir(parents=True, exist_ok=True)
  20. (root / _WALK_REL).parent.mkdir(parents=True, exist_ok=True)
  21. (root / _EDGE_CATALOG_REL).parent.mkdir(parents=True, exist_ok=True)
  22. shutil.copy(ROOT / _WALK_REL, root / _WALK_REL)
  23. shutil.copy(ROOT / _EDGE_CATALOG_REL, root / _EDGE_CATALOG_REL)
  24. package = json.loads((ROOT / _RULE_PACK_REL).read_text(encoding="utf-8"))
  25. for pack in package.get("rule_packs", []):
  26. for gate in pack.get("hard_gates", []):
  27. if gate.get("gate_id") == "judge_failed":
  28. gate["when"]["value"] = "ok"
  29. gate["decision_action"] = "REJECT_CONTENT"
  30. gate["severity"] = "fatal"
  31. (root / _RULE_PACK_REL).write_text(json.dumps(package, ensure_ascii=False, indent=2), encoding="utf-8")
  32. return JsonPolicyBundleStore(root)
  33. def _outcome(artifacts) -> dict:
  34. return {
  35. "reasons": sorted(d.get("decision_reason_code") for d in artifacts.decisions),
  36. "effect_status_counts": artifacts.summary.get("effect_status_counts"),
  37. "pooled": artifacts.summary.get("pooled_content_count"),
  38. "rejected": artifacts.summary.get("rejected_content_count"),
  39. }
  40. def _variant_overrides(variant: str, cfg_dir: Path):
  41. if variant == "default":
  42. return None
  43. if variant == "judge_ok_block":
  44. return {"policy_store": _judge_ok_block_store(cfg_dir)}
  45. raise ValueError(variant)
  46. @pytest.mark.parametrize("variant", ["default", "judge_ok_block"])
  47. def test_matrix_real_id45(variant, tmp_path):
  48. overrides = _variant_overrides(variant, tmp_path / "cfg")
  49. artifacts = replay_case("real_id45", runtime_root=tmp_path / "rt", config_overrides=overrides)
  50. assert artifacts.state["status"] == "success" # config change must not break the chain
  51. outcome = _outcome(artifacts)
  52. if variant == "default":
  53. # M11 re-baseline:3 入池(原第3条复看升入池)/ 1 淘汰。
  54. assert outcome["pooled"] == 3
  55. assert outcome["rejected"] == 1
  56. assert outcome["effect_status_counts"] == {
  57. "success": 3,
  58. "pending": 0,
  59. "failed": 1,
  60. "rule_blocked": 0,
  61. }
  62. else:
  63. assert outcome["pooled"] == 0
  64. assert outcome["rejected"] == 4
  65. assert outcome["effect_status_counts"]["rule_blocked"] == 4
  66. def test_judge_ok_block_changes_outcome(tmp_path):
  67. base = _outcome(replay_case("real_id45", runtime_root=tmp_path / "rt0"))
  68. blocked = _outcome(
  69. replay_case(
  70. "real_id45",
  71. runtime_root=tmp_path / "rt1",
  72. config_overrides={"policy_store": _judge_ok_block_store(tmp_path / "cfg")},
  73. )
  74. )
  75. assert base != blocked
  76. assert base["effect_status_counts"]["rule_blocked"] == 0
  77. assert base["pooled"] == 3
  78. assert blocked["reasons"] == ["v4_technical_retry_needed"] * 4
  79. assert blocked["effect_status_counts"]["rule_blocked"] == 4
  80. assert blocked["pooled"] == 0
  81. assert blocked["rejected"] == 4
  82. def test_matrix_query_profile_variant():
  83. from scripts.validate_query_prompts_config import validate_query_prompts_config
  84. config = json.loads((ROOT / "product_documents/配置/query_prompts.v1.json").read_text(encoding="utf-8"))
  85. assert validate_query_prompts_config(config) == []
  86. def test_decoupling_counterproof():
  87. # M3A removed the Content hardcode: dispatch is parametrized by target_entity,
  88. # so a non-Content (e.g. Author) pack can be routed without falling back.
  89. source = (ROOT / "content_agent/integrations/policy_json.py").read_text(encoding="utf-8")
  90. assert 'target_entity") == "Content"' not in source
  91. def test_judge_ok_block_blocks_all_walk_expansion(tmp_path):
  92. artifacts = replay_case(
  93. "real_id45",
  94. runtime_root=tmp_path / "rt",
  95. config_overrides={"policy_store": _judge_ok_block_store(tmp_path / "cfg")},
  96. )
  97. walk_actions = artifacts.files["walk_actions.jsonl"]
  98. assert not [row for row in walk_actions if row["edge_id"] == "query_next_page"]
  99. expansions = [
  100. row for row in walk_actions if row["edge_id"] in {"author_to_works", "hashtag_to_query"}
  101. ]
  102. assert expansions
  103. assert all(row["walk_status"] == "skipped" for row in expansions)
  104. assert all(row["reason_code"] == "v4_allow_walk_denied" for row in expansions)
  105. assert all(row["raw_payload"]["allow_walk"] is False for row in expansions)
  106. path_stops = [row for row in walk_actions if row["edge_id"] == "path_stop"]
  107. assert len(path_stops) == 4
  108. for row in path_stops:
  109. assert row["rule_pack_id"] == "douyin_content_discovery_rule_pack_v1"
  110. assert row["raw_payload"]["rule_pack_execution"]["executed_rule_pack_id"] == (
  111. "douyin_content_discovery_rule_pack_v1"
  112. )