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,1320 @@
1
+ """Stage and finalize completion transactions.
2
+
3
+ Validates completion targets, applies topology satisfaction, appends
4
+ transaction events, snapshots the analyst draft, and owns the
5
+ auditable-target downstream guard.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import shlex
12
+ import uuid
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from multi_agent_brief.contracts.target_contract import (
17
+ AUDIT_BINDING_SCHEMA,
18
+ auditable_gate_has_only_final_abstract_advisory_warnings,
19
+ load_experiment_080_condition_metadata,
20
+ project_assessment_target_status,
21
+ )
22
+ from multi_agent_brief.feedback.feedback_contract import current_stage_feedback_blocking_reasons
23
+ from multi_agent_brief.quality_gates.contract import current_stage_quality_gate_blocking_reasons
24
+ from multi_agent_brief.orchestrator_contract import resolve_repo_workdir
25
+ from multi_agent_brief.orchestrator.runtime_state._io import (
26
+ _read_json,
27
+ _read_json_if_exists,
28
+ _restore_state_files,
29
+ _sha256_file,
30
+ _snapshot_state_files,
31
+ _write_json_atomic,
32
+ )
33
+ from multi_agent_brief.orchestrator.runtime_state._transactions import (
34
+ _archive_finalized_state_if_needed,
35
+ _load_manifest_and_workflow,
36
+ _persist_run_contamination,
37
+ _preflight_transaction_files,
38
+ _restore_file_paths,
39
+ _snapshot_file_paths,
40
+ )
41
+ from multi_agent_brief.orchestrator.runtime_state.artifact_registry import (
42
+ _artifact_registry_path,
43
+ _artifact_registry_sha,
44
+ _build_artifact_registry,
45
+ _changed_artifact_events,
46
+ interpret_frozen_artifact_integrity,
47
+ require_frozen_artifact_integrity_pass,
48
+ )
49
+ from multi_agent_brief.orchestrator.runtime_state.claim_ledger_freeze import _claim_ledger_freeze_reasons
50
+ from multi_agent_brief.orchestrator.runtime_state.completion_gates import (
51
+ _completion_artifact_gate_reasons,
52
+ _fast_rerun_finalize_freshness_snapshot,
53
+ _finalize_completion_reasons,
54
+ _quality_gate_pass_reasons,
55
+ _raise_completion_reasons,
56
+ _role_topology_from_policy_pack,
57
+ _source_discovery_evidence_reasons,
58
+ _topology_satisfaction_artifact_reasons,
59
+ _topology_satisfaction_rules,
60
+ )
61
+ from multi_agent_brief.orchestrator.runtime_state.contracts_loader import (
62
+ _artifact_map,
63
+ _stage_ids,
64
+ load_artifact_contracts,
65
+ load_default_policy_pack,
66
+ load_stage_specs,
67
+ )
68
+ from multi_agent_brief.orchestrator.runtime_state.errors import (
69
+ E_ARTIFACT_INVALID,
70
+ E_ASSESSMENT_TARGET_COMPLETE,
71
+ E_COMPLETION_TRANSACTION_REQUIRED,
72
+ E_ILLEGAL_TRANSITION,
73
+ E_QUALITY_GATE_REQUIRED,
74
+ E_READER_FINAL_GATE_FAILED,
75
+ E_REQUIRED_ARTIFACT_MISSING,
76
+ E_STAGE_ALREADY_COMPLETED,
77
+ E_STAGE_MISMATCH,
78
+ E_TRANSACTION_INTEGRITY,
79
+ E_TRANSACTION_PARTIAL_WRITE,
80
+ RuntimeStateError,
81
+ _wrap_archive_error,
82
+ )
83
+ from multi_agent_brief.orchestrator.runtime_state.event_log import (
84
+ append_event,
85
+ read_event_log_records_strict,
86
+ )
87
+ from multi_agent_brief.orchestrator.runtime_state.identity import utc_now
88
+ from multi_agent_brief.orchestrator.runtime_state.lifecycle import show_runtime_state
89
+ from multi_agent_brief.orchestrator.runtime_state.manifest import (
90
+ _assert_manifest_extensions_preserved,
91
+ _preserved_manifest_extensions,
92
+ )
93
+ from multi_agent_brief.orchestrator.runtime_state.paths import (
94
+ _require_workspace,
95
+ _workspace_relative,
96
+ runtime_state_paths,
97
+ )
98
+ from multi_agent_brief.orchestrator.runtime_state.repair import (
99
+ _artifact_allowed,
100
+ raise_if_active_repair_open,
101
+ )
102
+ from multi_agent_brief.orchestrator.runtime_state.trajectory import (
103
+ _raise_if_trajectory_narrows_success_path,
104
+ _workflow_with_trajectory_decision_narrowing,
105
+ )
106
+ from multi_agent_brief.orchestrator.runtime_state.workflow import (
107
+ STAGE_BLOCKED,
108
+ STAGE_COMPLETE,
109
+ STAGE_PENDING,
110
+ STAGE_READY,
111
+ STAGE_SKIPPED,
112
+ _allowed_decisions_for_stage,
113
+ _next_stage_id,
114
+ _stage_status,
115
+ _status_entry,
116
+ _workflow_after_completion,
117
+ )
118
+ from multi_agent_brief.orchestrator.run_archive import (
119
+ RunArchiveError,
120
+ preflight_finalized_run_archive,
121
+ )
122
+ from multi_agent_brief.orchestrator.run_integrity import finalize_run_integrity as _finalize_run_integrity
123
+
124
+
125
+ ANALYST_DRAFT_SNAPSHOT_PATH = Path("output/intermediate/analyst_draft_snapshot.md")
126
+
127
+
128
+ def _manifest_with_fast_rerun_freshness_at_finalize(
129
+ manifest: dict[str, Any],
130
+ freshness_at_finalize: dict[str, Any] | None,
131
+ ) -> dict[str, Any]:
132
+ record = (
133
+ manifest.get("fact_layer_import")
134
+ if isinstance(manifest.get("fact_layer_import"), dict)
135
+ else None
136
+ )
137
+ if not record or not freshness_at_finalize:
138
+ return manifest
139
+ next_manifest = dict(manifest)
140
+ next_record = dict(record)
141
+ next_record["freshness_at_finalize"] = freshness_at_finalize
142
+ next_manifest["fact_layer_import"] = next_record
143
+ return next_manifest
144
+
145
+
146
+ def _older_stage_replay_message(
147
+ *,
148
+ stage_id: str,
149
+ current_stage: str | None,
150
+ stages: list[dict[str, Any]],
151
+ workflow: dict[str, Any],
152
+ ) -> str:
153
+ if current_stage is None or stage_id == current_stage:
154
+ return ""
155
+ stage_ids = _stage_ids(stages)
156
+ if stage_id not in stage_ids or current_stage not in stage_ids:
157
+ return ""
158
+ if stage_ids.index(stage_id) >= stage_ids.index(current_stage):
159
+ return ""
160
+ statuses = (
161
+ workflow.get("stage_statuses")
162
+ if isinstance(workflow.get("stage_statuses"), dict)
163
+ else {}
164
+ )
165
+ downstream_ids = stage_ids[stage_ids.index(stage_id) + 1 :]
166
+ downstream_touched = [
167
+ item
168
+ for item in downstream_ids
169
+ if ((statuses.get(item) or {}).get("status") or "")
170
+ in {STAGE_COMPLETE, STAGE_READY, STAGE_BLOCKED, STAGE_SKIPPED}
171
+ ]
172
+ if not downstream_touched:
173
+ return ""
174
+ return (
175
+ f"Stage-complete was attempted for older stage '{stage_id}' after downstream "
176
+ f"stage '{downstream_touched[0]}' already existed."
177
+ )
178
+
179
+
180
+ def _validate_completion_target(
181
+ *,
182
+ stage_id: str,
183
+ workflow: dict[str, Any],
184
+ stage_by_id: dict[str, dict[str, Any]],
185
+ finalize: bool,
186
+ ) -> dict[str, Any]:
187
+ if stage_id not in stage_by_id:
188
+ raise RuntimeStateError(
189
+ f"Unknown stage: {stage_id}",
190
+ details={"stage_id": stage_id, "known_stages": list(stage_by_id)},
191
+ error_code=E_ILLEGAL_TRANSITION,
192
+ )
193
+ current_stage = workflow.get("current_stage")
194
+ if current_stage is None and _stage_status(workflow, stage_id) == STAGE_COMPLETE:
195
+ raise RuntimeStateError(
196
+ f"Stage '{stage_id}' is already complete.",
197
+ details={"stage_id": stage_id},
198
+ error_code=E_STAGE_ALREADY_COMPLETED,
199
+ )
200
+ if stage_id != current_stage:
201
+ if _stage_status(workflow, stage_id) == STAGE_COMPLETE:
202
+ raise RuntimeStateError(
203
+ f"Stage '{stage_id}' is already complete.",
204
+ details={"stage_id": stage_id, "current_stage": current_stage},
205
+ error_code=E_STAGE_ALREADY_COMPLETED,
206
+ )
207
+ raise RuntimeStateError(
208
+ f"Completion stage '{stage_id}' does not match current stage '{current_stage}'.",
209
+ details={"stage_id": stage_id, "current_stage": current_stage},
210
+ error_code=E_STAGE_MISMATCH,
211
+ )
212
+ if finalize and stage_id != "finalize":
213
+ raise RuntimeStateError(
214
+ "finalize-complete can only complete the finalize stage.",
215
+ details={"stage_id": stage_id},
216
+ error_code=E_ILLEGAL_TRANSITION,
217
+ )
218
+ if not finalize and stage_id == "finalize":
219
+ raise RuntimeStateError(
220
+ "stage-complete cannot complete the finalize stage; use finalize-complete.",
221
+ details={"stage_id": stage_id},
222
+ error_code=E_ILLEGAL_TRANSITION,
223
+ )
224
+ stage = stage_by_id[stage_id]
225
+ decision = "finalize" if finalize else "continue"
226
+ allowed = [str(item) for item in (stage.get("allowed_decisions") or [])]
227
+ if decision not in allowed:
228
+ raise RuntimeStateError(
229
+ f"Decision '{decision}' is not allowed for stage '{stage_id}'.",
230
+ details={
231
+ "stage_id": stage_id,
232
+ "decision": decision,
233
+ "stage_allowed_decisions": allowed,
234
+ },
235
+ error_code=E_ILLEGAL_TRANSITION,
236
+ )
237
+ return stage
238
+
239
+
240
+ def _auditor_completion_metadata(
241
+ *,
242
+ workspace: Path,
243
+ registry: dict[str, Any],
244
+ event_records: list[dict[str, Any]],
245
+ transaction_id: str,
246
+ ) -> dict[str, Any]:
247
+ ledger_sha = _artifact_registry_sha(registry, "claim_ledger")
248
+ audited_brief_sha = _artifact_registry_sha(registry, "audited_brief")
249
+ audit_sha = _artifact_registry_sha(registry, "audit_report")
250
+ ledger_path = workspace / _artifact_registry_path(
251
+ registry,
252
+ "claim_ledger",
253
+ "output/intermediate/claim_ledger.json",
254
+ )
255
+ audited_brief_path = workspace / _artifact_registry_path(
256
+ registry,
257
+ "audited_brief",
258
+ "output/intermediate/audited_brief.md",
259
+ )
260
+ audit_path = workspace / _artifact_registry_path(
261
+ registry,
262
+ "audit_report",
263
+ "output/intermediate/audit_report.json",
264
+ )
265
+ if _sha256_file(ledger_path) != ledger_sha:
266
+ raise RuntimeStateError(
267
+ "Claim Ledger changed before auditor completion could bind it.",
268
+ details={
269
+ "artifact_id": "claim_ledger",
270
+ "path": str(ledger_path),
271
+ "registry_sha256": ledger_sha,
272
+ },
273
+ error_code=E_TRANSACTION_INTEGRITY,
274
+ )
275
+ if _sha256_file(audited_brief_path) != audited_brief_sha:
276
+ raise RuntimeStateError(
277
+ "Audited brief changed before auditor completion could bind it.",
278
+ details={
279
+ "artifact_id": "audited_brief",
280
+ "path": str(audited_brief_path),
281
+ "registry_sha256": audited_brief_sha,
282
+ },
283
+ error_code=E_TRANSACTION_INTEGRITY,
284
+ )
285
+ if _sha256_file(audit_path) != audit_sha:
286
+ raise RuntimeStateError(
287
+ "Audit report changed before auditor completion could bind it.",
288
+ details={
289
+ "artifact_id": "audit_report",
290
+ "path": str(audit_path),
291
+ "registry_sha256": audit_sha,
292
+ },
293
+ error_code=E_TRANSACTION_INTEGRITY,
294
+ )
295
+ relevant_repair_transaction_ids = _repair_transaction_ids_for_artifact(
296
+ event_records,
297
+ artifact_path="output/intermediate/audited_brief.md",
298
+ )
299
+ audit_binding = {
300
+ "schema_version": AUDIT_BINDING_SCHEMA,
301
+ "source": "auditor_stage_complete",
302
+ "claim_ledger_sha256": ledger_sha,
303
+ "audited_brief_sha256": audited_brief_sha,
304
+ "audit_report_sha256": audit_sha,
305
+ "relevant_repair_transaction_ids": relevant_repair_transaction_ids,
306
+ "auditor_stage_transaction_id": transaction_id,
307
+ "stage_completion_event": {
308
+ "event_type": "decision_recorded",
309
+ "transaction_id": transaction_id,
310
+ "event_id": None,
311
+ "sequence": None,
312
+ "availability": "not_available_until_event_append",
313
+ },
314
+ }
315
+ return {
316
+ "upstream_artifact_sha256": {
317
+ "claim_ledger": ledger_sha,
318
+ "audited_brief": audited_brief_sha,
319
+ },
320
+ "produced_artifact_sha256": {
321
+ "audit_report": audit_sha,
322
+ },
323
+ "audit_binding": audit_binding,
324
+ }
325
+
326
+
327
+ def _repair_transaction_ids_for_artifact(
328
+ event_records: list[dict[str, Any]],
329
+ *,
330
+ artifact_path: str,
331
+ ) -> list[str]:
332
+ ids: list[str] = []
333
+ for event in event_records:
334
+ if event.get("event_type") != "repair_completed":
335
+ continue
336
+ metadata = (
337
+ event.get("metadata") if isinstance(event.get("metadata"), dict) else {}
338
+ )
339
+ allowed = [str(item) for item in metadata.get("allowed_artifacts") or []]
340
+ if not _artifact_allowed(artifact_path, allowed):
341
+ continue
342
+ transaction_id = metadata.get("transaction_id") or metadata.get(
343
+ "repair_transaction_id"
344
+ )
345
+ if (
346
+ isinstance(transaction_id, str)
347
+ and transaction_id
348
+ and transaction_id not in ids
349
+ ):
350
+ ids.append(transaction_id)
351
+ return ids
352
+
353
+
354
+ def _stage_runtime_provenance(
355
+ *,
356
+ runtime: str | None,
357
+ model: str | None,
358
+ actor: str,
359
+ ) -> dict[str, Any] | None:
360
+ data: dict[str, Any] = {
361
+ "schema_version": "mabw.stage_runtime_provenance.v1",
362
+ "source": "stage_completion_args",
363
+ "recorded_by_actor": actor,
364
+ "provenance_only": True,
365
+ "quality_claim": False,
366
+ }
367
+ if runtime is not None and str(runtime).strip():
368
+ data["runtime"] = str(runtime).strip()
369
+ if model is not None and str(model).strip():
370
+ data["model"] = str(model).strip()
371
+ return data if "runtime" in data or "model" in data else None
372
+
373
+
374
+ def _topology_satisfier_aliases(*, stage_id: str, topology: str) -> set[str]:
375
+ aliases = {stage_id}
376
+ if topology == "human_assisted" and stage_id in {"analyst", "editor", "writer"}:
377
+ aliases.add("writer")
378
+ return aliases
379
+
380
+
381
+ def _topology_satisfaction_targets_for_completion(
382
+ *,
383
+ stages: list[dict[str, Any]],
384
+ policy_pack: dict[str, Any],
385
+ stage_id: str,
386
+ ) -> list[tuple[str, dict[str, Any]]]:
387
+ try:
388
+ topology = _role_topology_from_policy_pack(policy_pack)
389
+ rules = _topology_satisfaction_rules(stages=stages, policy_pack=policy_pack)
390
+ except ValueError as exc:
391
+ raise RuntimeStateError(
392
+ "policy.role_topology is invalid for stage satisfaction.",
393
+ details={"reason": str(exc)},
394
+ error_code=E_TRANSACTION_INTEGRITY,
395
+ ) from exc
396
+
397
+ satisfiers = _topology_satisfier_aliases(stage_id=stage_id, topology=topology)
398
+ targets: list[tuple[str, dict[str, Any]]] = []
399
+ current = _next_stage_id(stages, stage_id)
400
+ while current:
401
+ rule = rules.get(current)
402
+ if not rule:
403
+ break
404
+ if str(rule.get("satisfied_by") or "") not in satisfiers:
405
+ break
406
+ targets.append((current, rule))
407
+ current = _next_stage_id(stages, current)
408
+ return targets
409
+
410
+
411
+ def _topology_satisfaction_required_reasons(
412
+ *,
413
+ workspace: Path,
414
+ targets: list[tuple[str, dict[str, Any]]],
415
+ artifacts_by_id: dict[str, dict[str, Any]],
416
+ ) -> list[str]:
417
+ reasons: list[str] = []
418
+ for target_stage_id, rule in targets:
419
+ reasons.extend(
420
+ _topology_satisfaction_artifact_reasons(
421
+ workspace=workspace,
422
+ stage_id=target_stage_id,
423
+ rule=rule,
424
+ artifacts_by_id=artifacts_by_id,
425
+ )
426
+ )
427
+ return reasons
428
+
429
+
430
+ def _topology_satisfaction_target_blocking_reasons(
431
+ *,
432
+ workspace: Path,
433
+ targets: list[tuple[str, dict[str, Any]]],
434
+ stages: list[dict[str, Any]],
435
+ artifacts: list[dict[str, Any]],
436
+ ) -> list[str]:
437
+ reasons: list[str] = []
438
+ for target_stage_id, _rule in targets:
439
+ reasons.extend(
440
+ current_stage_feedback_blocking_reasons(
441
+ workspace=workspace,
442
+ current_stage=target_stage_id,
443
+ stages=stages,
444
+ artifacts=artifacts,
445
+ )
446
+ )
447
+ reasons.extend(
448
+ current_stage_quality_gate_blocking_reasons(
449
+ workspace=workspace,
450
+ current_stage=target_stage_id,
451
+ stages=stages,
452
+ artifacts=artifacts,
453
+ )
454
+ )
455
+ return reasons
456
+
457
+
458
+ def _workflow_with_topology_satisfaction(
459
+ *,
460
+ workflow: dict[str, Any],
461
+ stages: list[dict[str, Any]],
462
+ targets: list[tuple[str, dict[str, Any]]],
463
+ trigger_stage_id: str,
464
+ now: str,
465
+ transaction_id: str,
466
+ ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
467
+ if not targets:
468
+ return workflow, []
469
+
470
+ updated = dict(workflow)
471
+ statuses = dict(updated.get("stage_statuses") or {})
472
+ topology_events: list[dict[str, Any]] = []
473
+ current_stage = updated.get("current_stage")
474
+
475
+ for target_stage_id, rule in targets:
476
+ if current_stage != target_stage_id:
477
+ raise RuntimeStateError(
478
+ "Topology satisfaction target does not match the current workflow stage.",
479
+ details={
480
+ "target_stage_id": target_stage_id,
481
+ "current_stage": current_stage,
482
+ "trigger_stage_id": trigger_stage_id,
483
+ },
484
+ error_code=E_TRANSACTION_INTEGRITY,
485
+ )
486
+ topology = str(rule.get("topology") or "")
487
+ satisfied_by = str(rule.get("satisfied_by") or "")
488
+ required_artifacts = [
489
+ str(item) for item in (rule.get("required_artifacts") or []) if item
490
+ ]
491
+ reason = f"Stage satisfied by {satisfied_by} under {topology} role topology."
492
+ metadata = {
493
+ "satisfied_by_topology": True,
494
+ "topology": topology,
495
+ "satisfied_by": satisfied_by,
496
+ "satisfied_by_stage": trigger_stage_id,
497
+ "required_artifacts": required_artifacts,
498
+ "transaction_id": transaction_id,
499
+ }
500
+ statuses[target_stage_id] = _status_entry(
501
+ STAGE_COMPLETE,
502
+ reason,
503
+ now,
504
+ metadata=metadata,
505
+ )
506
+ topology_events.append(
507
+ {
508
+ "event_type": "stage_satisfied_by_topology",
509
+ "stage_id": target_stage_id,
510
+ "reason": reason,
511
+ "metadata": metadata,
512
+ }
513
+ )
514
+ current_stage = _next_stage_id(stages, target_stage_id)
515
+ if current_stage:
516
+ statuses[current_stage] = _status_entry(STAGE_READY, "", now)
517
+
518
+ updated["current_stage"] = current_stage
519
+ updated["blocked"] = False
520
+ updated["blocking_reason"] = ""
521
+ updated["stage_statuses"] = statuses
522
+ updated["next_allowed_decisions"] = _allowed_decisions_for_stage(
523
+ stages, current_stage
524
+ )
525
+ return updated, topology_events
526
+
527
+
528
+ def _append_transaction_events(
529
+ *,
530
+ workspace: Path,
531
+ run_id: str,
532
+ actor: str,
533
+ transaction_id: str,
534
+ stage_id: str,
535
+ decision: str,
536
+ reason: str,
537
+ next_stage: str | None,
538
+ artifact_events: list[dict[str, Any]],
539
+ topology_events: list[dict[str, Any]] | None = None,
540
+ runtime_provenance: dict[str, Any] | None = None,
541
+ ) -> None:
542
+ try:
543
+ for event in [*artifact_events, *(topology_events or [])]:
544
+ metadata = dict(event.get("metadata") or {})
545
+ metadata["transaction_id"] = transaction_id
546
+ append_event(
547
+ workspace=workspace,
548
+ run_id=run_id,
549
+ event_type=str(event["event_type"]),
550
+ actor=actor,
551
+ stage_id=event.get("stage_id"),
552
+ artifact_id=event.get("artifact_id"),
553
+ reason=str(event.get("reason") or ""),
554
+ metadata=metadata,
555
+ )
556
+ decision_metadata = {"next_stage": next_stage, "transaction_id": transaction_id}
557
+ if runtime_provenance:
558
+ decision_metadata["runtime_provenance"] = runtime_provenance
559
+ append_event(
560
+ workspace=workspace,
561
+ run_id=run_id,
562
+ event_type="decision_recorded",
563
+ actor=actor,
564
+ stage_id=stage_id,
565
+ decision=decision,
566
+ reason=reason,
567
+ metadata=decision_metadata,
568
+ )
569
+ except RuntimeStateError as exc:
570
+ raise RuntimeStateError(
571
+ "Completion transaction partially wrote state but failed to append event.",
572
+ details={
573
+ "transaction_id": transaction_id,
574
+ "stage_id": stage_id,
575
+ "decision": decision,
576
+ "event_error": str(exc),
577
+ "event_details": exc.details,
578
+ },
579
+ error_code=E_TRANSACTION_PARTIAL_WRITE,
580
+ ) from exc
581
+
582
+
583
+ def raise_if_auditable_target_complete_blocks_downstream(
584
+ *,
585
+ workspace: Path,
586
+ workflow: dict[str, Any],
587
+ command: str,
588
+ ) -> None:
589
+ condition_metadata = load_experiment_080_condition_metadata(workspace)
590
+ if (
591
+ not isinstance(condition_metadata, dict)
592
+ or condition_metadata.get("assessment_target") != "auditable_brief"
593
+ ):
594
+ return
595
+ paths = runtime_state_paths(workspace)
596
+ registry = _read_json_if_exists(paths["artifact_registry"])
597
+ auditor_gate = _read_json_if_exists(
598
+ workspace
599
+ / "output"
600
+ / "intermediate"
601
+ / "gates"
602
+ / "auditor_quality_gate_report.json"
603
+ )
604
+ event_records = (
605
+ read_event_log_records_strict(paths["event_log"])
606
+ if paths["event_log"].exists()
607
+ else []
608
+ )
609
+ projection = project_assessment_target_status(
610
+ condition_metadata=condition_metadata,
611
+ workflow_state=workflow,
612
+ artifact_registry=registry,
613
+ auditor_gate_report=auditor_gate,
614
+ event_records=event_records,
615
+ )
616
+ workspace_arg = shlex.quote(str(workspace))
617
+ target_complete = projection.get("target_complete") is True
618
+ if target_complete:
619
+ message = (
620
+ "TARGET COMPLETE: auditable_brief. This 080 workspace has reached the auditable-brief "
621
+ "assessment target; finalize/delivery actions are outside this target."
622
+ )
623
+ next_allowed_commands = [
624
+ (
625
+ "multi-agent-brief experiments 080 register-run --case <case_dir> "
626
+ f"--condition {projection.get('condition') or '<condition>'} "
627
+ f"--workspace {workspace_arg} --output <run_record.json>"
628
+ ),
629
+ (
630
+ "multi-agent-brief experiments 080 score-run --case <case_dir> "
631
+ "--run-record <run_record.json> --output <scorecard.json>"
632
+ ),
633
+ ]
634
+ else:
635
+ message = (
636
+ "TARGET INCOMPLETE: auditable_brief. This 080 workspace uses the auditable-brief "
637
+ "assessment target; finalize/delivery actions are blocked until target controls pass."
638
+ )
639
+ next_allowed_commands = [
640
+ f"multi-agent-brief status --workspace {workspace_arg} --json",
641
+ f"multi-agent-brief state check --workspace {workspace_arg} --strict --json",
642
+ f"multi-agent-brief repair route --workspace {workspace_arg} --json",
643
+ ]
644
+ raise RuntimeStateError(
645
+ message,
646
+ details={
647
+ "assessment_target": "auditable_brief",
648
+ "command": command,
649
+ "target_complete": target_complete,
650
+ "reasons": projection.get("reasons") or [],
651
+ "next_allowed_commands": next_allowed_commands,
652
+ "forbidden_downstream_actions": [
653
+ "multi-agent-brief finalize",
654
+ "multi-agent-brief state finalize-complete",
655
+ "multi-agent-brief deliver",
656
+ ],
657
+ "projection": projection,
658
+ },
659
+ error_code=E_ASSESSMENT_TARGET_COMPLETE,
660
+ )
661
+
662
+
663
+ def _auditable_target_auditor_gate_pass_reasons(
664
+ *,
665
+ workspace: Path,
666
+ stage_id: str,
667
+ ) -> list[str]:
668
+ if stage_id != "auditor":
669
+ return []
670
+ condition_metadata = load_experiment_080_condition_metadata(workspace)
671
+ if (
672
+ not isinstance(condition_metadata, dict)
673
+ or condition_metadata.get("assessment_target") != "auditable_brief"
674
+ ):
675
+ return []
676
+ gate_path = (
677
+ workspace
678
+ / "output"
679
+ / "intermediate"
680
+ / "gates"
681
+ / "auditor_quality_gate_report.json"
682
+ )
683
+ payload = _read_json_if_exists(gate_path)
684
+ if payload is None:
685
+ return [
686
+ "080 auditable_brief target requires output/intermediate/gates/auditor_quality_gate_report.json before auditor stage-complete."
687
+ ]
688
+ status = str(payload.get("status") or "")
689
+ if (
690
+ status != "pass"
691
+ and not auditable_gate_has_only_final_abstract_advisory_warnings(payload)
692
+ ):
693
+ return [
694
+ "080 auditable_brief target requires auditor quality gate report status pass before auditor stage-complete; "
695
+ f"got {status or '<missing>'}. Repair warnings before completing auditor."
696
+ ]
697
+ return []
698
+
699
+
700
+ def _stale_expected_artifact_refresh_reasons(
701
+ *,
702
+ workspace: Path,
703
+ workflow: dict[str, Any],
704
+ stage: dict[str, Any],
705
+ artifacts_by_id: dict[str, dict[str, Any]],
706
+ old_registry: dict[str, Any],
707
+ ) -> list[str]:
708
+ if not isinstance(old_registry, dict):
709
+ return []
710
+ registry_artifacts = (
711
+ old_registry.get("artifacts")
712
+ if isinstance(old_registry.get("artifacts"), dict)
713
+ else {}
714
+ )
715
+ reasons: list[str] = []
716
+ for artifact_id in [str(item) for item in (stage.get("expected_artifacts") or [])]:
717
+ record = registry_artifacts.get(artifact_id)
718
+ if not isinstance(record, dict):
719
+ continue
720
+ if (
721
+ record.get("status") != "stale"
722
+ and record.get("validation_result") != "stale_after_repair"
723
+ ):
724
+ continue
725
+ contract = artifacts_by_id.get(artifact_id)
726
+ if not isinstance(contract, dict):
727
+ continue
728
+ rel_path = str(contract.get("path") or record.get("path") or "")
729
+ if not rel_path:
730
+ continue
731
+ path = workspace / rel_path
732
+ if not path.is_file():
733
+ continue
734
+ stale_sha = _stale_artifact_baseline_sha(
735
+ workflow=workflow,
736
+ stage_id=str(stage.get("stage_id") or ""),
737
+ artifact_id=artifact_id,
738
+ record=record,
739
+ )
740
+ current_sha = _sha256_file(path)
741
+ if isinstance(stale_sha, str) and stale_sha == current_sha:
742
+ reasons.append(
743
+ f"Expected artifact '{artifact_id}' at '{rel_path}' is stale after repair "
744
+ "and still has the stale hash; rerun the producer stage and refresh the artifact before stage-complete."
745
+ )
746
+ return reasons
747
+
748
+
749
+ def _stale_artifact_baseline_sha(
750
+ *,
751
+ workflow: dict[str, Any],
752
+ stage_id: str,
753
+ artifact_id: str,
754
+ record: dict[str, Any],
755
+ ) -> str | None:
756
+ statuses = (
757
+ workflow.get("stage_statuses")
758
+ if isinstance(workflow.get("stage_statuses"), dict)
759
+ else {}
760
+ )
761
+ stage_status = (
762
+ statuses.get(stage_id) if isinstance(statuses.get(stage_id), dict) else {}
763
+ )
764
+ metadata = (
765
+ stage_status.get("metadata")
766
+ if isinstance(stage_status.get("metadata"), dict)
767
+ else {}
768
+ )
769
+ baselines = (
770
+ metadata.get("stale_artifact_baselines")
771
+ if isinstance(metadata.get("stale_artifact_baselines"), dict)
772
+ else {}
773
+ )
774
+ baseline = (
775
+ baselines.get(artifact_id)
776
+ if isinstance(baselines.get(artifact_id), dict)
777
+ else {}
778
+ )
779
+ baseline_sha = baseline.get("sha256")
780
+ if isinstance(baseline_sha, str) and baseline_sha:
781
+ return baseline_sha
782
+ record_baseline_sha = record.get("stale_baseline_sha256")
783
+ if isinstance(record_baseline_sha, str) and record_baseline_sha:
784
+ return record_baseline_sha
785
+ return None
786
+
787
+
788
+ def _complete_stage_transaction(
789
+ *,
790
+ workspace: str | Path,
791
+ stage_id: str,
792
+ reason: str,
793
+ repo_workdir: str | Path | None = None,
794
+ actor: str = "orchestrator",
795
+ finalize: bool = False,
796
+ stage_runtime: str | None = None,
797
+ stage_model: str | None = None,
798
+ ) -> dict[str, Any]:
799
+ ws = _require_workspace(workspace)
800
+ paths = runtime_state_paths(ws)
801
+ event_records = _preflight_transaction_files(paths)
802
+ ws, paths, manifest, workflow = _load_manifest_and_workflow(ws)
803
+ raise_if_active_repair_open(workspace=ws, workflow=workflow)
804
+ if finalize:
805
+ raise_if_auditable_target_complete_blocks_downstream(
806
+ workspace=ws,
807
+ workflow=workflow,
808
+ command="state finalize-complete",
809
+ )
810
+ repo = resolve_repo_workdir(repo_workdir, workspace=ws)
811
+ stages = load_stage_specs(repo)
812
+ artifacts = load_artifact_contracts(repo)
813
+ policy_pack = load_default_policy_pack(repo)
814
+ artifacts_by_id = _artifact_map(artifacts)
815
+ stage_by_id = {str(stage.get("stage_id")): stage for stage in stages}
816
+ stage = _validate_completion_target(
817
+ stage_id=stage_id,
818
+ workflow=workflow,
819
+ stage_by_id=stage_by_id,
820
+ finalize=finalize,
821
+ )
822
+ run_id = str(manifest["run_id"])
823
+ decision = "finalize" if finalize else "continue"
824
+ _raise_if_trajectory_narrows_success_path(
825
+ workspace=ws,
826
+ workflow=workflow,
827
+ event_records=event_records,
828
+ run_id=run_id,
829
+ stage_id=stage_id,
830
+ decision=decision,
831
+ )
832
+ replay_message = _older_stage_replay_message(
833
+ stage_id=stage_id,
834
+ current_stage=workflow.get("current_stage"),
835
+ stages=stages,
836
+ workflow=workflow,
837
+ )
838
+ if replay_message:
839
+ workflow = _persist_run_contamination(
840
+ workspace=ws,
841
+ paths=paths,
842
+ run_id=str(manifest["run_id"]),
843
+ workflow=workflow,
844
+ reason_code="older_stage_replay",
845
+ message=replay_message,
846
+ actor=actor,
847
+ stage_id=stage_id,
848
+ )
849
+
850
+ transaction_id = uuid.uuid4().hex
851
+ now = utc_now()
852
+ runtime_provenance = _stage_runtime_provenance(
853
+ runtime=stage_runtime,
854
+ model=stage_model,
855
+ actor=actor,
856
+ )
857
+ analyst_snapshot_before: dict[Path, bytes | None] | None = None
858
+ if stage_id == "analyst":
859
+ analyst_snapshot_before = _snapshot_file_paths(
860
+ [ws / ANALYST_DRAFT_SNAPSHOT_PATH]
861
+ )
862
+ _snapshot_analyst_draft(ws)
863
+ try:
864
+ artifact_reasons = _completion_artifact_gate_reasons(
865
+ workspace=ws,
866
+ stage=stage,
867
+ artifacts_by_id=artifacts_by_id,
868
+ )
869
+ old_registry_for_stale_check = _read_json_if_exists(paths["artifact_registry"])
870
+ stale_artifact_reasons = _stale_expected_artifact_refresh_reasons(
871
+ workspace=ws,
872
+ workflow=workflow,
873
+ stage=stage,
874
+ artifacts_by_id=artifacts_by_id,
875
+ old_registry=old_registry_for_stale_check,
876
+ )
877
+ if stale_artifact_reasons:
878
+ _raise_completion_reasons(
879
+ message=f"Cannot complete stage '{stage_id}' because stale downstream artifacts were not refreshed",
880
+ reasons=stale_artifact_reasons,
881
+ error_code=E_TRANSACTION_INTEGRITY,
882
+ details={"stage_id": stage_id},
883
+ )
884
+ topology_targets = _topology_satisfaction_targets_for_completion(
885
+ stages=stages,
886
+ policy_pack=policy_pack,
887
+ stage_id=stage_id,
888
+ )
889
+ artifact_reasons.extend(
890
+ _topology_satisfaction_required_reasons(
891
+ workspace=ws,
892
+ targets=topology_targets,
893
+ artifacts_by_id=artifacts_by_id,
894
+ )
895
+ )
896
+ if stage_id == "source-discovery":
897
+ artifact_reasons.extend(_source_discovery_evidence_reasons(ws))
898
+ if artifact_reasons:
899
+ code = E_REQUIRED_ARTIFACT_MISSING
900
+ if any("invalid" in item.lower() for item in artifact_reasons):
901
+ code = E_ARTIFACT_INVALID
902
+ _raise_completion_reasons(
903
+ message=f"Cannot complete stage '{stage_id}'",
904
+ reasons=artifact_reasons,
905
+ error_code=code,
906
+ details={"stage_id": stage_id},
907
+ )
908
+ if stage_id == "claim-ledger":
909
+ freeze_reasons = _claim_ledger_freeze_reasons(
910
+ workspace=ws, manifest=manifest
911
+ )
912
+ if freeze_reasons:
913
+ _raise_completion_reasons(
914
+ message="Cannot complete stage 'claim-ledger' before Claim Ledger freeze",
915
+ reasons=freeze_reasons,
916
+ error_code=E_COMPLETION_TRANSACTION_REQUIRED,
917
+ details={"stage_id": stage_id},
918
+ )
919
+
920
+ topology_target_reasons = _topology_satisfaction_target_blocking_reasons(
921
+ workspace=ws,
922
+ targets=topology_targets,
923
+ stages=stages,
924
+ artifacts=artifacts,
925
+ )
926
+ if topology_target_reasons:
927
+ _raise_completion_reasons(
928
+ message=f"Cannot complete stage '{stage_id}' because a topology-satisfied downstream stage is blocked",
929
+ reasons=topology_target_reasons,
930
+ error_code=E_ILLEGAL_TRANSITION,
931
+ details={
932
+ "stage_id": stage_id,
933
+ "topology_target_stages": [
934
+ target for target, _rule in topology_targets
935
+ ],
936
+ },
937
+ )
938
+
939
+ feedback_reasons = current_stage_feedback_blocking_reasons(
940
+ workspace=ws,
941
+ current_stage=stage_id,
942
+ stages=stages,
943
+ artifacts=artifacts,
944
+ )
945
+ if feedback_reasons:
946
+ _raise_completion_reasons(
947
+ message=f"Cannot complete stage '{stage_id}'",
948
+ reasons=feedback_reasons,
949
+ error_code=E_ILLEGAL_TRANSITION,
950
+ details={"stage_id": stage_id},
951
+ )
952
+
953
+ quality_reasons = current_stage_quality_gate_blocking_reasons(
954
+ workspace=ws,
955
+ current_stage=stage_id,
956
+ stages=stages,
957
+ artifacts=artifacts,
958
+ )
959
+ if stage_id == "auditor":
960
+ quality_reasons.extend(
961
+ _quality_gate_pass_reasons(
962
+ workspace=ws, stages=stages, artifacts=artifacts
963
+ )
964
+ )
965
+ quality_reasons.extend(
966
+ _auditable_target_auditor_gate_pass_reasons(
967
+ workspace=ws,
968
+ stage_id=stage_id,
969
+ )
970
+ )
971
+ if quality_reasons:
972
+ _raise_completion_reasons(
973
+ message=f"Cannot complete stage '{stage_id}'",
974
+ reasons=quality_reasons,
975
+ error_code=E_QUALITY_GATE_REQUIRED,
976
+ details={"stage_id": stage_id},
977
+ )
978
+
979
+ fast_rerun_freshness_at_finalize: dict[str, Any] | None = None
980
+ manifest_for_completion = manifest
981
+ if finalize:
982
+ fast_rerun_freshness_at_finalize = _fast_rerun_finalize_freshness_snapshot(
983
+ ws,
984
+ manifest,
985
+ checked_at=utc_now(),
986
+ )
987
+ manifest_for_completion = _manifest_with_fast_rerun_freshness_at_finalize(
988
+ manifest,
989
+ fast_rerun_freshness_at_finalize,
990
+ )
991
+ finalize_reasons = _finalize_completion_reasons(
992
+ ws,
993
+ stages=stages,
994
+ artifacts=artifacts,
995
+ runtime_manifest=manifest_for_completion,
996
+ fast_rerun_freshness_at_finalize=fast_rerun_freshness_at_finalize,
997
+ )
998
+ if finalize_reasons:
999
+ _raise_completion_reasons(
1000
+ message="Cannot complete finalize stage",
1001
+ reasons=finalize_reasons,
1002
+ error_code=E_READER_FINAL_GATE_FAILED,
1003
+ details={"stage_id": stage_id},
1004
+ )
1005
+
1006
+ preserved_extensions = _preserved_manifest_extensions(manifest_for_completion)
1007
+ next_workflow = _workflow_after_completion(
1008
+ workflow=workflow,
1009
+ stages=stages,
1010
+ stage_id=stage_id,
1011
+ reason=reason,
1012
+ now=now,
1013
+ transaction_id=transaction_id,
1014
+ finalize=finalize,
1015
+ runtime_provenance=runtime_provenance,
1016
+ )
1017
+ if finalize:
1018
+ next_workflow = _finalize_run_integrity(next_workflow)
1019
+ next_workflow, topology_events = _workflow_with_topology_satisfaction(
1020
+ workflow=next_workflow,
1021
+ stages=stages,
1022
+ targets=topology_targets,
1023
+ trigger_stage_id=stage_id,
1024
+ now=now,
1025
+ transaction_id=transaction_id,
1026
+ )
1027
+ next_workflow = _workflow_with_trajectory_decision_narrowing(
1028
+ workspace=ws,
1029
+ workflow=next_workflow,
1030
+ stages=stages,
1031
+ event_records=event_records,
1032
+ run_id=run_id,
1033
+ )
1034
+ old_registry = _read_json_if_exists(paths["artifact_registry"])
1035
+ registry = _build_artifact_registry(
1036
+ workspace=ws,
1037
+ run_id=run_id,
1038
+ artifacts=artifacts,
1039
+ workflow=next_workflow,
1040
+ updated_at=now,
1041
+ )
1042
+ frozen_verdict = interpret_frozen_artifact_integrity(
1043
+ old_registry=old_registry,
1044
+ registry=registry,
1045
+ workflow=workflow,
1046
+ artifacts=artifacts,
1047
+ stages=stages,
1048
+ mutating_stage=stage_id,
1049
+ )
1050
+ frozen_reasons = require_frozen_artifact_integrity_pass(frozen_verdict)
1051
+ if frozen_reasons:
1052
+ if frozen_verdict.contaminates_run:
1053
+ workflow = _persist_run_contamination(
1054
+ workspace=ws,
1055
+ paths=paths,
1056
+ run_id=run_id,
1057
+ workflow=workflow,
1058
+ reason_code="frozen_artifact_changed",
1059
+ message=" ".join(frozen_reasons),
1060
+ actor=actor,
1061
+ stage_id=stage_id,
1062
+ metadata={"blocking_reasons": frozen_reasons},
1063
+ )
1064
+ _raise_completion_reasons(
1065
+ message=(
1066
+ "Completion transaction cannot proceed because a frozen upstream artifact changed"
1067
+ if frozen_verdict.contaminates_run
1068
+ else "Completion transaction cannot proceed because frozen artifact integrity could not be verified"
1069
+ ),
1070
+ reasons=frozen_reasons,
1071
+ error_code=E_TRANSACTION_INTEGRITY,
1072
+ details={"stage_id": stage_id},
1073
+ )
1074
+ if stage_id == "auditor":
1075
+ statuses = dict(next_workflow.get("stage_statuses") or {})
1076
+ auditor_status = dict(statuses.get("auditor") or {})
1077
+ auditor_metadata = dict(auditor_status.get("metadata") or {})
1078
+ auditor_metadata.update(
1079
+ _auditor_completion_metadata(
1080
+ workspace=ws,
1081
+ registry=registry,
1082
+ event_records=event_records,
1083
+ transaction_id=transaction_id,
1084
+ )
1085
+ )
1086
+ auditor_status["metadata"] = auditor_metadata
1087
+ statuses["auditor"] = auditor_status
1088
+ next_workflow["stage_statuses"] = statuses
1089
+ finalize_report: dict[str, Any] | None = None
1090
+ if finalize:
1091
+ finalize_report = _read_json(
1092
+ paths["runtime_manifest"].parent / "finalize_report.json"
1093
+ )
1094
+ try:
1095
+ preflight_finalized_run_archive(
1096
+ workspace=ws,
1097
+ run_id=run_id,
1098
+ manifest=manifest_for_completion,
1099
+ workflow=next_workflow,
1100
+ artifact_registry=registry,
1101
+ finalize_report=finalize_report,
1102
+ fast_rerun_freshness_at_finalize=fast_rerun_freshness_at_finalize,
1103
+ )
1104
+ except RunArchiveError as exc:
1105
+ raise _wrap_archive_error(exc) from exc
1106
+ artifact_events = _changed_artifact_events(
1107
+ old_registry=old_registry, registry=registry
1108
+ )
1109
+ except RuntimeStateError:
1110
+ if analyst_snapshot_before is not None:
1111
+ _restore_file_paths(
1112
+ analyst_snapshot_before,
1113
+ rollback_message="Stage completion rollback failed after Analyst snapshot write.",
1114
+ )
1115
+ raise
1116
+
1117
+ state_written = False
1118
+ state_snapshots = _snapshot_state_files(
1119
+ paths, ("runtime_manifest", "artifact_registry", "workflow_state")
1120
+ )
1121
+ try:
1122
+ if manifest_for_completion != manifest:
1123
+ _write_json_atomic(paths["runtime_manifest"], manifest_for_completion)
1124
+ state_written = True
1125
+ _write_json_atomic(paths["artifact_registry"], registry)
1126
+ state_written = True
1127
+ _write_json_atomic(paths["workflow_state"], next_workflow)
1128
+ except RuntimeStateError as exc:
1129
+ try:
1130
+ _restore_state_files(paths, state_snapshots)
1131
+ if analyst_snapshot_before is not None:
1132
+ _restore_file_paths(
1133
+ analyst_snapshot_before,
1134
+ rollback_message="Stage completion rollback failed after state write failure.",
1135
+ )
1136
+ except RuntimeStateError as rollback_exc:
1137
+ raise RuntimeStateError(
1138
+ "Completion transaction partially wrote files and failed rollback.",
1139
+ details={
1140
+ "transaction_id": transaction_id,
1141
+ "stage_id": stage_id,
1142
+ "state_error": str(exc),
1143
+ "state_details": exc.details,
1144
+ "rollback_error": str(rollback_exc),
1145
+ "rollback_details": rollback_exc.details,
1146
+ },
1147
+ error_code=E_TRANSACTION_PARTIAL_WRITE,
1148
+ ) from rollback_exc
1149
+ code = E_TRANSACTION_PARTIAL_WRITE if state_written else exc.error_code
1150
+ raise RuntimeStateError(
1151
+ "Completion transaction failed while writing state files; control files were restored.",
1152
+ details={
1153
+ "transaction_id": transaction_id,
1154
+ "stage_id": stage_id,
1155
+ "state_error": str(exc),
1156
+ "state_details": exc.details,
1157
+ "restored": True,
1158
+ },
1159
+ error_code=code,
1160
+ ) from exc
1161
+
1162
+ _append_transaction_events(
1163
+ workspace=ws,
1164
+ run_id=run_id,
1165
+ actor=actor,
1166
+ transaction_id=transaction_id,
1167
+ stage_id=stage_id,
1168
+ decision="finalize" if finalize else "continue",
1169
+ reason=reason,
1170
+ next_stage=next_workflow.get("current_stage"),
1171
+ artifact_events=artifact_events,
1172
+ topology_events=topology_events,
1173
+ runtime_provenance=runtime_provenance,
1174
+ )
1175
+
1176
+ current_manifest = _read_json(paths["runtime_manifest"])
1177
+ _assert_manifest_extensions_preserved(
1178
+ before=preserved_extensions, after=current_manifest
1179
+ )
1180
+ archive_result: dict[str, Any] | None = None
1181
+ if finalize:
1182
+ archive_result = _archive_finalized_state_if_needed(
1183
+ workspace=ws,
1184
+ manifest=current_manifest,
1185
+ workflow=next_workflow,
1186
+ artifact_registry=registry,
1187
+ finalize_report=finalize_report
1188
+ or _read_json(paths["runtime_manifest"].parent / "finalize_report.json"),
1189
+ fast_rerun_freshness_at_finalize=fast_rerun_freshness_at_finalize,
1190
+ )
1191
+ try:
1192
+ append_event(
1193
+ workspace=ws,
1194
+ run_id=run_id,
1195
+ event_type="run_archived",
1196
+ actor=actor,
1197
+ stage_id=stage_id,
1198
+ reason="Finalized run archived.",
1199
+ metadata={
1200
+ "archive_path": _workspace_relative(
1201
+ ws, Path(str(archive_result["archive_path"]))
1202
+ ),
1203
+ "archive_manifest": _workspace_relative(
1204
+ ws, Path(str(archive_result["archive_manifest"]))
1205
+ ),
1206
+ "archive_manifest_sha256": archive_result[
1207
+ "archive_manifest_sha256"
1208
+ ],
1209
+ "file_count": archive_result["file_count"],
1210
+ "event_log_includes_run_archived": False,
1211
+ "transaction_id": transaction_id,
1212
+ },
1213
+ )
1214
+ except RuntimeStateError as exc:
1215
+ raise RuntimeStateError(
1216
+ "Completion transaction archived the run but failed to append archive event.",
1217
+ details={
1218
+ "transaction_id": transaction_id,
1219
+ "stage_id": stage_id,
1220
+ "archive_path": archive_result.get("archive_path"),
1221
+ "event_error": str(exc),
1222
+ "event_details": exc.details,
1223
+ },
1224
+ error_code=E_TRANSACTION_PARTIAL_WRITE,
1225
+ ) from exc
1226
+ state = show_runtime_state(workspace=ws)
1227
+ state["transaction"] = {
1228
+ "transaction_id": transaction_id,
1229
+ "stage_id": stage_id,
1230
+ "decision": "finalize" if finalize else "continue",
1231
+ }
1232
+ if runtime_provenance:
1233
+ state["transaction"]["runtime_provenance"] = runtime_provenance
1234
+ if archive_result is not None:
1235
+ state["run_archive"] = archive_result
1236
+ return state
1237
+
1238
+
1239
+ def _snapshot_analyst_draft(workspace: Path) -> None:
1240
+ source = workspace / "output/intermediate/audited_brief.md"
1241
+ target = workspace / ANALYST_DRAFT_SNAPSHOT_PATH
1242
+ if not source.exists():
1243
+ raise RuntimeStateError(
1244
+ "Cannot snapshot Analyst draft because output/intermediate/audited_brief.md is missing. "
1245
+ "The Analyst role must write audited_brief.md as the working draft; "
1246
+ "state stage-complete --stage analyst is the only writer that freezes "
1247
+ "output/intermediate/analyst_draft_snapshot.md.",
1248
+ details={"path": _workspace_relative(workspace, source)},
1249
+ error_code=E_REQUIRED_ARTIFACT_MISSING,
1250
+ )
1251
+ try:
1252
+ data = source.read_bytes()
1253
+ except OSError as exc:
1254
+ raise RuntimeStateError(
1255
+ "Cannot read Analyst draft for snapshot.",
1256
+ details={
1257
+ "path": _workspace_relative(workspace, source),
1258
+ "reason": str(exc),
1259
+ },
1260
+ ) from exc
1261
+ target.parent.mkdir(parents=True, exist_ok=True)
1262
+ tmp = target.with_name(f".{target.name}.{uuid.uuid4().hex}.tmp")
1263
+ try:
1264
+ tmp.write_bytes(data)
1265
+ os.replace(tmp, target)
1266
+ except OSError as exc:
1267
+ try:
1268
+ tmp.unlink(missing_ok=True)
1269
+ except OSError:
1270
+ pass
1271
+ raise RuntimeStateError(
1272
+ "Cannot write Analyst draft snapshot.",
1273
+ details={
1274
+ "path": _workspace_relative(workspace, target),
1275
+ "reason": str(exc),
1276
+ },
1277
+ ) from exc
1278
+
1279
+
1280
+ def complete_stage_transaction(
1281
+ *,
1282
+ workspace: str | Path,
1283
+ stage_id: str,
1284
+ reason: str,
1285
+ repo_workdir: str | Path | None = None,
1286
+ actor: str = "orchestrator",
1287
+ runtime: str | None = None,
1288
+ model: str | None = None,
1289
+ ) -> dict[str, Any]:
1290
+ return _complete_stage_transaction(
1291
+ workspace=workspace,
1292
+ stage_id=stage_id,
1293
+ reason=reason,
1294
+ repo_workdir=repo_workdir,
1295
+ actor=actor,
1296
+ finalize=False,
1297
+ stage_runtime=runtime,
1298
+ stage_model=model,
1299
+ )
1300
+
1301
+
1302
+ def complete_finalize_transaction(
1303
+ *,
1304
+ workspace: str | Path,
1305
+ reason: str,
1306
+ repo_workdir: str | Path | None = None,
1307
+ actor: str = "orchestrator",
1308
+ runtime: str | None = None,
1309
+ model: str | None = None,
1310
+ ) -> dict[str, Any]:
1311
+ return _complete_stage_transaction(
1312
+ workspace=workspace,
1313
+ stage_id="finalize",
1314
+ reason=reason,
1315
+ repo_workdir=repo_workdir,
1316
+ actor=actor,
1317
+ finalize=True,
1318
+ stage_runtime=runtime,
1319
+ stage_model=model,
1320
+ )