test_v4_validator_contract.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. from __future__ import annotations
  2. import copy
  3. from typing import Any, Callable
  4. from content_agent.business_modules.run_record.validation import validate_run
  5. from content_agent.integrations.runtime_files import LocalRuntimeFileStore, RUNTIME_FILENAMES
  6. RUN_ID = "run_v4_contract"
  7. POLICY_RUN_ID = "policy_v4_contract"
  8. def test_v4_contract_runtime_passes_validate_run(tmp_path):
  9. runtime = _write_runtime(tmp_path)
  10. result = validate_run(RUN_ID, runtime)
  11. assert result["status"] == "pass"
  12. def test_v4_contract_does_not_apply_to_v3_scorecard_records(tmp_path):
  13. def mutate(data: dict[str, Any]) -> None:
  14. decision = data["rule_decisions.jsonl"][0]
  15. decision["score"] = None
  16. decision["scorecard"] = {
  17. "total_score": None,
  18. "fit_senior_50plus": True,
  19. "relevance_score": 0.91,
  20. }
  21. decision["decision_replay_data"].pop("allow_walk")
  22. runtime = _write_runtime(tmp_path, mutate)
  23. result = validate_run(RUN_ID, runtime)
  24. assert result["status"] == "pass"
  25. def test_v4_score_contract_rejects_bad_total(tmp_path):
  26. def mutate(data: dict[str, Any]) -> None:
  27. data["rule_decisions.jsonl"][0]["score"] = 92
  28. _refresh_final_output_explanations(data)
  29. runtime = _write_runtime(tmp_path, mutate)
  30. result = validate_run(RUN_ID, runtime)
  31. assert _check_ids(result) == ["v4_score_total_mismatch"]
  32. def test_v4_score_contract_accepts_score_weights(tmp_path):
  33. def mutate(data: dict[str, Any]) -> None:
  34. data["rule_decisions.jsonl"][0]["scorecard"]["score_weights"] = {
  35. "query_relevance": 0.5,
  36. "platform_performance": 0.5,
  37. }
  38. _refresh_final_output_explanations(data)
  39. runtime = _write_runtime(tmp_path, mutate)
  40. result = validate_run(RUN_ID, runtime)
  41. assert result["status"] == "pass"
  42. def test_v4_score_contract_rejects_bad_score_weights(tmp_path):
  43. def mutate(data: dict[str, Any]) -> None:
  44. data["rule_decisions.jsonl"][0]["scorecard"]["score_weights"] = {
  45. "query_relevance": 0.4,
  46. "platform_performance": 0.4,
  47. }
  48. _refresh_final_output_explanations(data)
  49. runtime = _write_runtime(tmp_path, mutate)
  50. result = validate_run(RUN_ID, runtime)
  51. assert _check_ids(result) == ["v4_score_total_mismatch"]
  52. def test_v4_score_contract_not_attempted_score_weights_are_not_normalized(tmp_path):
  53. def mutate(data: dict[str, Any]) -> None:
  54. decision = data["rule_decisions.jsonl"][0]
  55. decision["score"] = 70
  56. decision["scorecard"]["query_relevance_score"] = 100
  57. decision["scorecard"]["platform_performance_score"] = 100
  58. decision["scorecard"]["fifty_plus_status"] = "not_attempted"
  59. decision["scorecard"]["score_weights"] = {
  60. "query_relevance": 0.35,
  61. "platform_performance": 0.35,
  62. }
  63. decision["decision_replay_data"]["walk_gate_snapshot"] = {
  64. "query_relevance_score": 100,
  65. "platform_performance_score": 100,
  66. "score": 70,
  67. }
  68. _refresh_final_output_explanations(data)
  69. runtime = _write_runtime(tmp_path, mutate)
  70. result = validate_run(RUN_ID, runtime)
  71. assert result["status"] == "pass"
  72. def test_v4_walk_gate_rejects_allow_walk_below_threshold(tmp_path):
  73. def mutate(data: dict[str, Any]) -> None:
  74. decision = data["rule_decisions.jsonl"][0]
  75. decision["score"] = 65
  76. decision["scorecard"]["query_relevance_score"] = 60
  77. decision["scorecard"]["platform_performance_score"] = 70
  78. _refresh_final_output_explanations(data)
  79. runtime = _write_runtime(tmp_path, mutate)
  80. result = validate_run(RUN_ID, runtime)
  81. assert "v4_allow_walk_threshold_mismatch" in _check_ids(result)
  82. def test_v4_walk_gate_rejects_allow_walk_false_at_passing_threshold(tmp_path):
  83. def mutate(data: dict[str, Any]) -> None:
  84. data["rule_decisions.jsonl"][0]["decision_replay_data"]["allow_walk"] = False
  85. _refresh_final_output_explanations(data)
  86. runtime = _write_runtime(tmp_path, mutate)
  87. result = validate_run(RUN_ID, runtime)
  88. assert "v4_allow_walk_threshold_mismatch" in _check_ids(result)
  89. def test_v4_walk_action_rejects_success_when_allow_walk_false(tmp_path):
  90. def mutate(data: dict[str, Any]) -> None:
  91. decision = data["rule_decisions.jsonl"][0]
  92. decision["score"] = 70
  93. decision["scorecard"]["platform_performance_score"] = 60
  94. decision["decision_replay_data"]["allow_walk"] = False
  95. decision["decision_replay_data"]["allow_walk_reason"] = "v4_query_and_platform_pass"
  96. decision["decision_replay_data"]["walk_gate_snapshot"] = {
  97. "query_relevance_score": 80,
  98. "platform_performance_score": 60,
  99. "score": 70,
  100. }
  101. _refresh_final_output_explanations(data)
  102. data["walk_actions.jsonl"].append(
  103. _walk_action(
  104. "walk_page_001",
  105. "hashtag_to_query",
  106. "success",
  107. {
  108. "decision_id": "decision_001",
  109. "allow_walk": False,
  110. "allow_walk_reason": "v4_query_and_platform_pass",
  111. "walk_gate_snapshot": decision["decision_replay_data"]["walk_gate_snapshot"],
  112. },
  113. )
  114. )
  115. runtime = _write_runtime(tmp_path, mutate)
  116. result = validate_run(RUN_ID, runtime)
  117. assert "v4_walk_action_allow_walk_denied" in _check_ids(result)
  118. def test_v4_walk_action_rejects_missing_gate_snapshot(tmp_path):
  119. def mutate(data: dict[str, Any]) -> None:
  120. data["walk_actions.jsonl"].append(
  121. _walk_action(
  122. "walk_tag_001",
  123. "hashtag_to_query",
  124. "success",
  125. {
  126. "decision_id": "decision_001",
  127. "allow_walk": True,
  128. "allow_walk_reason": "query>=70/platform>=65/score>=70",
  129. },
  130. )
  131. )
  132. runtime = _write_runtime(tmp_path, mutate)
  133. result = validate_run(RUN_ID, runtime)
  134. assert "v4_walk_action_gate_context_missing" in _check_ids(result)
  135. def test_v4_action_thresholds_reject_conflicting_action(tmp_path):
  136. def mutate(data: dict[str, Any]) -> None:
  137. decision = data["rule_decisions.jsonl"][0]
  138. decision["decision_action"] = "KEEP_CONTENT_FOR_REVIEW"
  139. runtime = _write_runtime(tmp_path, mutate)
  140. result = validate_run(RUN_ID, runtime)
  141. assert "v4_action_threshold_mismatch" in _check_ids(result)
  142. def test_v4_validator_uses_decision_score_thresholds(tmp_path):
  143. def mutate(data: dict[str, Any]) -> None:
  144. decision = data["rule_decisions.jsonl"][0]
  145. decision["score"] = 67.5
  146. decision["scorecard"]["query_relevance_score"] = 70
  147. decision["scorecard"]["platform_performance_score"] = 65
  148. decision["scorecard"]["score_thresholds"] = {
  149. "pool_total": 65,
  150. "pool_query": 65,
  151. "review_total": 55,
  152. "review_query": 55,
  153. "walk_query": 65,
  154. "walk_platform": 65,
  155. "walk_total": 65,
  156. }
  157. decision["decision_action"] = "ADD_TO_CONTENT_POOL"
  158. decision["decision_replay_data"]["allow_walk"] = True
  159. decision["decision_replay_data"]["allow_walk_reason"] = "query>=65/platform>=65/score>=65"
  160. decision["decision_replay_data"]["walk_gate_snapshot"] = {
  161. "query_relevance_score": 70,
  162. "platform_performance_score": 65,
  163. "score": 67.5,
  164. }
  165. _refresh_final_output_explanations(data)
  166. runtime = _write_runtime(tmp_path, mutate)
  167. result = validate_run(RUN_ID, runtime)
  168. assert result["status"] == "pass"
  169. def test_v4_gemini_failure_requires_structured_failure_fields(tmp_path):
  170. def mutate(data: dict[str, Any]) -> None:
  171. summary = data["pattern_recall_evidence.jsonl"][0]["evidence_summary"]
  172. summary.clear()
  173. summary.update(
  174. {
  175. "schema_version": "v4_gemini_query_relevance.v1",
  176. "final_status": "failed",
  177. "retry_count": 0,
  178. }
  179. )
  180. runtime = _write_runtime(tmp_path, mutate)
  181. result = validate_run(RUN_ID, runtime)
  182. assert {
  183. "v4_gemini_failure_incomplete",
  184. "v4_gemini_failure_retry_invalid",
  185. } <= set(_check_ids(result))
  186. def test_v4_legacy_field_blocklist_rejects_v4_records_only(tmp_path):
  187. def mutate(data: dict[str, Any]) -> None:
  188. data["rule_decisions.jsonl"][0]["scorecard"]["platform_heat"] = 88
  189. data["pattern_recall_evidence.jsonl"][0]["evidence_summary"]["relevance_score"] = 0.9
  190. runtime = _write_runtime(tmp_path, mutate)
  191. result = validate_run(RUN_ID, runtime)
  192. assert _check_ids(result).count("v4_legacy_field_present") == 2
  193. def test_v4_final_output_requires_explanation(tmp_path):
  194. def mutate(data: dict[str, Any]) -> None:
  195. data["final_output.json"]["decision_records"][0].pop("v4_explanation")
  196. runtime = _write_runtime(tmp_path, mutate)
  197. result = validate_run(RUN_ID, runtime)
  198. assert "v4_final_output_explanation_missing" in _check_ids(result)
  199. def test_v4_final_output_explanation_must_match_decision(tmp_path):
  200. def mutate(data: dict[str, Any]) -> None:
  201. data["final_output.json"]["decision_records"][0]["v4_explanation"]["allow_walk"] = False
  202. runtime = _write_runtime(tmp_path, mutate)
  203. result = validate_run(RUN_ID, runtime)
  204. assert "v4_final_output_explanation_mismatch" in _check_ids(result)
  205. def test_v4_strategy_review_requires_summary_when_generated(tmp_path):
  206. def mutate(data: dict[str, Any]) -> None:
  207. data["strategy_review.json"].pop("v4_summary")
  208. runtime = _write_runtime(tmp_path, mutate)
  209. result = validate_run(RUN_ID, runtime)
  210. assert "v4_strategy_review_explanation_missing" in _check_ids(result)
  211. def _write_runtime(
  212. tmp_path,
  213. mutate: Callable[[dict[str, Any]], None] | None = None,
  214. ) -> LocalRuntimeFileStore:
  215. runtime = LocalRuntimeFileStore(tmp_path / "runtime")
  216. runtime.prepare_run(RUN_ID)
  217. data = _runtime_payload()
  218. if mutate:
  219. mutate(data)
  220. for filename in RUNTIME_FILENAMES:
  221. value = data[filename]
  222. if isinstance(value, list):
  223. runtime.append_jsonl(RUN_ID, filename, value)
  224. else:
  225. runtime.write_json(RUN_ID, filename, value)
  226. return runtime
  227. def _runtime_payload() -> dict[str, Any]:
  228. evidence_pack = {
  229. "pattern_source_system": "pg_pattern_v2",
  230. "case_id_type": "post_id",
  231. "source_kind": "pattern_itemset",
  232. "source_post_id": "post_001",
  233. "pattern_execution_id": 581,
  234. "mining_config_id": 2082,
  235. "itemset_ids": [1608352],
  236. "itemset_items": ["毛主席", "感人"],
  237. "support": 5,
  238. "absolute_support": 5,
  239. "matched_post_ids": ["post_001", "post_002"],
  240. "video_ids": ["video_001"],
  241. "case_ids": ["case_001"],
  242. "seed_terms": ["父爱感悟"],
  243. "discovery_start_source": "pattern_seed",
  244. "previous_discovery_step": "search_query_generated",
  245. "origin_path_id": "path_origin_001",
  246. "run_id": RUN_ID,
  247. "policy_run_id": POLICY_RUN_ID,
  248. "source_certainty": "high",
  249. "validation_status": "validated",
  250. }
  251. source_evidence = _source_evidence(evidence_pack, "content_001")
  252. path_ids = ["path_pattern_query", "path_query_content", "path_decision_asset"]
  253. decision = {
  254. "record_schema_version": "runtime_record.v1",
  255. "run_id": RUN_ID,
  256. "policy_run_id": POLICY_RUN_ID,
  257. "decision_id": "decision_001",
  258. "policy_bundle_id": "policy_bundle_v4",
  259. "rule_pack_id": "douyin_content_discovery_rule_pack_v4",
  260. "rule_pack_version": "4.0.0",
  261. "strategy_version": "V4",
  262. "decision_target_type": "content",
  263. "decision_target_id": "content_001",
  264. "decision_action": "ADD_TO_CONTENT_POOL",
  265. "decision_reason_code": "v4_query_and_platform_pass",
  266. "search_query_effect_status": "success",
  267. "score": 75,
  268. "scorecard": {
  269. "schema_version": "v4_scorecard.v1",
  270. "query_relevance_score": 80,
  271. "platform_performance_score": 70,
  272. "missing_observable_fields": [],
  273. },
  274. "source_evidence": copy.deepcopy(source_evidence),
  275. "decision_replay_data": {
  276. "policy_bundle_hash": "hash_v4",
  277. "rule_pack_id": "douyin_content_discovery_rule_pack_v4",
  278. "rule_pack_version": "4.0.0",
  279. "dispatch_id": "dispatch_v4",
  280. "strategy_version": "V4",
  281. "allow_walk": True,
  282. "allow_walk_reason": "query>=70/platform>=65/score>=70",
  283. "walk_gate_snapshot": {
  284. "query_relevance_score": 80,
  285. "platform_performance_score": 70,
  286. "score": 75,
  287. },
  288. },
  289. "raw_payload": {"decision_id": "decision_001", "v4_contract": True},
  290. }
  291. v4_explanation = _v4_explanation(decision)
  292. return {
  293. "source_context.json": {
  294. "schema_version": "runtime_record.v1",
  295. "run_id": RUN_ID,
  296. "demand_content_id": "123",
  297. "ext_data": {"evidence_pack": evidence_pack},
  298. },
  299. "pattern_seed_pack.json": {
  300. "schema_version": "runtime_record.v1",
  301. "run_id": RUN_ID,
  302. "policy_run_id": POLICY_RUN_ID,
  303. "itemsets": [{"itemset_id": 1608352}],
  304. "seed_terms": ["父爱感悟"],
  305. },
  306. "search_queries.jsonl": [
  307. {
  308. "record_schema_version": "runtime_record.v1",
  309. "run_id": RUN_ID,
  310. "policy_run_id": POLICY_RUN_ID,
  311. "search_query_id": "query_001",
  312. "search_query": "父爱感悟",
  313. "search_query_generation_method": "v4_seed",
  314. "discovery_start_source": "pattern_seed",
  315. "previous_discovery_step": "search_query_generated",
  316. "search_query_effect_status": "success",
  317. "pattern_seed_ref": {"query_source_type": "seed"},
  318. "raw_payload": {"query_source_refs": [{"query_source_type": "seed"}]},
  319. }
  320. ],
  321. "discovered_content_items.jsonl": [
  322. {
  323. "record_schema_version": "runtime_record.v1",
  324. "run_id": RUN_ID,
  325. "policy_run_id": POLICY_RUN_ID,
  326. "content_discovery_id": "discovery_001",
  327. "search_query_id": "query_001",
  328. "platform": "douyin",
  329. "platform_content_id": "content_001",
  330. "content_url": "https://example.com/content_001",
  331. "statistics": {"share_count": 10},
  332. "tags": ["父爱"],
  333. "source_evidence": copy.deepcopy(source_evidence),
  334. "pattern_match_result": {
  335. "judge_status": "ok",
  336. "pattern_recall_evidence_id": "recall_001",
  337. },
  338. "platform_raw_payload": {},
  339. "raw_payload": {"platform_content_id": "content_001"},
  340. }
  341. ],
  342. "content_media_records.jsonl": [
  343. {
  344. "record_schema_version": "runtime_record.v1",
  345. "run_id": RUN_ID,
  346. "policy_run_id": POLICY_RUN_ID,
  347. "media_record_id": "media_001",
  348. "platform": "douyin",
  349. "platform_content_id": "content_001",
  350. "media_status": "available",
  351. "raw_payload": {"platform_content_id": "content_001"},
  352. }
  353. ],
  354. "pattern_recall_evidence.jsonl": [
  355. {
  356. "record_schema_version": "runtime_record.v1",
  357. "run_id": RUN_ID,
  358. "policy_run_id": POLICY_RUN_ID,
  359. "recall_evidence_id": "recall_001",
  360. "content_discovery_id": "discovery_001",
  361. "platform_content_id": "content_001",
  362. "recall_status": "judged",
  363. "evidence_summary": {
  364. "schema_version": "v4_gemini_query_relevance.v1",
  365. "final_status": "success",
  366. "query_relevance_score": 80,
  367. "reason": "契合 query",
  368. },
  369. "raw_payload": {"recall_evidence_id": "recall_001"},
  370. }
  371. ],
  372. "rule_decisions.jsonl": [decision],
  373. "walk_actions.jsonl": [],
  374. "run_events.jsonl": [],
  375. "source_path_records.jsonl": [
  376. _path("path_pattern_query", "pattern_to_search_query", "Pattern", 581, "SearchQuery", "query_001"),
  377. _path("path_query_content", "search_query_to_content", "SearchQuery", "query_001", "Content", "content_001"),
  378. _path(
  379. "path_decision_asset",
  380. "decision_to_asset",
  381. "RuleDecision",
  382. "decision_001",
  383. "ContentAsset",
  384. "content_001",
  385. decision_id="decision_001",
  386. ),
  387. ],
  388. "search_clues.jsonl": [
  389. {
  390. "record_schema_version": "runtime_record.v1",
  391. "run_id": RUN_ID,
  392. "policy_run_id": POLICY_RUN_ID,
  393. "clue_id": "clue_001",
  394. "search_query_id": "query_001",
  395. "search_query": "父爱感悟",
  396. "result_count": 1,
  397. "pooled_content_count": 1,
  398. "review_content_count": 0,
  399. "pending_content_count": 0,
  400. "rejected_content_count": 0,
  401. "search_query_effect_status": "success",
  402. "query_aggregation_id": "agg_query_success",
  403. "raw_payload": {"clue_id": "clue_001"},
  404. }
  405. ],
  406. "final_output.json": {
  407. "schema_version": "runtime_record.v1",
  408. "run_id": RUN_ID,
  409. "policy_run_id": POLICY_RUN_ID,
  410. "validation_status": "pass",
  411. "content_assets": [
  412. {
  413. "platform_content_id": "content_001",
  414. "decision_id": "decision_001",
  415. "source_path_record_ids": path_ids,
  416. "source_evidence": copy.deepcopy(source_evidence),
  417. "v4_explanation": copy.deepcopy(v4_explanation),
  418. }
  419. ],
  420. "author_assets": [],
  421. "review_records": [],
  422. "decision_records": [
  423. {
  424. "decision_id": "decision_001",
  425. "score": 75,
  426. "scorecard": copy.deepcopy(decision["scorecard"]),
  427. "decision_replay_data": copy.deepcopy(decision["decision_replay_data"]),
  428. "source_evidence": copy.deepcopy(source_evidence),
  429. "v4_explanation": copy.deepcopy(v4_explanation),
  430. }
  431. ],
  432. "search_clues": [],
  433. "reject_records": [],
  434. "summary": {
  435. "pooled_content_count": 1,
  436. "review_content_count": 0,
  437. "pending_content_count": 0,
  438. "rejected_content_count": 0,
  439. "run_path_complete": True,
  440. "trace_complete": True,
  441. },
  442. },
  443. "strategy_review.json": {
  444. "schema_version": "runtime_record.v1",
  445. "run_id": RUN_ID,
  446. "policy_run_id": POLICY_RUN_ID,
  447. "summary": {},
  448. "v4_summary": {
  449. "schema_version": "v4_strategy_review_summary.v1",
  450. "score_buckets": {
  451. "pool": 1,
  452. "review": 0,
  453. "reject": 0,
  454. "technical_retry": 0,
  455. "unknown": 0,
  456. },
  457. "allow_walk_distribution": {
  458. "allowed": 1,
  459. "denied": 0,
  460. "missing": 0,
  461. },
  462. "walk_gate_review": {
  463. "v4_gate_distribution": {
  464. "allowed": 0,
  465. "denied": 0,
  466. "missing": 0,
  467. }
  468. },
  469. },
  470. "raw_payload": {"strategy_review_id": "review_001"},
  471. },
  472. }
  473. def _v4_explanation(decision: dict[str, Any]) -> dict[str, Any]:
  474. scorecard = decision["scorecard"]
  475. replay = decision["decision_replay_data"]
  476. return {
  477. "schema_version": "v4_decision_explanation.v1",
  478. "scorecard_schema_version": scorecard["schema_version"],
  479. "query_relevance_score": scorecard["query_relevance_score"],
  480. "platform_performance_score": scorecard["platform_performance_score"],
  481. "score": decision["score"],
  482. "platform_performance_components": scorecard.get("platform_performance_components", []),
  483. "missing_observable_fields": scorecard.get("missing_observable_fields", []),
  484. "decision_action": decision["decision_action"],
  485. "decision_reason_code": decision["decision_reason_code"],
  486. "search_query_effect_status": decision["search_query_effect_status"],
  487. "allow_walk": replay["allow_walk"],
  488. "allow_walk_reason": replay["allow_walk_reason"],
  489. "walk_gate_snapshot": replay["walk_gate_snapshot"],
  490. }
  491. def _refresh_final_output_explanations(data: dict[str, Any]) -> None:
  492. decisions = {decision["decision_id"]: decision for decision in data["rule_decisions.jsonl"]}
  493. for section in ["content_assets", "review_records", "reject_records", "decision_records"]:
  494. for record in data["final_output.json"].get(section, []):
  495. decision = decisions.get(record.get("decision_id"))
  496. if not decision:
  497. continue
  498. record["v4_explanation"] = _v4_explanation(decision)
  499. if section == "decision_records":
  500. record["score"] = decision.get("score")
  501. record["scorecard"] = copy.deepcopy(decision.get("scorecard"))
  502. record["decision_replay_data"] = copy.deepcopy(
  503. decision.get("decision_replay_data")
  504. )
  505. def _source_evidence(evidence_pack: dict[str, Any], platform_content_id: str) -> dict[str, Any]:
  506. evidence = copy.deepcopy(evidence_pack)
  507. evidence["discovered_platform_content_id"] = platform_content_id
  508. return evidence
  509. def _path(
  510. path_id: str,
  511. path_type: str,
  512. from_type: str,
  513. from_id: Any,
  514. to_type: str,
  515. to_id: Any,
  516. decision_id: str | None = None,
  517. ) -> dict[str, Any]:
  518. return {
  519. "record_schema_version": "runtime_record.v1",
  520. "run_id": RUN_ID,
  521. "policy_run_id": POLICY_RUN_ID,
  522. "source_path_record_id": path_id,
  523. "source_path_type": path_type,
  524. "from_node_type": from_type,
  525. "from_node_id": from_id,
  526. "to_node_type": to_type,
  527. "to_node_id": to_id,
  528. "decision_id": decision_id,
  529. "raw_payload": {"source_path_record_id": path_id},
  530. }
  531. def _walk_action(
  532. action_id: str,
  533. edge_id: str,
  534. status: str,
  535. raw_payload: dict[str, Any],
  536. ) -> dict[str, Any]:
  537. return {
  538. "record_schema_version": "runtime_record.v1",
  539. "run_id": RUN_ID,
  540. "policy_run_id": POLICY_RUN_ID,
  541. "walk_action_id": action_id,
  542. "edge_id": edge_id,
  543. "edge_type": "query",
  544. "from_node_type": "SearchQuery",
  545. "from_node_id": "query_001",
  546. "to_node_type": "SearchQuery",
  547. "to_node_id": "query_002",
  548. "walk_action": "fetch_next_page",
  549. "walk_status": status,
  550. "budget_tier": "normal",
  551. "depth": 1,
  552. "reason_code": None,
  553. "raw_payload": raw_payload,
  554. }
  555. def _check_ids(result: dict[str, Any]) -> list[str]:
  556. return [finding["check_id"] for finding in result["findings"]]