briefloop 0.11.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. briefloop-0.11.12.dist-info/METADATA +473 -0
  2. briefloop-0.11.12.dist-info/RECORD +424 -0
  3. briefloop-0.11.12.dist-info/WHEEL +5 -0
  4. briefloop-0.11.12.dist-info/entry_points.txt +3 -0
  5. briefloop-0.11.12.dist-info/licenses/LICENSE +22 -0
  6. briefloop-0.11.12.dist-info/top_level.txt +1 -0
  7. multi_agent_brief/__init__.py +36 -0
  8. multi_agent_brief/analysis_blocks/__init__.py +11 -0
  9. multi_agent_brief/analysis_blocks/builder.py +206 -0
  10. multi_agent_brief/analysis_blocks/renderer.py +237 -0
  11. multi_agent_brief/analysis_blocks/schemas.py +52 -0
  12. multi_agent_brief/analysis_modules/__init__.py +8 -0
  13. multi_agent_brief/analysis_modules/base.py +87 -0
  14. multi_agent_brief/analysis_modules/market_competitor/__init__.py +68 -0
  15. multi_agent_brief/analysis_modules/market_competitor/auditor.py +290 -0
  16. multi_agent_brief/analysis_modules/market_competitor/config.py +187 -0
  17. multi_agent_brief/analysis_modules/market_competitor/event_builder.py +226 -0
  18. multi_agent_brief/analysis_modules/market_competitor/renderer.py +199 -0
  19. multi_agent_brief/analysis_modules/market_competitor/schemas.py +518 -0
  20. multi_agent_brief/analysis_modules/policy_regulatory/__init__.py +7 -0
  21. multi_agent_brief/analysis_modules/policy_regulatory/audit.py +195 -0
  22. multi_agent_brief/analysis_modules/policy_regulatory/module.py +387 -0
  23. multi_agent_brief/analysis_modules/policy_regulatory/schemas.py +129 -0
  24. multi_agent_brief/analysis_modules/registry.py +73 -0
  25. multi_agent_brief/audience/__init__.py +19 -0
  26. multi_agent_brief/audience/profiles.py +246 -0
  27. multi_agent_brief/audience_memory/__init__.py +23 -0
  28. multi_agent_brief/audience_memory/profile.py +358 -0
  29. multi_agent_brief/audit/__init__.py +36 -0
  30. multi_agent_brief/audit/case_applicability.py +181 -0
  31. multi_agent_brief/audit/deterministic.py +471 -0
  32. multi_agent_brief/audit/editorial_governance.py +404 -0
  33. multi_agent_brief/audit/final_quality.py +764 -0
  34. multi_agent_brief/audit/harness.py +242 -0
  35. multi_agent_brief/audit/interfaces.py +96 -0
  36. multi_agent_brief/audit/limitation_hygiene.py +238 -0
  37. multi_agent_brief/audit/proposal_boundary.py +34 -0
  38. multi_agent_brief/audit/redaction.py +27 -0
  39. multi_agent_brief/audit/rule_packs.py +96 -0
  40. multi_agent_brief/audit/semantic.py +435 -0
  41. multi_agent_brief/capabilities/__init__.py +22 -0
  42. multi_agent_brief/capabilities/catalog.py +215 -0
  43. multi_agent_brief/capabilities/detect.py +199 -0
  44. multi_agent_brief/capabilities/models.py +60 -0
  45. multi_agent_brief/capabilities/recommend.py +206 -0
  46. multi_agent_brief/cli/__init__.py +2 -0
  47. multi_agent_brief/cli/analysis_commands.py +140 -0
  48. multi_agent_brief/cli/approval_commands.py +107 -0
  49. multi_agent_brief/cli/audit_commands.py +59 -0
  50. multi_agent_brief/cli/capability_commands.py +548 -0
  51. multi_agent_brief/cli/claude_commands.py +159 -0
  52. multi_agent_brief/cli/competitors_commands.py +182 -0
  53. multi_agent_brief/cli/controls_commands.py +137 -0
  54. multi_agent_brief/cli/deliver_commands.py +942 -0
  55. multi_agent_brief/cli/eval_cases_commands.py +99 -0
  56. multi_agent_brief/cli/experiments_commands.py +395 -0
  57. multi_agent_brief/cli/feedback_commands.py +192 -0
  58. multi_agent_brief/cli/finalize_commands.py +153 -0
  59. multi_agent_brief/cli/gates_commands.py +174 -0
  60. multi_agent_brief/cli/hermes_commands.py +356 -0
  61. multi_agent_brief/cli/improve_commands.py +224 -0
  62. multi_agent_brief/cli/init_commands.py +564 -0
  63. multi_agent_brief/cli/init_wizard.py +1473 -0
  64. multi_agent_brief/cli/input_commands.py +237 -0
  65. multi_agent_brief/cli/main.py +288 -0
  66. multi_agent_brief/cli/onboard_commands.py +193 -0
  67. multi_agent_brief/cli/product_commands.py +1449 -0
  68. multi_agent_brief/cli/provenance_commands.py +100 -0
  69. multi_agent_brief/cli/release_commands.py +68 -0
  70. multi_agent_brief/cli/repair_commands.py +169 -0
  71. multi_agent_brief/cli/run_commands.py +292 -0
  72. multi_agent_brief/cli/runtime_commands.py +81 -0
  73. multi_agent_brief/cli/secrets_commands.py +232 -0
  74. multi_agent_brief/cli/semantic_support_commands.py +80 -0
  75. multi_agent_brief/cli/sources_commands.py +647 -0
  76. multi_agent_brief/cli/start_commands.py +8 -0
  77. multi_agent_brief/cli/state_commands.py +446 -0
  78. multi_agent_brief/cli/status_commands.py +26 -0
  79. multi_agent_brief/cli/workbuddy_commands.py +103 -0
  80. multi_agent_brief/configs/artifact_contracts.yaml +514 -0
  81. multi_agent_brief/configs/orchestrator_contract.yaml +181 -0
  82. multi_agent_brief/configs/policy_packs/default.yaml +65 -0
  83. multi_agent_brief/configs/policy_profiles/evidence_extract_default.yaml +58 -0
  84. multi_agent_brief/configs/policy_profiles/finance_default.yaml +44 -0
  85. multi_agent_brief/configs/policy_profiles/internet_default.yaml +44 -0
  86. multi_agent_brief/configs/policy_profiles/manufacturing_default.yaml +33 -0
  87. multi_agent_brief/configs/policy_profiles/solar_manufacturing_default.yaml +64 -0
  88. multi_agent_brief/configs/report_packs/evidence_extract.yaml +48 -0
  89. multi_agent_brief/configs/report_packs/management_monthly.yaml +36 -0
  90. multi_agent_brief/configs/report_packs/market_weekly.yaml +36 -0
  91. multi_agent_brief/configs/report_packs/solar_industry_periodic.yaml +50 -0
  92. multi_agent_brief/configs/report_templates/evidence_extract.yaml +55 -0
  93. multi_agent_brief/configs/report_templates/management_monthly.yaml +48 -0
  94. multi_agent_brief/configs/report_templates/market_weekly.yaml +49 -0
  95. multi_agent_brief/configs/report_templates/solar_industry_periodic.yaml +75 -0
  96. multi_agent_brief/configs/stage_specs.yaml +180 -0
  97. multi_agent_brief/contracts/__init__.py +41 -0
  98. multi_agent_brief/contracts/base.py +99 -0
  99. multi_agent_brief/contracts/errors.py +41 -0
  100. multi_agent_brief/contracts/migrations/__init__.py +5 -0
  101. multi_agent_brief/contracts/migrations/claim_v1_to_v2.py +33 -0
  102. multi_agent_brief/contracts/registry.py +159 -0
  103. multi_agent_brief/contracts/role_topology.py +25 -0
  104. multi_agent_brief/contracts/schemas/__init__.py +35 -0
  105. multi_agent_brief/contracts/schemas/analysis_pack.py +135 -0
  106. multi_agent_brief/contracts/schemas/atomic_claim_graph.py +304 -0
  107. multi_agent_brief/contracts/schemas/audit_report.py +80 -0
  108. multi_agent_brief/contracts/schemas/candidate_item.py +51 -0
  109. multi_agent_brief/contracts/schemas/claim.py +197 -0
  110. multi_agent_brief/contracts/schemas/claim_draft.py +386 -0
  111. multi_agent_brief/contracts/schemas/claim_support_matrix.py +334 -0
  112. multi_agent_brief/contracts/schemas/evidence_span_registry.py +270 -0
  113. multi_agent_brief/contracts/schemas/policy_profile.py +247 -0
  114. multi_agent_brief/contracts/schemas/report_spec.py +257 -0
  115. multi_agent_brief/contracts/schemas/semantic_assessment_report.py +480 -0
  116. multi_agent_brief/contracts/schemas/source_evidence_pack_manifest.py +233 -0
  117. multi_agent_brief/contracts/schemas/source_item.py +96 -0
  118. multi_agent_brief/contracts/semantic_assessment_status.py +12 -0
  119. multi_agent_brief/contracts/source_metadata.py +252 -0
  120. multi_agent_brief/contracts/target_contract.py +460 -0
  121. multi_agent_brief/contracts/validator.py +225 -0
  122. multi_agent_brief/controls/__init__.py +23 -0
  123. multi_agent_brief/controls/contract.py +106 -0
  124. multi_agent_brief/controls/switchboard.py +1076 -0
  125. multi_agent_brief/core/__init__.py +2 -0
  126. multi_agent_brief/core/citations.py +14 -0
  127. multi_agent_brief/core/claim_ledger.py +94 -0
  128. multi_agent_brief/core/config.py +191 -0
  129. multi_agent_brief/core/env.py +69 -0
  130. multi_agent_brief/core/previous.py +83 -0
  131. multi_agent_brief/core/schemas.py +181 -0
  132. multi_agent_brief/core/selection.py +189 -0
  133. multi_agent_brief/delivery/__init__.py +6 -0
  134. multi_agent_brief/delivery/base.py +36 -0
  135. multi_agent_brief/delivery/feishu.py +204 -0
  136. multi_agent_brief/delivery/gws.py +237 -0
  137. multi_agent_brief/evaluation_cases/__init__.py +15 -0
  138. multi_agent_brief/evaluation_cases/contract.py +429 -0
  139. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/config.yaml +18 -0
  140. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/improvement/ledger.jsonl +2 -0
  141. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/sources.yaml +2 -0
  142. multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/user.md +4 -0
  143. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/config.yaml +13 -0
  144. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/sources.yaml +9 -0
  145. multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/user.md +4 -0
  146. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/config.yaml +12 -0
  147. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/sources.yaml +6 -0
  148. multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/user.md +4 -0
  149. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/config.yaml +6 -0
  150. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/input/human_feedback.md +1 -0
  151. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/sources.yaml +2 -0
  152. multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/user.md +3 -0
  153. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/config.yaml +8 -0
  154. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/brief.md +14 -0
  155. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audit_report.json +1 -0
  156. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audited_brief.md +5 -0
  157. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/candidate_claims.json +1 -0
  158. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/claim_ledger.json +1 -0
  159. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/screened_candidates.json +1 -0
  160. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/sources.yaml +2 -0
  161. multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/user.md +3 -0
  162. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/config.yaml +13 -0
  163. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/sources.yaml +9 -0
  164. multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/user.md +4 -0
  165. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/config.yaml +4 -0
  166. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/sources.yaml +2 -0
  167. multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/user.md +3 -0
  168. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/config.yaml +13 -0
  169. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/sources.yaml +9 -0
  170. multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/user.md +4 -0
  171. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/config.yaml +13 -0
  172. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/sources.yaml +9 -0
  173. multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/user.md +5 -0
  174. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/config.yaml +6 -0
  175. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/audited_brief.md +3 -0
  176. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/candidate_claims.json +1 -0
  177. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/claim_ledger.json +1 -0
  178. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/feedback_issues.json +25 -0
  179. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/repair_plan.json +27 -0
  180. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/screened_candidates.json +1 -0
  181. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/sources.yaml +2 -0
  182. multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/user.md +3 -0
  183. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/config.yaml +8 -0
  184. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audit_report.json +1 -0
  185. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audited_brief.md +3 -0
  186. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/candidate_claims.json +1 -0
  187. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/claim_ledger.json +11 -0
  188. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/quality_gate_report.json +22 -0
  189. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/screened_candidates.json +1 -0
  190. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/sources.yaml +2 -0
  191. multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/user.md +3 -0
  192. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/config.yaml +13 -0
  193. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audit_report.json +1 -0
  194. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audited_brief.md +5 -0
  195. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/candidate_claims.json +1 -0
  196. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/claim_ledger.json +40 -0
  197. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/screened_candidates.json +1 -0
  198. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/sources.yaml +3 -0
  199. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/user.md +3 -0
  200. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/config.yaml +8 -0
  201. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/brief.md +3 -0
  202. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audit_report.json +1 -0
  203. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audited_brief.md +3 -0
  204. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/candidate_claims.json +1 -0
  205. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/claim_ledger.json +13 -0
  206. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/screened_candidates.json +1 -0
  207. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/sources.yaml +2 -0
  208. multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/user.md +3 -0
  209. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/config.yaml +13 -0
  210. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/output/intermediate/release_readiness_report.json +31 -0
  211. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/sources.yaml +9 -0
  212. multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/user.md +4 -0
  213. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/config.yaml +18 -0
  214. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/ledger.jsonl +3 -0
  215. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/memory.md +3 -0
  216. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/output/intermediate/improvement_memory_snapshot.md +3 -0
  217. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/sources.yaml +2 -0
  218. multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/user.md +4 -0
  219. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/config.yaml +12 -0
  220. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/input/sources/source-001.txt +1 -0
  221. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/brief.md +37 -0
  222. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/delivery/brief.md +37 -0
  223. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/atomic_claim_graph.json +17 -0
  224. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audit_report.json +5 -0
  225. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audited_brief.md +37 -0
  226. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/claim_ledger.json +20 -0
  227. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/evidence_span_registry.json +20 -0
  228. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/finalize_report.json +40 -0
  229. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/auditor_quality_gate_report.json +42 -0
  230. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/finalize_quality_gate_report.json +42 -0
  231. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/screened_candidates.json +33 -0
  232. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix.md +12 -0
  233. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix_trace.md +10 -0
  234. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/report_spec.yaml +24 -0
  235. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/sources.yaml +4 -0
  236. multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/user.md +3 -0
  237. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/config.yaml +13 -0
  238. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/input/sources/README.md +4 -0
  239. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/output/intermediate/source_evidence_pack_manifest.json +26 -0
  240. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/sources.yaml +9 -0
  241. multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/user.md +4 -0
  242. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/config.yaml +9 -0
  243. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audit_report.json +1 -0
  244. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audited_brief.md +3 -0
  245. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/candidate_claims.json +1 -0
  246. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/claim_ledger.json +13 -0
  247. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/screened_candidates.json +1 -0
  248. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/sources.yaml +2 -0
  249. multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/user.md +3 -0
  250. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/config.yaml +13 -0
  251. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/sources.yaml +9 -0
  252. multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/user.md +4 -0
  253. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/config.yaml +4 -0
  254. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/sources.yaml +2 -0
  255. multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/user.md +3 -0
  256. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/config.yaml +18 -0
  257. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/improvement/ledger.jsonl +1 -0
  258. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/sources.yaml +2 -0
  259. multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/user.md +4 -0
  260. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/config.yaml +18 -0
  261. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/sources.yaml +9 -0
  262. multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/user.md +5 -0
  263. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/config.yaml +8 -0
  264. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audit_report.json +1 -0
  265. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audited_brief.md +8 -0
  266. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/candidate_claims.json +1 -0
  267. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/claim_ledger.json +1 -0
  268. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/screened_candidates.json +1 -0
  269. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/sources.yaml +2 -0
  270. multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/user.md +3 -0
  271. multi_agent_brief/evaluation_cases/fixtures/manifest.yaml +861 -0
  272. multi_agent_brief/evaluation_cases/fixtures.py +20 -0
  273. multi_agent_brief/evaluation_cases/runner.py +1603 -0
  274. multi_agent_brief/experiments/__init__.py +45 -0
  275. multi_agent_brief/experiments/experiment_080.py +5835 -0
  276. multi_agent_brief/experiments/schemas.py +63 -0
  277. multi_agent_brief/experiments/target_contract.py +10 -0
  278. multi_agent_brief/feedback/__init__.py +17 -0
  279. multi_agent_brief/feedback/feedback_contract.py +482 -0
  280. multi_agent_brief/feedback/feedback_state.py +898 -0
  281. multi_agent_brief/hermes/__init__.py +25 -0
  282. multi_agent_brief/hermes/adapter.py +832 -0
  283. multi_agent_brief/improvement/__init__.py +83 -0
  284. multi_agent_brief/improvement/contract.py +1114 -0
  285. multi_agent_brief/improvement/memory.py +451 -0
  286. multi_agent_brief/improvement/product_definition.py +228 -0
  287. multi_agent_brief/improvement/state.py +961 -0
  288. multi_agent_brief/inputs/__init__.py +2 -0
  289. multi_agent_brief/inputs/classifier.py +322 -0
  290. multi_agent_brief/inputs/contracts.py +59 -0
  291. multi_agent_brief/inputs/extractor.py +310 -0
  292. multi_agent_brief/install/__init__.py +2 -0
  293. multi_agent_brief/install/writer.py +97 -0
  294. multi_agent_brief/onboarding/__init__.py +11 -0
  295. multi_agent_brief/onboarding/io.py +103 -0
  296. multi_agent_brief/onboarding/mapper.py +484 -0
  297. multi_agent_brief/onboarding/schema.py +46 -0
  298. multi_agent_brief/orchestrator/__init__.py +2 -0
  299. multi_agent_brief/orchestrator/fact_layer_import.py +352 -0
  300. multi_agent_brief/orchestrator/handoff.py +2209 -0
  301. multi_agent_brief/orchestrator/role_topology.py +42 -0
  302. multi_agent_brief/orchestrator/run_archive.py +888 -0
  303. multi_agent_brief/orchestrator/run_integrity.py +346 -0
  304. multi_agent_brief/orchestrator/runtime_state/__init__.py +70 -0
  305. multi_agent_brief/orchestrator/runtime_state/_io.py +167 -0
  306. multi_agent_brief/orchestrator/runtime_state/_transactions.py +298 -0
  307. multi_agent_brief/orchestrator/runtime_state/artifact_registry.py +1825 -0
  308. multi_agent_brief/orchestrator/runtime_state/atomic_claim_graph.py +96 -0
  309. multi_agent_brief/orchestrator/runtime_state/claim_ledger_freeze.py +510 -0
  310. multi_agent_brief/orchestrator/runtime_state/claim_metadata_enrichment.py +836 -0
  311. multi_agent_brief/orchestrator/runtime_state/claim_support_matrix.py +582 -0
  312. multi_agent_brief/orchestrator/runtime_state/completion_gates.py +672 -0
  313. multi_agent_brief/orchestrator/runtime_state/contracts_loader.py +66 -0
  314. multi_agent_brief/orchestrator/runtime_state/decisions.py +239 -0
  315. multi_agent_brief/orchestrator/runtime_state/errors.py +61 -0
  316. multi_agent_brief/orchestrator/runtime_state/event_log.py +256 -0
  317. multi_agent_brief/orchestrator/runtime_state/evidence_span_registry.py +233 -0
  318. multi_agent_brief/orchestrator/runtime_state/fact_layer.py +755 -0
  319. multi_agent_brief/orchestrator/runtime_state/identity.py +90 -0
  320. multi_agent_brief/orchestrator/runtime_state/lifecycle.py +675 -0
  321. multi_agent_brief/orchestrator/runtime_state/manifest.py +82 -0
  322. multi_agent_brief/orchestrator/runtime_state/operations.py +224 -0
  323. multi_agent_brief/orchestrator/runtime_state/paths.py +38 -0
  324. multi_agent_brief/orchestrator/runtime_state/repair.py +984 -0
  325. multi_agent_brief/orchestrator/runtime_state/safety_surfaces.py +147 -0
  326. multi_agent_brief/orchestrator/runtime_state/semantic_assessment_report.py +692 -0
  327. multi_agent_brief/orchestrator/runtime_state/semantic_support_acceptance.py +683 -0
  328. multi_agent_brief/orchestrator/runtime_state/source_evidence_pack.py +131 -0
  329. multi_agent_brief/orchestrator/runtime_state/stage_completion.py +1320 -0
  330. multi_agent_brief/orchestrator/runtime_state/trajectory.py +200 -0
  331. multi_agent_brief/orchestrator/runtime_state/workflow.py +350 -0
  332. multi_agent_brief/orchestrator/source_evidence.py +43 -0
  333. multi_agent_brief/orchestrator/timing.py +410 -0
  334. multi_agent_brief/orchestrator_contract.py +96 -0
  335. multi_agent_brief/outputs/__init__.py +2 -0
  336. multi_agent_brief/outputs/atomic_reader_projection.py +288 -0
  337. multi_agent_brief/outputs/docx.py +32 -0
  338. multi_agent_brief/outputs/finalize.py +1036 -0
  339. multi_agent_brief/outputs/ib_docx.py +983 -0
  340. multi_agent_brief/outputs/naming.py +38 -0
  341. multi_agent_brief/outputs/pdf.py +20 -0
  342. multi_agent_brief/outputs/reader_final_gate.py +509 -0
  343. multi_agent_brief/outputs/reader_projection.py +397 -0
  344. multi_agent_brief/outputs/source_appendix.py +753 -0
  345. multi_agent_brief/outputs/templates/__init__.py +112 -0
  346. multi_agent_brief/product/__init__.py +49 -0
  347. multi_agent_brief/product/bundle_projection.py +686 -0
  348. multi_agent_brief/product/citation_profile.py +145 -0
  349. multi_agent_brief/product/guidance_manifestation.py +421 -0
  350. multi_agent_brief/product/materiality_selection.py +472 -0
  351. multi_agent_brief/product/policy_gate_adapter.py +116 -0
  352. multi_agent_brief/product/policy_profile.py +39 -0
  353. multi_agent_brief/product/policy_projection.py +161 -0
  354. multi_agent_brief/product/policy_registry.py +83 -0
  355. multi_agent_brief/product/policy_resolver.py +225 -0
  356. multi_agent_brief/product/quality_closeout.py +188 -0
  357. multi_agent_brief/product/quality_panel.py +2217 -0
  358. multi_agent_brief/product/release_approval.py +1034 -0
  359. multi_agent_brief/product/report_pack.py +130 -0
  360. multi_agent_brief/product/report_pack_aliases.py +68 -0
  361. multi_agent_brief/product/report_registry.py +101 -0
  362. multi_agent_brief/product/report_spec.py +181 -0
  363. multi_agent_brief/product/support_wording.py +525 -0
  364. multi_agent_brief/product/template_conformance.py +516 -0
  365. multi_agent_brief/product/template_projection.py +123 -0
  366. multi_agent_brief/product/template_registry.py +244 -0
  367. multi_agent_brief/product/template_render_plan.py +315 -0
  368. multi_agent_brief/product/template_renderer.py +224 -0
  369. multi_agent_brief/product/trajectory_regulation.py +441 -0
  370. multi_agent_brief/provenance/__init__.py +5 -0
  371. multi_agent_brief/provenance/builder.py +841 -0
  372. multi_agent_brief/provenance/contract.py +21 -0
  373. multi_agent_brief/provenance/io.py +144 -0
  374. multi_agent_brief/provenance/model.py +110 -0
  375. multi_agent_brief/provenance/references.py +43 -0
  376. multi_agent_brief/provenance/validator.py +135 -0
  377. multi_agent_brief/quality_gates/__init__.py +19 -0
  378. multi_agent_brief/quality_gates/contract.py +581 -0
  379. multi_agent_brief/quality_gates/state.py +2498 -0
  380. multi_agent_brief/repair/__init__.py +5 -0
  381. multi_agent_brief/repair/router.py +842 -0
  382. multi_agent_brief/runtime_assets.py +431 -0
  383. multi_agent_brief/sources/__init__.py +16 -0
  384. multi_agent_brief/sources/api_filings.py +236 -0
  385. multi_agent_brief/sources/api_news.py +174 -0
  386. multi_agent_brief/sources/base.py +142 -0
  387. multi_agent_brief/sources/cached_package.py +119 -0
  388. multi_agent_brief/sources/cli_provider.py +263 -0
  389. multi_agent_brief/sources/coverage.py +336 -0
  390. multi_agent_brief/sources/decider.py +756 -0
  391. multi_agent_brief/sources/doctor.py +373 -0
  392. multi_agent_brief/sources/evidence_pack.py +364 -0
  393. multi_agent_brief/sources/feishu_provider.py +326 -0
  394. multi_agent_brief/sources/filing_resolver.py +400 -0
  395. multi_agent_brief/sources/industry_packs.py +130 -0
  396. multi_agent_brief/sources/join.py +206 -0
  397. multi_agent_brief/sources/local_signal.py +81 -0
  398. multi_agent_brief/sources/local_signal_planner.py +636 -0
  399. multi_agent_brief/sources/manual.py +259 -0
  400. multi_agent_brief/sources/mcp_provider.py +309 -0
  401. multi_agent_brief/sources/mineru_provider.py +601 -0
  402. multi_agent_brief/sources/normalizer.py +95 -0
  403. multi_agent_brief/sources/opencli_provider.py +282 -0
  404. multi_agent_brief/sources/planner.py +178 -0
  405. multi_agent_brief/sources/registry.py +367 -0
  406. multi_agent_brief/sources/rss.py +190 -0
  407. multi_agent_brief/sources/search_backends/__init__.py +31 -0
  408. multi_agent_brief/sources/search_backends/base.py +51 -0
  409. multi_agent_brief/sources/search_backends/brave.py +192 -0
  410. multi_agent_brief/sources/search_backends/capabilities.py +94 -0
  411. multi_agent_brief/sources/search_backends/exa.py +180 -0
  412. multi_agent_brief/sources/search_backends/firecrawl.py +164 -0
  413. multi_agent_brief/sources/search_backends/serper.py +241 -0
  414. multi_agent_brief/sources/search_backends/tavily.py +124 -0
  415. multi_agent_brief/sources/sourcehub.py +505 -0
  416. multi_agent_brief/sources/web_search.py +267 -0
  417. multi_agent_brief/status.py +966 -0
  418. multi_agent_brief/tools/__init__.py +3 -0
  419. multi_agent_brief/tools/draft_cleanup.py +198 -0
  420. multi_agent_brief/workbuddy/__init__.py +1 -0
  421. multi_agent_brief/workbuddy/diagnose.py +569 -0
  422. multi_agent_brief/workbuddy/skill_pack.py +309 -0
  423. multi_agent_brief/workspace/__init__.py +1 -0
  424. multi_agent_brief/workspace/init_profile.py +45 -0
