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.
- briefloop-0.11.12.dist-info/METADATA +473 -0
- briefloop-0.11.12.dist-info/RECORD +424 -0
- briefloop-0.11.12.dist-info/WHEEL +5 -0
- briefloop-0.11.12.dist-info/entry_points.txt +3 -0
- briefloop-0.11.12.dist-info/licenses/LICENSE +22 -0
- briefloop-0.11.12.dist-info/top_level.txt +1 -0
- multi_agent_brief/__init__.py +36 -0
- multi_agent_brief/analysis_blocks/__init__.py +11 -0
- multi_agent_brief/analysis_blocks/builder.py +206 -0
- multi_agent_brief/analysis_blocks/renderer.py +237 -0
- multi_agent_brief/analysis_blocks/schemas.py +52 -0
- multi_agent_brief/analysis_modules/__init__.py +8 -0
- multi_agent_brief/analysis_modules/base.py +87 -0
- multi_agent_brief/analysis_modules/market_competitor/__init__.py +68 -0
- multi_agent_brief/analysis_modules/market_competitor/auditor.py +290 -0
- multi_agent_brief/analysis_modules/market_competitor/config.py +187 -0
- multi_agent_brief/analysis_modules/market_competitor/event_builder.py +226 -0
- multi_agent_brief/analysis_modules/market_competitor/renderer.py +199 -0
- multi_agent_brief/analysis_modules/market_competitor/schemas.py +518 -0
- multi_agent_brief/analysis_modules/policy_regulatory/__init__.py +7 -0
- multi_agent_brief/analysis_modules/policy_regulatory/audit.py +195 -0
- multi_agent_brief/analysis_modules/policy_regulatory/module.py +387 -0
- multi_agent_brief/analysis_modules/policy_regulatory/schemas.py +129 -0
- multi_agent_brief/analysis_modules/registry.py +73 -0
- multi_agent_brief/audience/__init__.py +19 -0
- multi_agent_brief/audience/profiles.py +246 -0
- multi_agent_brief/audience_memory/__init__.py +23 -0
- multi_agent_brief/audience_memory/profile.py +358 -0
- multi_agent_brief/audit/__init__.py +36 -0
- multi_agent_brief/audit/case_applicability.py +181 -0
- multi_agent_brief/audit/deterministic.py +471 -0
- multi_agent_brief/audit/editorial_governance.py +404 -0
- multi_agent_brief/audit/final_quality.py +764 -0
- multi_agent_brief/audit/harness.py +242 -0
- multi_agent_brief/audit/interfaces.py +96 -0
- multi_agent_brief/audit/limitation_hygiene.py +238 -0
- multi_agent_brief/audit/proposal_boundary.py +34 -0
- multi_agent_brief/audit/redaction.py +27 -0
- multi_agent_brief/audit/rule_packs.py +96 -0
- multi_agent_brief/audit/semantic.py +435 -0
- multi_agent_brief/capabilities/__init__.py +22 -0
- multi_agent_brief/capabilities/catalog.py +215 -0
- multi_agent_brief/capabilities/detect.py +199 -0
- multi_agent_brief/capabilities/models.py +60 -0
- multi_agent_brief/capabilities/recommend.py +206 -0
- multi_agent_brief/cli/__init__.py +2 -0
- multi_agent_brief/cli/analysis_commands.py +140 -0
- multi_agent_brief/cli/approval_commands.py +107 -0
- multi_agent_brief/cli/audit_commands.py +59 -0
- multi_agent_brief/cli/capability_commands.py +548 -0
- multi_agent_brief/cli/claude_commands.py +159 -0
- multi_agent_brief/cli/competitors_commands.py +182 -0
- multi_agent_brief/cli/controls_commands.py +137 -0
- multi_agent_brief/cli/deliver_commands.py +942 -0
- multi_agent_brief/cli/eval_cases_commands.py +99 -0
- multi_agent_brief/cli/experiments_commands.py +395 -0
- multi_agent_brief/cli/feedback_commands.py +192 -0
- multi_agent_brief/cli/finalize_commands.py +153 -0
- multi_agent_brief/cli/gates_commands.py +174 -0
- multi_agent_brief/cli/hermes_commands.py +356 -0
- multi_agent_brief/cli/improve_commands.py +224 -0
- multi_agent_brief/cli/init_commands.py +564 -0
- multi_agent_brief/cli/init_wizard.py +1473 -0
- multi_agent_brief/cli/input_commands.py +237 -0
- multi_agent_brief/cli/main.py +288 -0
- multi_agent_brief/cli/onboard_commands.py +193 -0
- multi_agent_brief/cli/product_commands.py +1449 -0
- multi_agent_brief/cli/provenance_commands.py +100 -0
- multi_agent_brief/cli/release_commands.py +68 -0
- multi_agent_brief/cli/repair_commands.py +169 -0
- multi_agent_brief/cli/run_commands.py +292 -0
- multi_agent_brief/cli/runtime_commands.py +81 -0
- multi_agent_brief/cli/secrets_commands.py +232 -0
- multi_agent_brief/cli/semantic_support_commands.py +80 -0
- multi_agent_brief/cli/sources_commands.py +647 -0
- multi_agent_brief/cli/start_commands.py +8 -0
- multi_agent_brief/cli/state_commands.py +446 -0
- multi_agent_brief/cli/status_commands.py +26 -0
- multi_agent_brief/cli/workbuddy_commands.py +103 -0
- multi_agent_brief/configs/artifact_contracts.yaml +514 -0
- multi_agent_brief/configs/orchestrator_contract.yaml +181 -0
- multi_agent_brief/configs/policy_packs/default.yaml +65 -0
- multi_agent_brief/configs/policy_profiles/evidence_extract_default.yaml +58 -0
- multi_agent_brief/configs/policy_profiles/finance_default.yaml +44 -0
- multi_agent_brief/configs/policy_profiles/internet_default.yaml +44 -0
- multi_agent_brief/configs/policy_profiles/manufacturing_default.yaml +33 -0
- multi_agent_brief/configs/policy_profiles/solar_manufacturing_default.yaml +64 -0
- multi_agent_brief/configs/report_packs/evidence_extract.yaml +48 -0
- multi_agent_brief/configs/report_packs/management_monthly.yaml +36 -0
- multi_agent_brief/configs/report_packs/market_weekly.yaml +36 -0
- multi_agent_brief/configs/report_packs/solar_industry_periodic.yaml +50 -0
- multi_agent_brief/configs/report_templates/evidence_extract.yaml +55 -0
- multi_agent_brief/configs/report_templates/management_monthly.yaml +48 -0
- multi_agent_brief/configs/report_templates/market_weekly.yaml +49 -0
- multi_agent_brief/configs/report_templates/solar_industry_periodic.yaml +75 -0
- multi_agent_brief/configs/stage_specs.yaml +180 -0
- multi_agent_brief/contracts/__init__.py +41 -0
- multi_agent_brief/contracts/base.py +99 -0
- multi_agent_brief/contracts/errors.py +41 -0
- multi_agent_brief/contracts/migrations/__init__.py +5 -0
- multi_agent_brief/contracts/migrations/claim_v1_to_v2.py +33 -0
- multi_agent_brief/contracts/registry.py +159 -0
- multi_agent_brief/contracts/role_topology.py +25 -0
- multi_agent_brief/contracts/schemas/__init__.py +35 -0
- multi_agent_brief/contracts/schemas/analysis_pack.py +135 -0
- multi_agent_brief/contracts/schemas/atomic_claim_graph.py +304 -0
- multi_agent_brief/contracts/schemas/audit_report.py +80 -0
- multi_agent_brief/contracts/schemas/candidate_item.py +51 -0
- multi_agent_brief/contracts/schemas/claim.py +197 -0
- multi_agent_brief/contracts/schemas/claim_draft.py +386 -0
- multi_agent_brief/contracts/schemas/claim_support_matrix.py +334 -0
- multi_agent_brief/contracts/schemas/evidence_span_registry.py +270 -0
- multi_agent_brief/contracts/schemas/policy_profile.py +247 -0
- multi_agent_brief/contracts/schemas/report_spec.py +257 -0
- multi_agent_brief/contracts/schemas/semantic_assessment_report.py +480 -0
- multi_agent_brief/contracts/schemas/source_evidence_pack_manifest.py +233 -0
- multi_agent_brief/contracts/schemas/source_item.py +96 -0
- multi_agent_brief/contracts/semantic_assessment_status.py +12 -0
- multi_agent_brief/contracts/source_metadata.py +252 -0
- multi_agent_brief/contracts/target_contract.py +460 -0
- multi_agent_brief/contracts/validator.py +225 -0
- multi_agent_brief/controls/__init__.py +23 -0
- multi_agent_brief/controls/contract.py +106 -0
- multi_agent_brief/controls/switchboard.py +1076 -0
- multi_agent_brief/core/__init__.py +2 -0
- multi_agent_brief/core/citations.py +14 -0
- multi_agent_brief/core/claim_ledger.py +94 -0
- multi_agent_brief/core/config.py +191 -0
- multi_agent_brief/core/env.py +69 -0
- multi_agent_brief/core/previous.py +83 -0
- multi_agent_brief/core/schemas.py +181 -0
- multi_agent_brief/core/selection.py +189 -0
- multi_agent_brief/delivery/__init__.py +6 -0
- multi_agent_brief/delivery/base.py +36 -0
- multi_agent_brief/delivery/feishu.py +204 -0
- multi_agent_brief/delivery/gws.py +237 -0
- multi_agent_brief/evaluation_cases/__init__.py +15 -0
- multi_agent_brief/evaluation_cases/contract.py +429 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/improvement/ledger.jsonl +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/approved_guidance_materialized/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/company_event_missing_latest_official_check/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/config.yaml +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/sources.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/control_switchboard_selection_is_not_execution/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/config.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/input/human_feedback.md +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/feedback_triage_required/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/brief.md +14 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/audited_brief.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/final_abstract_quality_warning_surface/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/formal_release_missing_human_approval/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/config.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/guidance_manifestation_not_observable/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/media_only_legal_policy_blocks_research_review/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/mixed_metric_scope_support_blocker/workspace/user.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/config.yaml +6 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/feedback_issues.json +25 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/repair_plan.json +27 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/planned_blocking_issue_cannot_continue/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/claim_ledger.json +11 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/quality_gate_report.json +22 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/provenance_projection_minimal/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/audited_brief.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/claim_ledger.json +40 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/sources.yaml +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_source_appendix/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/claim_ledger.json +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reader_facing_target_relevance/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/output/intermediate/release_readiness_report.json +31 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/release_readiness_forged_event_blocker/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/ledger.jsonl +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/improvement/memory.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/output/intermediate/improvement_memory_snapshot.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/reverted_entry_removed_from_next_snapshot/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/config.yaml +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/input/sources/source-001.txt +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/delivery/brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/atomic_claim_graph.json +17 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audit_report.json +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/audited_brief.md +37 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/claim_ledger.json +20 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/evidence_span_registry.json +20 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/finalize_report.json +40 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/auditor_quality_gate_report.json +42 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/gates/finalize_quality_gate_report.json +42 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/intermediate/screened_candidates.json +33 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix.md +12 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/output/source_appendix_trace.md +10 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/report_spec.yaml +24 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/sources.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/same_evidence_reader_quality_regression/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/input/sources/README.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/output/intermediate/source_evidence_pack_manifest.json +26 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/source_evidence_pack_blocks_non_evidence_file/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/config.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/audited_brief.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/claim_ledger.json +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/stale_current_claim/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/config.yaml +13 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/third_party_price_snapshot_formal_block/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/config.yaml +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/trajectory_retry_budget_exhausted/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/improvement/ledger.jsonl +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unapproved_entry_not_materialized/workspace/user.md +4 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/config.yaml +18 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/sources.yaml +9 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unauthorized_institution_branding_blocks_release/workspace/user.md +5 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/config.yaml +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audit_report.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/audited_brief.md +8 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/candidate_claims.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/claim_ledger.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/output/intermediate/screened_candidates.json +1 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/sources.yaml +2 -0
- multi_agent_brief/evaluation_cases/fixtures/cases/unsupported_material_fact/workspace/user.md +3 -0
- multi_agent_brief/evaluation_cases/fixtures/manifest.yaml +861 -0
- multi_agent_brief/evaluation_cases/fixtures.py +20 -0
- multi_agent_brief/evaluation_cases/runner.py +1603 -0
- multi_agent_brief/experiments/__init__.py +45 -0
- multi_agent_brief/experiments/experiment_080.py +5835 -0
- multi_agent_brief/experiments/schemas.py +63 -0
- multi_agent_brief/experiments/target_contract.py +10 -0
- multi_agent_brief/feedback/__init__.py +17 -0
- multi_agent_brief/feedback/feedback_contract.py +482 -0
- multi_agent_brief/feedback/feedback_state.py +898 -0
- multi_agent_brief/hermes/__init__.py +25 -0
- multi_agent_brief/hermes/adapter.py +832 -0
- multi_agent_brief/improvement/__init__.py +83 -0
- multi_agent_brief/improvement/contract.py +1114 -0
- multi_agent_brief/improvement/memory.py +451 -0
- multi_agent_brief/improvement/product_definition.py +228 -0
- multi_agent_brief/improvement/state.py +961 -0
- multi_agent_brief/inputs/__init__.py +2 -0
- multi_agent_brief/inputs/classifier.py +322 -0
- multi_agent_brief/inputs/contracts.py +59 -0
- multi_agent_brief/inputs/extractor.py +310 -0
- multi_agent_brief/install/__init__.py +2 -0
- multi_agent_brief/install/writer.py +97 -0
- multi_agent_brief/onboarding/__init__.py +11 -0
- multi_agent_brief/onboarding/io.py +103 -0
- multi_agent_brief/onboarding/mapper.py +484 -0
- multi_agent_brief/onboarding/schema.py +46 -0
- multi_agent_brief/orchestrator/__init__.py +2 -0
- multi_agent_brief/orchestrator/fact_layer_import.py +352 -0
- multi_agent_brief/orchestrator/handoff.py +2209 -0
- multi_agent_brief/orchestrator/role_topology.py +42 -0
- multi_agent_brief/orchestrator/run_archive.py +888 -0
- multi_agent_brief/orchestrator/run_integrity.py +346 -0
- multi_agent_brief/orchestrator/runtime_state/__init__.py +70 -0
- multi_agent_brief/orchestrator/runtime_state/_io.py +167 -0
- multi_agent_brief/orchestrator/runtime_state/_transactions.py +298 -0
- multi_agent_brief/orchestrator/runtime_state/artifact_registry.py +1825 -0
- multi_agent_brief/orchestrator/runtime_state/atomic_claim_graph.py +96 -0
- multi_agent_brief/orchestrator/runtime_state/claim_ledger_freeze.py +510 -0
- multi_agent_brief/orchestrator/runtime_state/claim_metadata_enrichment.py +836 -0
- multi_agent_brief/orchestrator/runtime_state/claim_support_matrix.py +582 -0
- multi_agent_brief/orchestrator/runtime_state/completion_gates.py +672 -0
- multi_agent_brief/orchestrator/runtime_state/contracts_loader.py +66 -0
- multi_agent_brief/orchestrator/runtime_state/decisions.py +239 -0
- multi_agent_brief/orchestrator/runtime_state/errors.py +61 -0
- multi_agent_brief/orchestrator/runtime_state/event_log.py +256 -0
- multi_agent_brief/orchestrator/runtime_state/evidence_span_registry.py +233 -0
- multi_agent_brief/orchestrator/runtime_state/fact_layer.py +755 -0
- multi_agent_brief/orchestrator/runtime_state/identity.py +90 -0
- multi_agent_brief/orchestrator/runtime_state/lifecycle.py +675 -0
- multi_agent_brief/orchestrator/runtime_state/manifest.py +82 -0
- multi_agent_brief/orchestrator/runtime_state/operations.py +224 -0
- multi_agent_brief/orchestrator/runtime_state/paths.py +38 -0
- multi_agent_brief/orchestrator/runtime_state/repair.py +984 -0
- multi_agent_brief/orchestrator/runtime_state/safety_surfaces.py +147 -0
- multi_agent_brief/orchestrator/runtime_state/semantic_assessment_report.py +692 -0
- multi_agent_brief/orchestrator/runtime_state/semantic_support_acceptance.py +683 -0
- multi_agent_brief/orchestrator/runtime_state/source_evidence_pack.py +131 -0
- multi_agent_brief/orchestrator/runtime_state/stage_completion.py +1320 -0
- multi_agent_brief/orchestrator/runtime_state/trajectory.py +200 -0
- multi_agent_brief/orchestrator/runtime_state/workflow.py +350 -0
- multi_agent_brief/orchestrator/source_evidence.py +43 -0
- multi_agent_brief/orchestrator/timing.py +410 -0
- multi_agent_brief/orchestrator_contract.py +96 -0
- multi_agent_brief/outputs/__init__.py +2 -0
- multi_agent_brief/outputs/atomic_reader_projection.py +288 -0
- multi_agent_brief/outputs/docx.py +32 -0
- multi_agent_brief/outputs/finalize.py +1036 -0
- multi_agent_brief/outputs/ib_docx.py +983 -0
- multi_agent_brief/outputs/naming.py +38 -0
- multi_agent_brief/outputs/pdf.py +20 -0
- multi_agent_brief/outputs/reader_final_gate.py +509 -0
- multi_agent_brief/outputs/reader_projection.py +397 -0
- multi_agent_brief/outputs/source_appendix.py +753 -0
- multi_agent_brief/outputs/templates/__init__.py +112 -0
- multi_agent_brief/product/__init__.py +49 -0
- multi_agent_brief/product/bundle_projection.py +686 -0
- multi_agent_brief/product/citation_profile.py +145 -0
- multi_agent_brief/product/guidance_manifestation.py +421 -0
- multi_agent_brief/product/materiality_selection.py +472 -0
- multi_agent_brief/product/policy_gate_adapter.py +116 -0
- multi_agent_brief/product/policy_profile.py +39 -0
- multi_agent_brief/product/policy_projection.py +161 -0
- multi_agent_brief/product/policy_registry.py +83 -0
- multi_agent_brief/product/policy_resolver.py +225 -0
- multi_agent_brief/product/quality_closeout.py +188 -0
- multi_agent_brief/product/quality_panel.py +2217 -0
- multi_agent_brief/product/release_approval.py +1034 -0
- multi_agent_brief/product/report_pack.py +130 -0
- multi_agent_brief/product/report_pack_aliases.py +68 -0
- multi_agent_brief/product/report_registry.py +101 -0
- multi_agent_brief/product/report_spec.py +181 -0
- multi_agent_brief/product/support_wording.py +525 -0
- multi_agent_brief/product/template_conformance.py +516 -0
- multi_agent_brief/product/template_projection.py +123 -0
- multi_agent_brief/product/template_registry.py +244 -0
- multi_agent_brief/product/template_render_plan.py +315 -0
- multi_agent_brief/product/template_renderer.py +224 -0
- multi_agent_brief/product/trajectory_regulation.py +441 -0
- multi_agent_brief/provenance/__init__.py +5 -0
- multi_agent_brief/provenance/builder.py +841 -0
- multi_agent_brief/provenance/contract.py +21 -0
- multi_agent_brief/provenance/io.py +144 -0
- multi_agent_brief/provenance/model.py +110 -0
- multi_agent_brief/provenance/references.py +43 -0
- multi_agent_brief/provenance/validator.py +135 -0
- multi_agent_brief/quality_gates/__init__.py +19 -0
- multi_agent_brief/quality_gates/contract.py +581 -0
- multi_agent_brief/quality_gates/state.py +2498 -0
- multi_agent_brief/repair/__init__.py +5 -0
- multi_agent_brief/repair/router.py +842 -0
- multi_agent_brief/runtime_assets.py +431 -0
- multi_agent_brief/sources/__init__.py +16 -0
- multi_agent_brief/sources/api_filings.py +236 -0
- multi_agent_brief/sources/api_news.py +174 -0
- multi_agent_brief/sources/base.py +142 -0
- multi_agent_brief/sources/cached_package.py +119 -0
- multi_agent_brief/sources/cli_provider.py +263 -0
- multi_agent_brief/sources/coverage.py +336 -0
- multi_agent_brief/sources/decider.py +756 -0
- multi_agent_brief/sources/doctor.py +373 -0
- multi_agent_brief/sources/evidence_pack.py +364 -0
- multi_agent_brief/sources/feishu_provider.py +326 -0
- multi_agent_brief/sources/filing_resolver.py +400 -0
- multi_agent_brief/sources/industry_packs.py +130 -0
- multi_agent_brief/sources/join.py +206 -0
- multi_agent_brief/sources/local_signal.py +81 -0
- multi_agent_brief/sources/local_signal_planner.py +636 -0
- multi_agent_brief/sources/manual.py +259 -0
- multi_agent_brief/sources/mcp_provider.py +309 -0
- multi_agent_brief/sources/mineru_provider.py +601 -0
- multi_agent_brief/sources/normalizer.py +95 -0
- multi_agent_brief/sources/opencli_provider.py +282 -0
- multi_agent_brief/sources/planner.py +178 -0
- multi_agent_brief/sources/registry.py +367 -0
- multi_agent_brief/sources/rss.py +190 -0
- multi_agent_brief/sources/search_backends/__init__.py +31 -0
- multi_agent_brief/sources/search_backends/base.py +51 -0
- multi_agent_brief/sources/search_backends/brave.py +192 -0
- multi_agent_brief/sources/search_backends/capabilities.py +94 -0
- multi_agent_brief/sources/search_backends/exa.py +180 -0
- multi_agent_brief/sources/search_backends/firecrawl.py +164 -0
- multi_agent_brief/sources/search_backends/serper.py +241 -0
- multi_agent_brief/sources/search_backends/tavily.py +124 -0
- multi_agent_brief/sources/sourcehub.py +505 -0
- multi_agent_brief/sources/web_search.py +267 -0
- multi_agent_brief/status.py +966 -0
- multi_agent_brief/tools/__init__.py +3 -0
- multi_agent_brief/tools/draft_cleanup.py +198 -0
- multi_agent_brief/workbuddy/__init__.py +1 -0
- multi_agent_brief/workbuddy/diagnose.py +569 -0
- multi_agent_brief/workbuddy/skill_pack.py +309 -0
- multi_agent_brief/workspace/__init__.py +1 -0
- multi_agent_brief/workspace/init_profile.py +45 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"""Assessment target definitions and read-only projections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fnmatch
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ALLOWED_ASSESSMENT_TARGETS = {"auditable_brief", "delivery_brief"}
|
|
12
|
+
DEFAULT_ASSESSMENT_TARGET = "delivery_brief"
|
|
13
|
+
EXPERIMENT_080_CONDITION_PATH = Path("experiment/080/condition.json")
|
|
14
|
+
AUDIT_BINDING_SCHEMA = "mabw.auditable_audit_binding.v1"
|
|
15
|
+
AUDITABLE_TARGET_DOWNSTREAM_STAGES = ("analyst", "editor", "auditor")
|
|
16
|
+
|
|
17
|
+
AUDITABLE_TARGET_ARTIFACTS = {
|
|
18
|
+
"audited_brief": "output/intermediate/audited_brief.md",
|
|
19
|
+
"audit_report": "output/intermediate/audit_report.json",
|
|
20
|
+
"auditor_quality_gate_report": "output/intermediate/gates/auditor_quality_gate_report.json",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
FINAL_ABSTRACT_QUALITY_WARNING_TYPES = {
|
|
24
|
+
"final_scope_title_mismatch",
|
|
25
|
+
"final_missing_comparison_basis",
|
|
26
|
+
"final_missing_limitation_section",
|
|
27
|
+
"final_incomplete_key_case_fields",
|
|
28
|
+
"final_unsupported_superlative",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ASSESSMENT_TARGET_CLAIM_SCOPE = {
|
|
32
|
+
"auditable_brief": [
|
|
33
|
+
"guidance_manifestation_in_audited_brief",
|
|
34
|
+
"evidence_use_under_frozen_fact_layer",
|
|
35
|
+
"auditor_gate_passage",
|
|
36
|
+
],
|
|
37
|
+
"delivery_brief": [
|
|
38
|
+
"guidance_manifestation_in_reader_delivery",
|
|
39
|
+
"reader_clean_delivery",
|
|
40
|
+
"finalize_transform_included",
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ASSESSMENT_TARGET_EXCLUDED_CLAIM_SCOPE = {
|
|
45
|
+
"auditable_brief": [
|
|
46
|
+
"reader_clean_delivery",
|
|
47
|
+
"finalize_transform_correctness",
|
|
48
|
+
"management_ready_output",
|
|
49
|
+
"docx_pdf_delivery_quality",
|
|
50
|
+
],
|
|
51
|
+
"delivery_brief": [],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ASSESSMENT_TARGET_INCLUDED_CONTROLS = {
|
|
55
|
+
"auditable_brief": [
|
|
56
|
+
"analyst complete",
|
|
57
|
+
"editor complete",
|
|
58
|
+
"auditor complete",
|
|
59
|
+
"auditor gates pass",
|
|
60
|
+
"audit report artifact present",
|
|
61
|
+
"auditor gate report artifact present",
|
|
62
|
+
"run integrity clean",
|
|
63
|
+
],
|
|
64
|
+
"delivery_brief": [
|
|
65
|
+
"finalize complete",
|
|
66
|
+
"reader-clean delivery pass",
|
|
67
|
+
"delivery artifacts present",
|
|
68
|
+
"run archive present",
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
AUDITABLE_TARGET_NEXT_ALLOWED = [
|
|
73
|
+
"multi-agent-brief experiments 080 register-run --case <case_dir> --condition <condition> --workspace <workspace> --output <run_record.json>",
|
|
74
|
+
"multi-agent-brief experiments 080 score-run --case <case_dir> --run-record <run_record.json> --output <scorecard.json>",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
AUDITABLE_TARGET_FORBIDDEN = [
|
|
78
|
+
"finalize",
|
|
79
|
+
"finalize-complete",
|
|
80
|
+
"deliver",
|
|
81
|
+
"DOCX/PDF delivery quality claims",
|
|
82
|
+
"reader-clean delivery claims",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def assessment_target(container: dict[str, Any]) -> str:
|
|
87
|
+
value = container.get("assessment_target")
|
|
88
|
+
if isinstance(value, str) and value in ALLOWED_ASSESSMENT_TARGETS:
|
|
89
|
+
return value
|
|
90
|
+
return DEFAULT_ASSESSMENT_TARGET
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def assessment_target_manifest(target: str) -> dict[str, Any]:
|
|
94
|
+
target = target if target in ALLOWED_ASSESSMENT_TARGETS else DEFAULT_ASSESSMENT_TARGET
|
|
95
|
+
if target == "auditable_brief":
|
|
96
|
+
return {
|
|
97
|
+
"assessment_target": target,
|
|
98
|
+
"target_status_semantics": "auditor_ready_internal_auditable_draft",
|
|
99
|
+
"target_artifact": "output/intermediate/audited_brief.md",
|
|
100
|
+
"target_complete_when": [
|
|
101
|
+
"analyst_complete",
|
|
102
|
+
"editor_complete",
|
|
103
|
+
"auditor_complete",
|
|
104
|
+
"analyst_editor_auditor_stage_completion_events_bound",
|
|
105
|
+
"auditor_quality_gates_pass",
|
|
106
|
+
"run_integrity_clean",
|
|
107
|
+
"no_active_repair",
|
|
108
|
+
],
|
|
109
|
+
"included_control_scope": ASSESSMENT_TARGET_INCLUDED_CONTROLS[target],
|
|
110
|
+
"claim_scope": ASSESSMENT_TARGET_CLAIM_SCOPE[target],
|
|
111
|
+
"excluded_claim_scope": ASSESSMENT_TARGET_EXCLUDED_CLAIM_SCOPE[target],
|
|
112
|
+
"audit_binding_status": "required_python_owned",
|
|
113
|
+
"timing_semantics": "diagnostic_only",
|
|
114
|
+
"reader_clean_required": False,
|
|
115
|
+
"delivery_archive_required": False,
|
|
116
|
+
"next_allowed_commands": list(AUDITABLE_TARGET_NEXT_ALLOWED),
|
|
117
|
+
"forbidden_downstream_actions": list(AUDITABLE_TARGET_FORBIDDEN),
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
"assessment_target": target,
|
|
121
|
+
"target_status_semantics": "reader_delivery_brief",
|
|
122
|
+
"target_artifact": "output/delivery/brief.md",
|
|
123
|
+
"target_complete_when": [
|
|
124
|
+
"finalize_complete",
|
|
125
|
+
"reader_clean_pass",
|
|
126
|
+
"delivery_artifacts_present",
|
|
127
|
+
"run_archive_present",
|
|
128
|
+
],
|
|
129
|
+
"included_control_scope": ASSESSMENT_TARGET_INCLUDED_CONTROLS[target],
|
|
130
|
+
"claim_scope": ASSESSMENT_TARGET_CLAIM_SCOPE[target],
|
|
131
|
+
"excluded_claim_scope": ASSESSMENT_TARGET_EXCLUDED_CLAIM_SCOPE[target],
|
|
132
|
+
"audit_binding_status": "not_applicable",
|
|
133
|
+
"timing_semantics": "required_control_for_a_controlled",
|
|
134
|
+
"reader_clean_required": True,
|
|
135
|
+
"delivery_archive_required": True,
|
|
136
|
+
"next_allowed_commands": [],
|
|
137
|
+
"forbidden_downstream_actions": [],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def load_experiment_080_condition_metadata(workspace: str | Path) -> dict[str, Any] | None:
|
|
142
|
+
path = Path(workspace).expanduser().resolve() / EXPERIMENT_080_CONDITION_PATH
|
|
143
|
+
if not path.exists():
|
|
144
|
+
return None
|
|
145
|
+
try:
|
|
146
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
147
|
+
except (OSError, json.JSONDecodeError):
|
|
148
|
+
return None
|
|
149
|
+
return payload if isinstance(payload, dict) else None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def project_assessment_target_status(
|
|
153
|
+
*,
|
|
154
|
+
condition_metadata: dict[str, Any] | None,
|
|
155
|
+
workflow_state: dict[str, Any] | None,
|
|
156
|
+
artifact_registry: dict[str, Any] | None,
|
|
157
|
+
auditor_gate_report: dict[str, Any] | None,
|
|
158
|
+
event_records: list[dict[str, Any]] | None = None,
|
|
159
|
+
) -> dict[str, Any]:
|
|
160
|
+
if not isinstance(condition_metadata, dict):
|
|
161
|
+
return {"present": False}
|
|
162
|
+
target = assessment_target(condition_metadata)
|
|
163
|
+
manifest = assessment_target_manifest(target)
|
|
164
|
+
projection: dict[str, Any] = {
|
|
165
|
+
"present": True,
|
|
166
|
+
"experiment_id": condition_metadata.get("experiment_id"),
|
|
167
|
+
"case_id": condition_metadata.get("case_id"),
|
|
168
|
+
"condition": condition_metadata.get("condition"),
|
|
169
|
+
"assessment_target": target,
|
|
170
|
+
"assessment_target_manifest": manifest,
|
|
171
|
+
"target_complete": False,
|
|
172
|
+
"status": "not_applicable" if target != "auditable_brief" else "incomplete",
|
|
173
|
+
"audit_binding_status": manifest.get("audit_binding_status"),
|
|
174
|
+
"reasons": [],
|
|
175
|
+
}
|
|
176
|
+
if target != "auditable_brief":
|
|
177
|
+
return projection
|
|
178
|
+
|
|
179
|
+
reasons = _auditable_target_incomplete_reasons(
|
|
180
|
+
workflow_state=workflow_state,
|
|
181
|
+
artifact_registry=artifact_registry,
|
|
182
|
+
auditor_gate_report=auditor_gate_report,
|
|
183
|
+
event_records=event_records,
|
|
184
|
+
)
|
|
185
|
+
projection["target_complete"] = not reasons
|
|
186
|
+
projection["status"] = "complete" if not reasons else "incomplete"
|
|
187
|
+
projection["reasons"] = reasons
|
|
188
|
+
return projection
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _auditable_target_incomplete_reasons(
|
|
192
|
+
*,
|
|
193
|
+
workflow_state: dict[str, Any] | None,
|
|
194
|
+
artifact_registry: dict[str, Any] | None,
|
|
195
|
+
auditor_gate_report: dict[str, Any] | None,
|
|
196
|
+
event_records: list[dict[str, Any]] | None,
|
|
197
|
+
) -> list[str]:
|
|
198
|
+
reasons: list[str] = []
|
|
199
|
+
workflow = workflow_state if isinstance(workflow_state, dict) else {}
|
|
200
|
+
registry = artifact_registry if isinstance(artifact_registry, dict) else {}
|
|
201
|
+
artifacts = registry.get("artifacts") if isinstance(registry.get("artifacts"), dict) else {}
|
|
202
|
+
|
|
203
|
+
current_stage = workflow.get("current_stage")
|
|
204
|
+
if current_stage not in {"finalize", None}:
|
|
205
|
+
reasons.append(f"workflow current_stage is {current_stage or '<missing>'}, expected finalize or terminal")
|
|
206
|
+
if isinstance(workflow.get("active_repair"), dict):
|
|
207
|
+
reasons.append("active repair is open")
|
|
208
|
+
|
|
209
|
+
statuses = workflow.get("stage_statuses") if isinstance(workflow.get("stage_statuses"), dict) else {}
|
|
210
|
+
for stage_id in AUDITABLE_TARGET_DOWNSTREAM_STAGES:
|
|
211
|
+
stage_status = statuses.get(stage_id) if isinstance(statuses.get(stage_id), dict) else {}
|
|
212
|
+
if stage_status.get("status") != "complete":
|
|
213
|
+
reasons.append(f"{stage_id} stage is not complete")
|
|
214
|
+
reasons.extend(
|
|
215
|
+
auditable_stage_completion_event_reasons(
|
|
216
|
+
workflow_state=workflow,
|
|
217
|
+
event_records=event_records,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
integrity = workflow.get("run_integrity") if isinstance(workflow.get("run_integrity"), dict) else {}
|
|
222
|
+
if integrity.get("status") != "clean":
|
|
223
|
+
reasons.append("run_integrity is not clean")
|
|
224
|
+
if integrity.get("reference_eligible") is not True:
|
|
225
|
+
reasons.append("run_integrity.reference_eligible is not true")
|
|
226
|
+
|
|
227
|
+
for artifact_id, expected_path in AUDITABLE_TARGET_ARTIFACTS.items():
|
|
228
|
+
record = artifacts.get(artifact_id) if isinstance(artifacts.get(artifact_id), dict) else {}
|
|
229
|
+
if record.get("path") != expected_path:
|
|
230
|
+
reasons.append(f"{artifact_id} path is not {expected_path}")
|
|
231
|
+
if record.get("status") != "valid":
|
|
232
|
+
reasons.append(f"{artifact_id} is not valid in artifact_registry")
|
|
233
|
+
if not isinstance(record.get("sha256"), str) or len(str(record.get("sha256"))) != 64:
|
|
234
|
+
reasons.append(f"{artifact_id} has no frozen sha256")
|
|
235
|
+
|
|
236
|
+
_extend_audit_binding_reasons(
|
|
237
|
+
reasons,
|
|
238
|
+
workflow=workflow,
|
|
239
|
+
artifacts=artifacts,
|
|
240
|
+
event_records=event_records,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
gate = auditor_gate_report if isinstance(auditor_gate_report, dict) else {}
|
|
244
|
+
if gate.get("status") != "pass" and not auditable_gate_has_only_final_abstract_advisory_warnings(gate):
|
|
245
|
+
reasons.append("auditor quality gate report status is not pass")
|
|
246
|
+
for result in gate.get("gate_results") or []:
|
|
247
|
+
if isinstance(result, dict) and (result.get("status") == "fail" or result.get("blocking") is True):
|
|
248
|
+
reasons.append("auditor quality gate report contains blocking gate_results")
|
|
249
|
+
break
|
|
250
|
+
for finding in gate.get("findings") or []:
|
|
251
|
+
if isinstance(finding, dict) and (
|
|
252
|
+
finding.get("blocking") is True or finding.get("blocking_level") == "blocking"
|
|
253
|
+
):
|
|
254
|
+
reasons.append("auditor quality gate report contains blocking findings")
|
|
255
|
+
break
|
|
256
|
+
|
|
257
|
+
return reasons
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def auditable_gate_has_only_final_abstract_advisory_warnings(payload: dict[str, Any]) -> bool:
|
|
261
|
+
if payload.get("status") != "warning":
|
|
262
|
+
return False
|
|
263
|
+
gate_results = payload.get("gate_results")
|
|
264
|
+
findings = payload.get("findings")
|
|
265
|
+
if not isinstance(gate_results, list) or not isinstance(findings, list):
|
|
266
|
+
return False
|
|
267
|
+
advisory_finding_ids: set[str] = set()
|
|
268
|
+
for finding in findings:
|
|
269
|
+
if not isinstance(finding, dict):
|
|
270
|
+
return False
|
|
271
|
+
if finding.get("gate_id") != "final_abstract_quality":
|
|
272
|
+
return False
|
|
273
|
+
if finding.get("finding_type") not in FINAL_ABSTRACT_QUALITY_WARNING_TYPES:
|
|
274
|
+
return False
|
|
275
|
+
if finding.get("blocking") is True or finding.get("blocking_level") == "blocking":
|
|
276
|
+
return False
|
|
277
|
+
finding_id = str(finding.get("finding_id") or "")
|
|
278
|
+
if finding_id:
|
|
279
|
+
advisory_finding_ids.add(finding_id)
|
|
280
|
+
|
|
281
|
+
saw_advisory_warning = False
|
|
282
|
+
for result in gate_results:
|
|
283
|
+
if not isinstance(result, dict):
|
|
284
|
+
return False
|
|
285
|
+
gate_id = result.get("gate_id")
|
|
286
|
+
status = result.get("status")
|
|
287
|
+
if status == "pass":
|
|
288
|
+
continue
|
|
289
|
+
if gate_id != "final_abstract_quality" or status != "warning" or result.get("blocking") is True:
|
|
290
|
+
return False
|
|
291
|
+
finding_ids = result.get("finding_ids")
|
|
292
|
+
if not isinstance(finding_ids, list):
|
|
293
|
+
return False
|
|
294
|
+
if any(str(finding_id) not in advisory_finding_ids for finding_id in finding_ids):
|
|
295
|
+
return False
|
|
296
|
+
saw_advisory_warning = True
|
|
297
|
+
return saw_advisory_warning
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def auditable_gate_projection_has_only_final_abstract_advisory_warnings(gate: dict[str, Any]) -> bool:
|
|
301
|
+
if gate.get("report_status") != "warning" or gate.get("no_blocking") is not True:
|
|
302
|
+
return False
|
|
303
|
+
warning_types = gate.get("warning_finding_types")
|
|
304
|
+
if not isinstance(warning_types, list) or not warning_types:
|
|
305
|
+
return False
|
|
306
|
+
normalized = {str(item) for item in warning_types if isinstance(item, str) and item}
|
|
307
|
+
return bool(normalized) and normalized <= FINAL_ABSTRACT_QUALITY_WARNING_TYPES
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def auditable_stage_completion_event_reasons(
|
|
311
|
+
*,
|
|
312
|
+
workflow_state: dict[str, Any] | None,
|
|
313
|
+
event_records: list[dict[str, Any]] | None,
|
|
314
|
+
) -> list[str]:
|
|
315
|
+
workflow = workflow_state if isinstance(workflow_state, dict) else {}
|
|
316
|
+
if event_records is None:
|
|
317
|
+
return ["stage completion event log is unavailable"]
|
|
318
|
+
run_id = workflow.get("run_id")
|
|
319
|
+
run_id_text = run_id.strip() if isinstance(run_id, str) and run_id.strip() else ""
|
|
320
|
+
if not run_id_text:
|
|
321
|
+
return ["stage completion events cannot verify workflow run_id"]
|
|
322
|
+
reasons: list[str] = []
|
|
323
|
+
for stage_id in AUDITABLE_TARGET_DOWNSTREAM_STAGES:
|
|
324
|
+
if _auditable_stage_completion_event(event_records, run_id=run_id_text, stage_id=stage_id) is None:
|
|
325
|
+
reasons.append(f"{stage_id} stage completion decision_recorded event is missing")
|
|
326
|
+
return reasons
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _extend_audit_binding_reasons(
|
|
330
|
+
reasons: list[str],
|
|
331
|
+
*,
|
|
332
|
+
workflow: dict[str, Any],
|
|
333
|
+
artifacts: dict[str, Any],
|
|
334
|
+
event_records: list[dict[str, Any]] | None,
|
|
335
|
+
) -> None:
|
|
336
|
+
statuses = workflow.get("stage_statuses") if isinstance(workflow.get("stage_statuses"), dict) else {}
|
|
337
|
+
auditor_status = statuses.get("auditor") if isinstance(statuses.get("auditor"), dict) else {}
|
|
338
|
+
auditor_metadata = (
|
|
339
|
+
auditor_status.get("metadata")
|
|
340
|
+
if isinstance(auditor_status.get("metadata"), dict)
|
|
341
|
+
else {}
|
|
342
|
+
)
|
|
343
|
+
binding = (
|
|
344
|
+
auditor_metadata.get("audit_binding")
|
|
345
|
+
if isinstance(auditor_metadata.get("audit_binding"), dict)
|
|
346
|
+
else {}
|
|
347
|
+
)
|
|
348
|
+
if binding.get("schema_version") != AUDIT_BINDING_SCHEMA:
|
|
349
|
+
reasons.append("auditor audit_binding is missing or has unsupported schema")
|
|
350
|
+
return
|
|
351
|
+
expected = {
|
|
352
|
+
"claim_ledger_sha256": _artifact_sha(artifacts, "claim_ledger"),
|
|
353
|
+
"audited_brief_sha256": _artifact_sha(artifacts, "audited_brief"),
|
|
354
|
+
"audit_report_sha256": _artifact_sha(artifacts, "audit_report"),
|
|
355
|
+
}
|
|
356
|
+
for field, expected_sha in expected.items():
|
|
357
|
+
actual = binding.get(field)
|
|
358
|
+
if not isinstance(expected_sha, str) or len(expected_sha) != 64:
|
|
359
|
+
reasons.append(f"audit binding cannot verify {field.removesuffix('_sha256')}")
|
|
360
|
+
elif actual != expected_sha:
|
|
361
|
+
reasons.append(f"audit binding {field} does not match artifact_registry")
|
|
362
|
+
run_id = workflow.get("run_id")
|
|
363
|
+
run_id_text = run_id.strip() if isinstance(run_id, str) and run_id.strip() else ""
|
|
364
|
+
repair_ids = binding.get("relevant_repair_transaction_ids")
|
|
365
|
+
if not isinstance(repair_ids, list) or any(not isinstance(item, str) or not item for item in repair_ids):
|
|
366
|
+
reasons.append("audit binding relevant_repair_transaction_ids is invalid")
|
|
367
|
+
elif event_records is not None:
|
|
368
|
+
if not run_id_text:
|
|
369
|
+
reasons.append("audit binding repair history cannot verify workflow run_id")
|
|
370
|
+
else:
|
|
371
|
+
expected_repair_ids = _auditable_brief_repair_transaction_ids(
|
|
372
|
+
event_records,
|
|
373
|
+
run_id=run_id_text,
|
|
374
|
+
)
|
|
375
|
+
if list(repair_ids) != expected_repair_ids:
|
|
376
|
+
reasons.append("audit binding relevant_repair_transaction_ids does not match event_log")
|
|
377
|
+
auditor_stage_transaction_id = binding.get("auditor_stage_transaction_id")
|
|
378
|
+
if not isinstance(auditor_stage_transaction_id, str) or not auditor_stage_transaction_id:
|
|
379
|
+
reasons.append("audit binding auditor_stage_transaction_id is missing")
|
|
380
|
+
elif event_records is not None:
|
|
381
|
+
if not run_id_text:
|
|
382
|
+
reasons.append("audit binding auditor_stage_transaction_id cannot verify workflow run_id")
|
|
383
|
+
elif _auditable_auditor_completion_event(
|
|
384
|
+
event_records,
|
|
385
|
+
run_id=run_id_text,
|
|
386
|
+
transaction_id=auditor_stage_transaction_id,
|
|
387
|
+
) is None:
|
|
388
|
+
reasons.append("audit binding auditor_stage_transaction_id does not match event_log")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _artifact_sha(artifacts: dict[str, Any], artifact_id: str) -> str | None:
|
|
392
|
+
record = artifacts.get(artifact_id) if isinstance(artifacts.get(artifact_id), dict) else {}
|
|
393
|
+
sha = record.get("sha256")
|
|
394
|
+
return sha if isinstance(sha, str) else None
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _auditable_brief_repair_transaction_ids(
|
|
398
|
+
records: list[dict[str, Any]],
|
|
399
|
+
*,
|
|
400
|
+
run_id: str,
|
|
401
|
+
) -> list[str]:
|
|
402
|
+
ids: list[str] = []
|
|
403
|
+
artifact_path = "output/intermediate/audited_brief.md"
|
|
404
|
+
for event in records:
|
|
405
|
+
if event.get("run_id") != run_id:
|
|
406
|
+
continue
|
|
407
|
+
if event.get("event_type") != "repair_completed":
|
|
408
|
+
continue
|
|
409
|
+
metadata = event.get("metadata") if isinstance(event.get("metadata"), dict) else {}
|
|
410
|
+
allowed = [str(item) for item in metadata.get("allowed_artifacts") or []]
|
|
411
|
+
if not any(_artifact_path_matches(pattern, artifact_path) for pattern in allowed):
|
|
412
|
+
continue
|
|
413
|
+
transaction_id = metadata.get("transaction_id") or metadata.get("repair_transaction_id")
|
|
414
|
+
if isinstance(transaction_id, str) and transaction_id and transaction_id not in ids:
|
|
415
|
+
ids.append(transaction_id)
|
|
416
|
+
return ids
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _artifact_path_matches(pattern: str, path: str) -> bool:
|
|
420
|
+
candidate = pattern.strip()
|
|
421
|
+
return bool(candidate and (path == candidate or fnmatch.fnmatch(path, candidate)))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _auditable_auditor_completion_event(
|
|
425
|
+
records: list[dict[str, Any]],
|
|
426
|
+
*,
|
|
427
|
+
run_id: str,
|
|
428
|
+
transaction_id: str,
|
|
429
|
+
) -> dict[str, Any] | None:
|
|
430
|
+
for event in records:
|
|
431
|
+
if event.get("run_id") != run_id:
|
|
432
|
+
continue
|
|
433
|
+
if event.get("event_type") != "decision_recorded":
|
|
434
|
+
continue
|
|
435
|
+
if event.get("stage_id") != "auditor" or event.get("decision") != "continue":
|
|
436
|
+
continue
|
|
437
|
+
metadata = event.get("metadata") if isinstance(event.get("metadata"), dict) else {}
|
|
438
|
+
if metadata.get("transaction_id") == transaction_id:
|
|
439
|
+
return event
|
|
440
|
+
return None
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _auditable_stage_completion_event(
|
|
444
|
+
records: list[dict[str, Any]],
|
|
445
|
+
*,
|
|
446
|
+
run_id: str,
|
|
447
|
+
stage_id: str,
|
|
448
|
+
) -> dict[str, Any] | None:
|
|
449
|
+
for event in records:
|
|
450
|
+
if event.get("run_id") != run_id:
|
|
451
|
+
continue
|
|
452
|
+
if event.get("event_type") != "decision_recorded":
|
|
453
|
+
continue
|
|
454
|
+
if event.get("stage_id") != stage_id or event.get("decision") != "continue":
|
|
455
|
+
continue
|
|
456
|
+
metadata = event.get("metadata") if isinstance(event.get("metadata"), dict) else {}
|
|
457
|
+
transaction_id = metadata.get("transaction_id")
|
|
458
|
+
if isinstance(transaction_id, str) and transaction_id:
|
|
459
|
+
return event
|
|
460
|
+
return None
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Validation helpers for MABW orchestration contract registries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from multi_agent_brief.contracts.errors import FieldViolation
|
|
8
|
+
from multi_agent_brief.contracts.registry import ContractRegistry
|
|
9
|
+
from multi_agent_brief.contracts.role_topology import (
|
|
10
|
+
ROLE_TOPOLOGY_SATISFIER_VALUES,
|
|
11
|
+
ROLE_TOPOLOGY_VALUES,
|
|
12
|
+
resolve_role_topology,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PARITY_CONFIG_FILES = (
|
|
17
|
+
"stage_specs.yaml",
|
|
18
|
+
"artifact_contracts.yaml",
|
|
19
|
+
"orchestrator_contract.yaml",
|
|
20
|
+
"policy_packs/default.yaml",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate_contract_registry(registry: ContractRegistry) -> list[FieldViolation]:
|
|
25
|
+
"""Validate stage/artifact/decision references in a registry."""
|
|
26
|
+
violations: list[FieldViolation] = []
|
|
27
|
+
stage_ids = [stage.stage_id for stage in registry.stages]
|
|
28
|
+
artifact_ids = [artifact.artifact_id for artifact in registry.artifacts]
|
|
29
|
+
stage_id_set = set(stage_ids)
|
|
30
|
+
artifact_id_set = set(artifact_ids)
|
|
31
|
+
decision_set = set(registry.decision_vocabulary)
|
|
32
|
+
producer_kind_set = set(registry.producer_kind_values)
|
|
33
|
+
|
|
34
|
+
violations.extend(_duplicate_violations("stages.stage_id", stage_ids))
|
|
35
|
+
violations.extend(_duplicate_violations("artifacts.artifact_id", artifact_ids))
|
|
36
|
+
violations.extend(_role_topology_violations(registry))
|
|
37
|
+
|
|
38
|
+
for stage in registry.stages:
|
|
39
|
+
if not stage.stage_id:
|
|
40
|
+
violations.append(FieldViolation("stages.stage_id", "stage_id is required"))
|
|
41
|
+
for artifact_id in stage.expected_artifacts:
|
|
42
|
+
if artifact_id not in artifact_id_set:
|
|
43
|
+
violations.append(
|
|
44
|
+
FieldViolation(
|
|
45
|
+
f"stages.{stage.stage_id}.expected_artifacts",
|
|
46
|
+
f"unknown artifact: {artifact_id}",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
for decision in stage.allowed_decisions:
|
|
50
|
+
if decision not in decision_set:
|
|
51
|
+
violations.append(
|
|
52
|
+
FieldViolation(
|
|
53
|
+
f"stages.{stage.stage_id}.allowed_decisions",
|
|
54
|
+
f"unknown decision: {decision}",
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for artifact in registry.artifacts:
|
|
59
|
+
if not artifact.artifact_id:
|
|
60
|
+
violations.append(
|
|
61
|
+
FieldViolation("artifacts.artifact_id", "artifact_id is required")
|
|
62
|
+
)
|
|
63
|
+
if producer_kind_set and artifact.producer_kind not in producer_kind_set:
|
|
64
|
+
violations.append(
|
|
65
|
+
FieldViolation(
|
|
66
|
+
f"artifacts.{artifact.artifact_id}.producer_kind",
|
|
67
|
+
f"unknown producer kind: {artifact.producer_kind}",
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
if artifact.producer_kind == "workflow_stage" and artifact.producer_stage not in stage_id_set:
|
|
71
|
+
violations.append(
|
|
72
|
+
FieldViolation(
|
|
73
|
+
f"artifacts.{artifact.artifact_id}.producer_stage",
|
|
74
|
+
f"unknown producer stage: {artifact.producer_stage}",
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
for stage_id in artifact.consumer_stages:
|
|
78
|
+
if stage_id not in stage_id_set:
|
|
79
|
+
violations.append(
|
|
80
|
+
FieldViolation(
|
|
81
|
+
f"artifacts.{artifact.artifact_id}.consumer_stages",
|
|
82
|
+
f"unknown consumer stage: {stage_id}",
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
for decision in artifact.allowed_decisions:
|
|
86
|
+
if decision not in decision_set:
|
|
87
|
+
violations.append(
|
|
88
|
+
FieldViolation(
|
|
89
|
+
f"artifacts.{artifact.artifact_id}.allowed_decisions",
|
|
90
|
+
f"unknown decision: {decision}",
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return violations
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _role_topology_violations(registry: ContractRegistry) -> list[FieldViolation]:
|
|
98
|
+
violations: list[FieldViolation] = []
|
|
99
|
+
try:
|
|
100
|
+
resolve_role_topology(registry.policy_pack_data)
|
|
101
|
+
except ValueError:
|
|
102
|
+
violations.append(
|
|
103
|
+
FieldViolation(
|
|
104
|
+
"policy.role_topology",
|
|
105
|
+
"must be one of: " + ", ".join(sorted(ROLE_TOPOLOGY_VALUES)),
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
artifact_id_set = {artifact.artifact_id for artifact in registry.artifacts}
|
|
110
|
+
for stage in registry.stage_specs_data.get("workflow", {}).get("stages", []) or []:
|
|
111
|
+
if not isinstance(stage, dict):
|
|
112
|
+
continue
|
|
113
|
+
stage_id = str(stage.get("stage_id") or "")
|
|
114
|
+
topology_satisfaction = stage.get("topology_satisfaction")
|
|
115
|
+
if topology_satisfaction is None:
|
|
116
|
+
continue
|
|
117
|
+
if not isinstance(topology_satisfaction, dict):
|
|
118
|
+
violations.append(
|
|
119
|
+
FieldViolation(
|
|
120
|
+
f"stages.{stage_id}.topology_satisfaction",
|
|
121
|
+
"must be an object",
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
continue
|
|
125
|
+
for topology, rule in topology_satisfaction.items():
|
|
126
|
+
if str(topology) not in ROLE_TOPOLOGY_VALUES:
|
|
127
|
+
violations.append(
|
|
128
|
+
FieldViolation(
|
|
129
|
+
f"stages.{stage_id}.topology_satisfaction",
|
|
130
|
+
f"unknown role topology: {topology}",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
continue
|
|
134
|
+
if not isinstance(rule, dict):
|
|
135
|
+
violations.append(
|
|
136
|
+
FieldViolation(
|
|
137
|
+
f"stages.{stage_id}.topology_satisfaction.{topology}",
|
|
138
|
+
"must be an object",
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
continue
|
|
142
|
+
satisfied_by = str(rule.get("satisfied_by") or "").strip()
|
|
143
|
+
if not satisfied_by:
|
|
144
|
+
continue
|
|
145
|
+
if satisfied_by not in ROLE_TOPOLOGY_SATISFIER_VALUES:
|
|
146
|
+
violations.append(
|
|
147
|
+
FieldViolation(
|
|
148
|
+
f"stages.{stage_id}.topology_satisfaction.{topology}.satisfied_by",
|
|
149
|
+
f"unknown topology satisfier: {satisfied_by}",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
required_artifacts = rule.get("required_artifacts") or []
|
|
153
|
+
required_artifacts_field = (
|
|
154
|
+
f"stages.{stage_id}.topology_satisfaction.{topology}.required_artifacts"
|
|
155
|
+
)
|
|
156
|
+
if not isinstance(required_artifacts, list):
|
|
157
|
+
violations.append(
|
|
158
|
+
FieldViolation(
|
|
159
|
+
required_artifacts_field,
|
|
160
|
+
"must be a list of artifact ids",
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
continue
|
|
164
|
+
for artifact_id in required_artifacts:
|
|
165
|
+
if not isinstance(artifact_id, str) or not artifact_id.strip():
|
|
166
|
+
violations.append(
|
|
167
|
+
FieldViolation(
|
|
168
|
+
required_artifacts_field,
|
|
169
|
+
"must contain only non-empty artifact id strings",
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
continue
|
|
173
|
+
if artifact_id not in artifact_id_set:
|
|
174
|
+
violations.append(
|
|
175
|
+
FieldViolation(
|
|
176
|
+
required_artifacts_field,
|
|
177
|
+
f"unknown artifact: {artifact_id}",
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
return violations
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def validate_config_parity(
|
|
184
|
+
*,
|
|
185
|
+
root_config_dir: str | Path,
|
|
186
|
+
package_config_dir: str | Path,
|
|
187
|
+
) -> list[FieldViolation]:
|
|
188
|
+
"""Validate that root and packaged config copies are byte-equivalent."""
|
|
189
|
+
root = Path(root_config_dir)
|
|
190
|
+
package = Path(package_config_dir)
|
|
191
|
+
violations: list[FieldViolation] = []
|
|
192
|
+
for rel_path in PARITY_CONFIG_FILES:
|
|
193
|
+
root_path = root / rel_path
|
|
194
|
+
package_path = package / rel_path
|
|
195
|
+
if not root_path.exists():
|
|
196
|
+
violations.append(
|
|
197
|
+
FieldViolation(f"configs.{rel_path}", "root config file is missing")
|
|
198
|
+
)
|
|
199
|
+
continue
|
|
200
|
+
if not package_path.exists():
|
|
201
|
+
violations.append(
|
|
202
|
+
FieldViolation(f"configs.{rel_path}", "packaged config file is missing")
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
if root_path.read_text(encoding="utf-8") != package_path.read_text(encoding="utf-8"):
|
|
206
|
+
violations.append(
|
|
207
|
+
FieldViolation(
|
|
208
|
+
f"configs.{rel_path}",
|
|
209
|
+
"root and packaged config copies differ",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
return violations
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _duplicate_violations(field: str, values: list[str]) -> list[FieldViolation]:
|
|
216
|
+
seen: set[str] = set()
|
|
217
|
+
duplicates: set[str] = set()
|
|
218
|
+
for value in values:
|
|
219
|
+
if value in seen:
|
|
220
|
+
duplicates.add(value)
|
|
221
|
+
seen.add(value)
|
|
222
|
+
return [
|
|
223
|
+
FieldViolation(field, f"duplicate value: {value}")
|
|
224
|
+
for value in sorted(duplicates)
|
|
225
|
+
]
|