@@ -0,0 +1,672 @@
1
+ """Completion-gate orchestration helpers for Orchestrator runtime state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import date, datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import yaml
11
+
12
+ from multi_agent_brief.core.claim_ledger import ClaimLedger
13
+ from multi_agent_brief.quality_gates.contract import (
14
+ interpret_quality_gate_binding,
15
+ require_quality_gate_binding_pass,
16
+ )
17
+ from multi_agent_brief.orchestrator.role_topology import (
18
+ resolve_role_topology,
19
+ stage_satisfaction_rules_for_topology,
20
+ )
21
+ from multi_agent_brief.orchestrator.runtime_state._io import (
22
+ _load_workspace_yaml,
23
+ _read_json,
24
+ )
25
+ from multi_agent_brief.orchestrator.runtime_state.artifact_registry import (
26
+ ARTIFACT_INVALID,
27
+ ARTIFACT_VALID,
28
+ _validate_artifact,
29
+ )
30
+ from multi_agent_brief.orchestrator.runtime_state.errors import RuntimeStateError
31
+ from multi_agent_brief.orchestrator.runtime_state.identity import utc_now
32
+ from multi_agent_brief.orchestrator.source_evidence import is_evidence_input_path
33
+ from multi_agent_brief.outputs.finalize import (
34
+ interpret_finalize_audit_binding,
35
+ require_finalize_audit_binding_pass,
36
+ )
37
+ from multi_agent_brief.outputs.reader_final_gate import (
38
+ combine_reader_final_gate_results,
39
+ detect_reader_residue,
40
+ detect_reader_residue_in_docx,
41
+ )
42
+ from multi_agent_brief.product.policy_gate_adapter import (
43
+ policy_forbidden_phrases,
44
+ resolve_workspace_policy_gate_adapter,
45
+ )
46
+
47
+
48
+ def _role_topology_from_policy_pack(policy_pack: dict[str, Any] | None) -> str:
49
+ """Return the configured role topology for Layer-D gate logic."""
50
+
51
+ return resolve_role_topology(policy_pack)
52
+
53
+
54
+ def _topology_satisfaction_rules(
55
+ *,
56
+ stages: list[dict[str, Any]],
57
+ policy_pack: dict[str, Any] | None,
58
+ ) -> dict[str, dict[str, Any]]:
59
+ """Return declared satisfaction hooks without applying stage progress."""
60
+
61
+ return stage_satisfaction_rules_for_topology(stages=stages, policy_pack=policy_pack)
62
+
63
+
64
+ def _completion_artifact_gate_reasons(
65
+ *,
66
+ workspace: Path,
67
+ stage: dict[str, Any],
68
+ artifacts_by_id: dict[str, dict[str, Any]],
69
+ ) -> list[str]:
70
+ return _artifact_gate_reasons_for_ids(
71
+ workspace=workspace,
72
+ artifact_ids=[str(item) for item in (stage.get("expected_artifacts") or [])],
73
+ artifacts_by_id=artifacts_by_id,
74
+ reason_prefix="Required expected artifact",
75
+ optional_prefix="Optional expected artifact",
76
+ )
77
+
78
+
79
+ def _topology_satisfaction_artifact_reasons(
80
+ *,
81
+ workspace: Path,
82
+ stage_id: str,
83
+ rule: dict[str, Any],
84
+ artifacts_by_id: dict[str, dict[str, Any]],
85
+ ) -> list[str]:
86
+ artifact_ids = [
87
+ str(item)
88
+ for item in (rule.get("required_artifacts") or [])
89
+ if item
90
+ ]
91
+ return _artifact_gate_reasons_for_ids(
92
+ workspace=workspace,
93
+ artifact_ids=artifact_ids,
94
+ artifacts_by_id=artifacts_by_id,
95
+ reason_prefix=f"Required topology artifact for stage '{stage_id}'",
96
+ optional_prefix=f"Optional topology artifact for stage '{stage_id}'",
97
+ )
98
+
99
+
100
+ def _artifact_gate_reasons_for_ids(
101
+ *,
102
+ workspace: Path,
103
+ artifact_ids: list[str],
104
+ artifacts_by_id: dict[str, dict[str, Any]],
105
+ reason_prefix: str,
106
+ optional_prefix: str,
107
+ ) -> list[str]:
108
+ reasons: list[str] = []
109
+ for artifact_id in artifact_ids:
110
+ contract = artifacts_by_id.get(str(artifact_id))
111
+ if not contract:
112
+ reasons.append(f"{reason_prefix} '{artifact_id}' is not declared in artifact contracts.")
113
+ continue
114
+ rel_path = str(contract.get("path") or "")
115
+ fmt = str(contract.get("format") or "")
116
+ status, validation_result = _validate_artifact(workspace / rel_path, fmt, str(artifact_id))
117
+ required = bool(contract.get("required", False))
118
+ if required and status != ARTIFACT_VALID:
119
+ reasons.append(
120
+ f"{reason_prefix} '{artifact_id}' at '{rel_path}' is {status} ({validation_result})."
121
+ )
122
+ elif not required and status == ARTIFACT_INVALID:
123
+ reasons.append(
124
+ f"{optional_prefix} '{artifact_id}' at '{rel_path}' is invalid ({validation_result})."
125
+ )
126
+ return reasons
127
+
128
+
129
+ def _count_evidence_files(path: Path, workspace: Path) -> int:
130
+ if not path.exists() or not is_evidence_input_path(path, workspace):
131
+ return 0
132
+ if path.is_file():
133
+ return 1
134
+ if path.is_dir():
135
+ return sum(
136
+ 1
137
+ for item in path.rglob("*")
138
+ if item.is_file() and is_evidence_input_path(item, workspace)
139
+ )
140
+ return 0
141
+
142
+
143
+ def _configured_evidence_source_count(sources: dict[str, Any], workspace: Path) -> int:
144
+ count = 0
145
+ manual = sources.get("manual") if isinstance(sources.get("manual"), dict) else {}
146
+ manual_sources = manual.get("sources") if isinstance(manual.get("sources"), list) else []
147
+ for item in manual_sources:
148
+ if not isinstance(item, dict):
149
+ continue
150
+ if item.get("enabled") is False:
151
+ continue
152
+ if item.get("url"):
153
+ count += 1
154
+ continue
155
+ raw_path = item.get("path")
156
+ if raw_path:
157
+ source_path = Path(str(raw_path))
158
+ if not source_path.is_absolute():
159
+ source_path = workspace / source_path
160
+ count += _count_evidence_files(source_path, workspace)
161
+
162
+ rss = sources.get("rss") if isinstance(sources.get("rss"), dict) else {}
163
+ feeds = rss.get("feeds") if isinstance(rss.get("feeds"), list) else []
164
+ count += len([
165
+ item
166
+ for item in feeds
167
+ if isinstance(item, dict) and item.get("enabled", True) and item.get("url")
168
+ ])
169
+
170
+ cached = sources.get("cached_package") if isinstance(sources.get("cached_package"), dict) else {}
171
+ if cached.get("enabled"):
172
+ paths = cached.get("paths") if isinstance(cached.get("paths"), list) else []
173
+ for raw_path in paths:
174
+ source_path = Path(str(raw_path))
175
+ if not source_path.is_absolute():
176
+ source_path = workspace / source_path
177
+ count += _count_evidence_files(source_path, workspace)
178
+
179
+ filing_resolver = (
180
+ sources.get("filing_resolver")
181
+ if isinstance(sources.get("filing_resolver"), dict)
182
+ else {}
183
+ )
184
+ if filing_resolver.get("enabled"):
185
+ tickers = filing_resolver.get("tickers")
186
+ if isinstance(tickers, list):
187
+ count += len([item for item in tickers if item])
188
+
189
+ feishu = sources.get("feishu") if isinstance(sources.get("feishu"), dict) else {}
190
+ if feishu.get("enabled"):
191
+ feishu_sources = feishu.get("sources")
192
+ if isinstance(feishu_sources, list):
193
+ count += len([item for item in feishu_sources if item])
194
+
195
+ mcp = sources.get("mcp") if isinstance(sources.get("mcp"), dict) else {}
196
+ if mcp.get("enabled"):
197
+ servers = mcp.get("servers")
198
+ if isinstance(servers, list):
199
+ count += len([item for item in servers if item])
200
+
201
+ input_dir = workspace / "input"
202
+ if input_dir.exists():
203
+ count += sum(
204
+ 1
205
+ for item in input_dir.rglob("*")
206
+ if item.is_file() and is_evidence_input_path(item, workspace)
207
+ )
208
+ return count
209
+
210
+
211
+ def _runtime_search_observation_counts(path: Path) -> tuple[bool, list[int]]:
212
+ if not path.exists():
213
+ return False, []
214
+ try:
215
+ text = path.read_text(encoding="utf-8")
216
+ data = yaml.safe_load(text) or {}
217
+ except (OSError, yaml.YAMLError):
218
+ return False, []
219
+ if "Did 0 searches" in text:
220
+ return True, [0]
221
+ if not isinstance(data, dict):
222
+ return False, []
223
+
224
+ counts: list[int] = []
225
+
226
+ def walk(value: Any) -> None:
227
+ if isinstance(value, dict):
228
+ lowered = {str(key).lower(): val for key, val in value.items()}
229
+ for key in ("result_count", "results_count", "search_count", "observation_count"):
230
+ if key in lowered:
231
+ try:
232
+ counts.append(int(lowered[key]))
233
+ except (TypeError, ValueError):
234
+ pass
235
+ for item in value.values():
236
+ walk(item)
237
+ elif isinstance(value, list):
238
+ for item in value:
239
+ walk(item)
240
+
241
+ walk(data)
242
+ return bool(counts), counts
243
+
244
+
245
+ def _contains_zero_runtime_search_observation(path: Path) -> bool:
246
+ has_observation, counts = _runtime_search_observation_counts(path)
247
+ return has_observation and counts and all(count == 0 for count in counts)
248
+
249
+
250
+ def _source_candidates_is_plan_only(path: Path) -> bool:
251
+ if not path.exists():
252
+ return False
253
+ data = _load_workspace_yaml(path)
254
+ artifact_type = str(data.get("artifact_type") or "")
255
+ evidence_status = str(data.get("evidence_status") or "")
256
+ return artifact_type == "source_plan_only" or evidence_status == "not_evidence"
257
+
258
+
259
+ def _source_discovery_evidence_reasons(workspace: Path) -> list[str]:
260
+ sources = _load_workspace_yaml(workspace / "sources.yaml")
261
+ web_search = sources.get("web_search") if isinstance(sources.get("web_search"), dict) else {}
262
+ web_search_enabled = web_search.get("enabled") is True
263
+ web_search_mode = str(web_search.get("mode") or "")
264
+ candidates_path = workspace / "source_candidates.yaml"
265
+ evidence_count = _configured_evidence_source_count(sources, workspace)
266
+ has_evidence = evidence_count > 0
267
+
268
+ reasons: list[str] = []
269
+ if (
270
+ web_search_enabled
271
+ and web_search_mode == "runtime_tool"
272
+ and _contains_zero_runtime_search_observation(candidates_path)
273
+ ):
274
+ reasons.append(
275
+ "Runtime WebSearch source discovery reported zero searches or zero observations; request human review instead of completing source-discovery."
276
+ )
277
+
278
+ if has_evidence:
279
+ return reasons
280
+
281
+ if _source_candidates_is_plan_only(candidates_path):
282
+ reasons.append(
283
+ "source_candidates.yaml is a source plan, not evidence; materialize approved sources into input/sources/ or supported source configuration before completing source-discovery."
284
+ )
285
+ if web_search_enabled and web_search_mode == "configure_later":
286
+ reasons.append(
287
+ "Cannot complete source-discovery: web_search.mode is configure_later, and no durable evidence source is available."
288
+ )
289
+ if web_search_enabled and web_search_mode == "runtime_tool":
290
+ reasons.append(
291
+ "Cannot complete source-discovery: runtime_tool web search is enabled, but no evidence source is available. Runtime WebSearch results must be written as durable source files under input/sources/ or into supported source configuration. source_candidates.yaml is a source plan, not evidence."
292
+ )
293
+ return reasons
294
+
295
+
296
+ def _stage_quality_gate_pass_reasons(
297
+ *,
298
+ workspace: Path,
299
+ stage_id: str,
300
+ expected_brief: str,
301
+ expected_ledger: str,
302
+ stages: list[dict[str, Any]],
303
+ artifacts: list[dict[str, Any]],
304
+ ) -> list[str]:
305
+ verdict = interpret_quality_gate_binding(
306
+ workspace=workspace,
307
+ stage_id=stage_id,
308
+ expected_brief=expected_brief,
309
+ expected_ledger=expected_ledger,
310
+ stages=stages,
311
+ artifacts=artifacts,
312
+ )
313
+ return require_quality_gate_binding_pass(verdict)
314
+
315
+
316
+ def _quality_gate_pass_reasons(
317
+ *,
318
+ workspace: Path,
319
+ stages: list[dict[str, Any]],
320
+ artifacts: list[dict[str, Any]],
321
+ ) -> list[str]:
322
+ return _stage_quality_gate_pass_reasons(
323
+ workspace=workspace,
324
+ stage_id="auditor",
325
+ expected_brief="output/intermediate/audited_brief.md",
326
+ expected_ledger="output/intermediate/claim_ledger.json",
327
+ stages=stages,
328
+ artifacts=artifacts,
329
+ )
330
+
331
+
332
+ def _finalize_quality_gate_pass_reasons(
333
+ *,
334
+ workspace: Path,
335
+ stages: list[dict[str, Any]],
336
+ artifacts: list[dict[str, Any]],
337
+ ) -> list[str]:
338
+ return _stage_quality_gate_pass_reasons(
339
+ workspace=workspace,
340
+ stage_id="finalize",
341
+ expected_brief="output/brief.md",
342
+ expected_ledger="output/intermediate/claim_ledger.json",
343
+ stages=stages,
344
+ artifacts=artifacts,
345
+ )
346
+
347
+
348
+ def _resolve_report_artifact_path(workspace: Path, value: Any) -> Path | None:
349
+ if not isinstance(value, str) or not value.strip():
350
+ return None
351
+ path = Path(value).expanduser()
352
+ if not path.is_absolute():
353
+ path = workspace / path
354
+ return path.resolve()
355
+
356
+
357
+ def _finalize_report_reader_artifact_paths(workspace: Path, report: dict[str, Any]) -> list[Path]:
358
+ paths: list[Path] = []
359
+ required_brief = workspace / "output" / "brief.md"
360
+ paths.append(required_brief.resolve())
361
+ for key in ("reader_brief", "named_reader_brief", "reader_docx", "named_reader_docx", "source_appendix"):
362
+ path = _resolve_report_artifact_path(workspace, report.get(key))
363
+ if path is not None:
364
+ paths.append(path)
365
+ unique: list[Path] = []
366
+ seen: set[str] = set()
367
+ for path in paths:
368
+ marker = str(path)
369
+ if marker not in seen:
370
+ seen.add(marker)
371
+ unique.append(path)
372
+ return unique
373
+
374
+
375
+ def _finalize_report_auxiliary_artifact_reasons(workspace: Path, report: dict[str, Any]) -> list[str]:
376
+ reasons: list[str] = []
377
+ for key in ("source_appendix_trace",):
378
+ path = _resolve_report_artifact_path(workspace, report.get(key))
379
+ if path is None:
380
+ continue
381
+ if not path.exists():
382
+ reasons.append(f"finalize_report.json references missing auxiliary artifact {key}: {path}.")
383
+ return reasons
384
+
385
+
386
+ def _finalize_report_delivery_artifact_reasons(workspace: Path, report: dict[str, Any]) -> list[str]:
387
+ artifacts = report.get("delivery_artifacts")
388
+ if not isinstance(artifacts, list) or not artifacts:
389
+ return ["finalize_report.json delivery_artifacts must list the reader delivery bundle."]
390
+ reasons: list[str] = []
391
+ delivery_root = (workspace / "output" / "delivery").resolve()
392
+ for item in artifacts:
393
+ path = _resolve_report_artifact_path(workspace, item)
394
+ if path is None:
395
+ reasons.append("finalize_report.json contains an invalid delivery_artifacts entry.")
396
+ continue
397
+ if not path.exists():
398
+ reasons.append(f"finalize_report.json references missing delivery artifact: {path}.")
399
+ continue
400
+ try:
401
+ path.relative_to(delivery_root)
402
+ except ValueError:
403
+ reasons.append(
404
+ "finalize_report.json delivery_artifacts may only reference files under output/delivery."
405
+ )
406
+ return reasons
407
+
408
+
409
+ def _parse_control_date(value: Any) -> date | None:
410
+ if value is None:
411
+ return None
412
+ text = str(value).strip()
413
+ if not text:
414
+ return None
415
+ for fmt, width in (("%Y-%m-%d", 10), ("%Y%m%d", 8)):
416
+ try:
417
+ return datetime.strptime(text[:width], fmt).date()
418
+ except ValueError:
419
+ continue
420
+ try:
421
+ return datetime.fromisoformat(text.replace("Z", "+00:00")).date()
422
+ except ValueError:
423
+ return None
424
+
425
+
426
+ def _fast_rerun_report_freshness_settings(workspace: Path) -> dict[str, Any]:
427
+ config = _load_workspace_yaml(workspace / "config.yaml")
428
+ report = config.get("report") if isinstance(config.get("report"), dict) else {}
429
+ report_date = str(report.get("date") or "")
430
+ max_age_raw = report.get("max_source_age_days")
431
+ max_source_age_days: int | None = None
432
+ if max_age_raw is not None:
433
+ try:
434
+ max_source_age_days = int(max_age_raw)
435
+ except (TypeError, ValueError):
436
+ max_source_age_days = None
437
+ return {
438
+ "report_date": report_date,
439
+ "max_source_age_days": max_source_age_days,
440
+ "fail_on_stale_source": report.get("fail_on_stale_source"),
441
+ }
442
+
443
+
444
+ def _fast_rerun_import_freshness_snapshot(workspace: Path, *, checked_at: str) -> dict[str, Any]:
445
+ settings = _fast_rerun_report_freshness_settings(workspace)
446
+ ledger_path = workspace / "output" / "intermediate" / "claim_ledger.json"
447
+ report_day = _parse_control_date(settings["report_date"])
448
+ max_age = settings["max_source_age_days"]
449
+ dates: list[dict[str, Any]] = []
450
+ missing_date_claim_ids: list[str] = []
451
+ stale_claims: list[dict[str, Any]] = []
452
+ try:
453
+ ledger = ClaimLedger.import_json(ledger_path)
454
+ except Exception as exc:
455
+ return {
456
+ "schema_version": "mabw.fact_layer_import.freshness.v1",
457
+ "checked_at": checked_at,
458
+ "status": "unknown",
459
+ "reason": "claim_ledger_unreadable",
460
+ "error": str(exc),
461
+ **settings,
462
+ }
463
+
464
+ for claim in ledger:
465
+ date_text = str(
466
+ claim.metadata.get("published_at")
467
+ or claim.metadata.get("publication_date")
468
+ or ""
469
+ )
470
+ source_day = _parse_control_date(date_text)
471
+ if source_day is None:
472
+ missing_date_claim_ids.append(claim.claim_id)
473
+ continue
474
+ dates.append({
475
+ "claim_id": claim.claim_id,
476
+ "source_date": source_day.isoformat(),
477
+ "source_date_text": date_text,
478
+ })
479
+ if report_day is not None and max_age is not None:
480
+ age_days = (report_day - source_day).days
481
+ if age_days > max_age:
482
+ stale_claims.append({
483
+ "claim_id": claim.claim_id,
484
+ "source_date": source_day.isoformat(),
485
+ "age_days": age_days,
486
+ })
487
+
488
+ if report_day is None or max_age is None:
489
+ status = "unknown"
490
+ reason = "report_date_or_max_source_age_missing"
491
+ elif missing_date_claim_ids:
492
+ status = "unknown"
493
+ reason = "claim_publication_dates_missing"
494
+ elif stale_claims:
495
+ status = "stale"
496
+ reason = "source_age_exceeds_target_window"
497
+ else:
498
+ status = "within_window"
499
+ reason = ""
500
+
501
+ sorted_dates = sorted(item["source_date"] for item in dates)
502
+ return {
503
+ "schema_version": "mabw.fact_layer_import.freshness.v1",
504
+ "checked_at": checked_at,
505
+ "status": status,
506
+ "reason": reason,
507
+ "report_date": settings["report_date"],
508
+ "max_source_age_days": max_age,
509
+ "fail_on_stale_source": settings["fail_on_stale_source"],
510
+ "claim_count": len(ledger),
511
+ "dated_claim_count": len(dates),
512
+ "missing_date_claim_count": len(missing_date_claim_ids),
513
+ "missing_date_claim_ids": missing_date_claim_ids[:10],
514
+ "oldest_source_date": sorted_dates[0] if sorted_dates else "",
515
+ "newest_source_date": sorted_dates[-1] if sorted_dates else "",
516
+ "stale_claim_count": len(stale_claims),
517
+ "stale_claims": stale_claims[:10],
518
+ }
519
+
520
+
521
+ def _fast_rerun_finalize_freshness_snapshot(
522
+ workspace: Path,
523
+ manifest: dict[str, Any],
524
+ *,
525
+ checked_at: str,
526
+ ) -> dict[str, Any]:
527
+ record = manifest.get("fact_layer_import") if isinstance(manifest.get("fact_layer_import"), dict) else None
528
+ if not record:
529
+ return {}
530
+ return _fast_rerun_import_freshness_snapshot(workspace, checked_at=checked_at)
531
+
532
+
533
+ def _fast_rerun_import_freshness_reasons(snapshot: dict[str, Any]) -> list[str]:
534
+ if not snapshot:
535
+ return []
536
+ status = snapshot.get("status")
537
+ if status == "within_window":
538
+ return []
539
+ stale_claims = snapshot.get("stale_claims") if isinstance(snapshot.get("stale_claims"), list) else []
540
+ sample = ", ".join(
541
+ f"{item.get('claim_id')} ({item.get('age_days')}d)"
542
+ for item in stale_claims[:5]
543
+ if isinstance(item, dict)
544
+ )
545
+ if status == "stale":
546
+ return [
547
+ "Fast-rerun imported fact layer is stale at target delivery time "
548
+ f"(report_date={snapshot.get('report_date')}, "
549
+ f"max_source_age_days={snapshot.get('max_source_age_days')}, "
550
+ f"stale_claim_count={snapshot.get('stale_claim_count')}"
551
+ + (f", sample={sample}" if sample else "")
552
+ + ")."
553
+ ]
554
+ return [
555
+ "Fast-rerun imported fact layer freshness cannot be verified at target delivery time "
556
+ f"(status={status or 'unknown'}, reason={snapshot.get('reason') or 'unknown'}, "
557
+ f"report_date={snapshot.get('report_date')}, max_source_age_days={snapshot.get('max_source_age_days')}"
558
+ + ")."
559
+ ]
560
+
561
+
562
+ def _finalize_completion_reasons(
563
+ workspace: Path,
564
+ *,
565
+ stages: list[dict[str, Any]],
566
+ artifacts: list[dict[str, Any]],
567
+ runtime_manifest: dict[str, Any] | None = None,
568
+ fast_rerun_freshness_at_finalize: dict[str, Any] | None = None,
569
+ ) -> list[str]:
570
+ reasons: list[str] = []
571
+ reasons.extend(
572
+ _finalize_quality_gate_pass_reasons(
573
+ workspace=workspace,
574
+ stages=stages,
575
+ artifacts=artifacts,
576
+ )
577
+ )
578
+ report_path = workspace / "output" / "intermediate" / "finalize_report.json"
579
+ if not report_path.exists():
580
+ reasons.append("finalize_report.json is required before finalize-complete.")
581
+ return reasons
582
+ try:
583
+ report = json.loads(report_path.read_text(encoding="utf-8"))
584
+ except json.JSONDecodeError as exc:
585
+ return [f"finalize_report.json is invalid JSON: {exc}"]
586
+ except OSError as exc:
587
+ return [f"finalize_report.json could not be read: {exc}"]
588
+ if not isinstance(report, dict):
589
+ return ["finalize_report.json must contain an object."]
590
+ if report.get("status") != "pass":
591
+ reasons.append("finalize_report.json status must be pass.")
592
+ reader_clean = report.get("reader_clean")
593
+ if not isinstance(reader_clean, dict) or reader_clean.get("status") != "pass":
594
+ reasons.append("finalize_report.json reader_clean.status must be pass.")
595
+ reasons.extend(
596
+ require_finalize_audit_binding_pass(
597
+ interpret_finalize_audit_binding(
598
+ workspace=workspace,
599
+ finalize_report=report,
600
+ )
601
+ )
602
+ )
603
+ reasons.extend(_finalize_report_delivery_artifact_reasons(workspace, report))
604
+ reasons.extend(_finalize_report_auxiliary_artifact_reasons(workspace, report))
605
+ if runtime_manifest is None:
606
+ manifest_path = workspace / "output" / "intermediate" / "runtime_manifest.json"
607
+ try:
608
+ runtime_manifest = _read_json(manifest_path)
609
+ except RuntimeStateError as exc:
610
+ reasons.append(f"runtime_manifest.json could not be read for fast-rerun freshness check: {exc}")
611
+ runtime_manifest = None
612
+ if runtime_manifest is not None:
613
+ if fast_rerun_freshness_at_finalize is None:
614
+ fast_rerun_freshness_at_finalize = _fast_rerun_finalize_freshness_snapshot(
615
+ workspace,
616
+ runtime_manifest,
617
+ checked_at=utc_now(),
618
+ )
619
+ reasons.extend(_fast_rerun_import_freshness_reasons(fast_rerun_freshness_at_finalize))
620
+
621
+ artifact_paths = _finalize_report_reader_artifact_paths(workspace, report)
622
+ missing = [path for path in artifact_paths if not path.exists()]
623
+ if missing:
624
+ reasons.append(
625
+ "finalize_report.json references missing reader artifacts: "
626
+ + ", ".join(str(path) for path in missing)
627
+ )
628
+ return reasons
629
+
630
+ gate_results = []
631
+ forbidden_phrases = policy_forbidden_phrases(resolve_workspace_policy_gate_adapter(workspace))
632
+ for path in artifact_paths:
633
+ suffix = path.suffix.lower()
634
+ if suffix in {".md", ".markdown"}:
635
+ try:
636
+ gate_results.append(
637
+ detect_reader_residue(
638
+ path.read_text(encoding="utf-8"),
639
+ artifact=str(path),
640
+ forbidden_phrases=forbidden_phrases,
641
+ )
642
+ )
643
+ except OSError as exc:
644
+ reasons.append(f"Reader artifact could not be read: {path}: {exc}")
645
+ elif suffix == ".docx":
646
+ gate_results.append(
647
+ detect_reader_residue_in_docx(path, artifact=str(path), forbidden_phrases=forbidden_phrases)
648
+ )
649
+ if gate_results:
650
+ reader_gate = combine_reader_final_gate_results(gate_results)
651
+ if reader_gate.status == "fail":
652
+ reasons.append(
653
+ "Current reader artifacts fail reader final gate: "
654
+ f"{sum(reader_gate.counts.values())} residue findings."
655
+ )
656
+ return reasons
657
+
658
+
659
+ def _raise_completion_reasons(
660
+ *,
661
+ message: str,
662
+ reasons: list[str],
663
+ error_code: str,
664
+ details: dict[str, Any] | None = None,
665
+ ) -> None:
666
+ payload = dict(details or {})
667
+ payload["blocking_reasons"] = reasons
668
+ raise RuntimeStateError(
669
+ f"{message}: {' '.join(reasons)}",
670
+ details=payload,
671
+ error_code=error_code,
672
+ )