gabriel / musehub public
feat BREAKING proposals task/proposal-list-models #1 / 1
AI Agent gabriel · 127 days ago · May 8, 2026 · Diff

feat(proposals): Phase 1 list enrichment models and service

Add ProposalListEntry, ProposalListFilters, DomainHeatResponse, and MergeReadinessResponse Pydantic models. Implement zero-N+1 enrichment via _prefetch_for_batch + _enrich_one, plus enrich_proposal_list_batch, enrich_proposal_list_entry, get_domain_heat, and get_merge_readiness in musehub_proposals.py.

Delete infer_list_risk_band (branch-name heuristic with no DB backing) from musehub_proposal_risk.py and all three call sites; replace with _score_to_band using proposal.risk_score.

Add 64 Tier-1 (unit) and Tier-5 (data-integrity) tests covering band mapping, enrich_one risk/approval/metadata logic, Pydantic field constraints, and output invariants.

sha256:0614a3162653d62006dff44ec86e0fddc614adcbc0010fcc1af59f5af0347f68 sha
+115 ~11 −18 symbols
sha256:f0f37864b4c1990e80969f58b066567df7756306f922b8ba2aa2d3346dbd9967 snapshot
+115
symbols added
~11
symbols modified
−18
symbols removed
0
dead code introduced
Semantic Changes 144 symbols
~ tests/test_proposal_list_phase1.py .py 94 symbols added
+ TestDataIntegrityEnrichOne class class TestDataIntegrityEnrichOne L446–547
+ test_active_domains_subset_of_domain_risk_keys method method test_active_domains_subset_of_domain_risk_keys L468–472
+ test_aggregate_band_consistent_with_score method method test_aggregate_band_consistent_with_score L512–519
+ test_aggregate_risk_score_always_in_0_1 method method test_aggregate_risk_score_always_in_0_1 L453–459
+ test_deterministic_output_same_input method method test_deterministic_output_same_input L492–497
+ test_domain_risk_values_always_in_0_1 method method test_domain_risk_values_always_in_0_1 L461–466
+ test_domains_approved_subset_of_active_domains method method test_domains_approved_subset_of_active_domains L474–480
+ test_domains_pending_review_complement_of_approved_within_active method method test_domains_pending_review_complement_of_approved_within_active L482–490
+ test_merge_condition_met_only_when_no_breakage_and_sufficient_approvals method method test_merge_condition_met_only_when_no_breakage_and_sufficient_approvals L521–535
+ test_null_risk_score_treated_as_zero method method test_null_risk_score_treated_as_zero L542–547
+ test_null_touched_symbols_treated_as_empty method method test_null_touched_symbols_treated_as_empty L537–540
+ test_proposal_id_preserved_exactly method method test_proposal_id_preserved_exactly L499–503
+ test_touched_symbols_preview_never_longer_than_3 method method test_touched_symbols_preview_never_longer_than_3 L505–510
+ TestUnitEnrichOneApprovals class class TestUnitEnrichOneApprovals L203–270
+ test_all_merge_conditions_met_requires_approvals_and_no_breakage method method test_all_merge_conditions_met_requires_approvals_and_no_breakage L227–235
+ test_breakage_blocks_merge_even_with_enough_approvals method method test_breakage_blocks_merge_even_with_enough_approvals L237–245
+ test_domains_approved_populated_when_code_active_and_approved method method test_domains_approved_populated_when_code_active_and_approved L258–263
+ test_domains_pending_review_when_not_approved method method test_domains_pending_review_when_not_approved L265–270
+ test_no_reviews_zero_approvals method method test_no_reviews_zero_approvals L209–213
+ test_non_approved_reviews_not_counted method method test_non_approved_reviews_not_counted L247–256
+ test_one_approved_review_increments_count method method test_one_approved_review_increments_count L215–220
+ test_required_approvals_default_is_two method method test_required_approvals_default_is_two L222–225
+ TestUnitEnrichOneMiscFields class class TestUnitEnrichOneMiscFields L277–335
+ test_author_type_defaults_to_human_if_missing method method test_author_type_defaults_to_human_if_missing L314–317
+ test_author_type_resolved_from_prefetch method method test_author_type_resolved_from_prefetch L305–312
+ test_is_blocked_false_by_default method method test_is_blocked_false_by_default L330–335
+ test_is_draft_false_for_open_state method method test_is_draft_false_for_open_state L300–303
+ test_is_draft_true_for_drafting_state method method test_is_draft_true_for_drafting_state L295–298
+ test_title_passthrough_under_limit method method test_title_passthrough_under_limit L283–286
+ test_title_truncated_at_80_chars method method test_title_truncated_at_80_chars L288–293
+ test_touched_symbols_preview_capped_at_three method method test_touched_symbols_preview_capped_at_three L319–323
+ test_touched_symbols_preview_empty_list method method test_touched_symbols_preview_empty_list L325–328
+ TestUnitEnrichOneRisk class class TestUnitEnrichOneRisk L150–196
+ test_aggregate_score_equals_code_when_only_domain method method test_aggregate_score_equals_code_when_only_domain L171–175
+ test_aggregate_score_rounded_to_4_decimal_places method method test_aggregate_score_rounded_to_4_decimal_places L192–196
+ test_domain_risk_band_follows_score method method test_domain_risk_band_follows_score L177–180
+ test_invalid_risk_score_raises_value_error method method test_invalid_risk_score_raises_value_error L182–185
+ test_negative_risk_score_raises_value_error method method test_negative_risk_score_raises_value_error L187–190
+ test_nonzero_risk_activates_code_domain method method test_nonzero_risk_activates_code_domain L164–169
+ test_zero_risk_score_yields_no_active_domains method method test_zero_risk_score_yields_no_active_domains L156–162
+ TestUnitProposalListFilters class class TestUnitProposalListFilters L342–401
+ test_assigned_reviewer_pattern_rejects_spaces method method test_assigned_reviewer_pattern_rejects_spaces L398–401
+ test_cursor_is_none_by_default method method test_cursor_is_none_by_default L394–396
+ test_default_author_type_is_all method method test_default_author_type_is_all L390–392
+ test_default_limit_is_20 method method test_default_limit_is_20 L362–364
+ test_default_sort_is_newest method method test_default_sort_is_newest L376–378
+ test_default_state_is_open method method test_default_state_is_open L348–350
+ test_invalid_sort_raises method method test_invalid_sort_raises L385–388
+ test_invalid_state_raises method method test_invalid_state_raises L357–360
+ test_limit_max_is_100 method method test_limit_max_is_100 L371–374
+ test_limit_min_is_1 method method test_limit_min_is_1 L366–369
+ test_valid_sorts_accepted method method test_valid_sorts_accepted L380–383
+ test_valid_states_accepted method method test_valid_states_accepted L352–355
+ TestUnitResponseDefaults class class TestUnitResponseDefaults L408–439
+ test_domain_heat_entry_defaults method method test_domain_heat_entry_defaults L419–422
+ test_domain_heat_entry_rejects_negative_count method method test_domain_heat_entry_rejects_negative_count L431–434
+ test_domain_heat_entry_rejects_risk_above_1 method method test_domain_heat_entry_rejects_risk_above_1 L436–439
+ test_domain_heat_response_defaults method method test_domain_heat_response_defaults L414–417
+ test_merge_readiness_response_defaults method method test_merge_readiness_response_defaults L424–429
+ TestUnitScoreToBand class class TestUnitScoreToBand L105–143
+ test_below_critical_threshold_returns_high method method test_below_critical_threshold_returns_high L133–134
+ test_below_high_threshold_returns_medium method method test_below_high_threshold_returns_medium L127–128
+ test_below_low_threshold_returns_none method method test_below_low_threshold_returns_none L114–116
+ test_below_medium_threshold_returns_low method method test_below_medium_threshold_returns_low L121–122
+ test_exactly_critical_threshold_returns_critical method method test_exactly_critical_threshold_returns_critical L136–137
+ test_exactly_high_threshold_returns_high method method test_exactly_high_threshold_returns_high L130–131
+ test_exactly_low_threshold_returns_low method method test_exactly_low_threshold_returns_low L118–119
+ test_exactly_medium_threshold_returns_medium method method test_exactly_medium_threshold_returns_medium L124–125
+ test_max_score_returns_critical method method test_max_score_returns_critical L139–140
+ test_mid_critical_range method method test_mid_critical_range L142–143
+ test_zero_returns_none method method test_zero_returns_none L111–112
+ _empty_prefetch function function _empty_prefetch L84–89
+ _make_proposal function function _make_proposal L42–74
+ _make_review function function _make_review L77–81
+ _prefetch_with_reviews function function _prefetch_with_reviews L92–98
+ Any import import Any L17–17
+ AsyncMock import import AsyncMock L18–18
+ DomainHeatEntry import import DomainHeatEntry L22–22
+ DomainHeatResponse import import DomainHeatResponse L22–22
+ MagicMock import import MagicMock L18–18
+ MergeReadinessResponse import import MergeReadinessResponse L22–22
+ ProposalListEntry import import ProposalListEntry L22–22
+ ProposalListFilters import import ProposalListFilters L22–22
+ _DEFAULT_REQUIRED_APPROVALS import import _DEFAULT_REQUIRED_APPROVALS L29–29
+ _DOMAIN_WEIGHTS import import _DOMAIN_WEIGHTS L29–29
+ _ProposalPrefetch import import _ProposalPrefetch L29–29
+ _enrich_one import import _enrich_one L29–29
+ _score_to_band import import _score_to_band L29–29
+ annotations import import annotations L13–13
+ datetime import import datetime L16–16
+ patch import import patch L18–18
+ pytest import import pytest L20–20
+ timezone import import timezone L16–16
+ uuid import import uuid L15–15
~ musehub/api/routes/musehub/ui_proposals.py .py 1 symbol removed, 2 symbols modified
infer_list_risk_band import import infer_list_risk_band L32–32
~ musehub/models/musehub.py .py 5 symbols added
+ DomainHeatEntry class class DomainHeatEntry L1338–1347
+ DomainHeatResponse class class DomainHeatResponse L1350–1362
+ MergeReadinessResponse class class MergeReadinessResponse L1365–1382
+ ProposalListEntry class class ProposalListEntry L1155–1273
+ ProposalListFilters class class ProposalListFilters L1276–1335
infer_list_risk_band function function infer_list_risk_band L192–206
~ musehub/services/musehub_proposals.py .py 16 symbols added, 6 symbols modified
+ _DEFAULT_REQUIRED_APPROVALS variable variable _DEFAULT_REQUIRED_APPROVALS L844–844
+ _DOMAIN_WEIGHTS variable variable _DOMAIN_WEIGHTS L847–853
+ _ProposalPrefetch class class _ProposalPrefetch L856–871
+ __init__ method method __init__ L864–871
+ _RISK_BAND_THRESHOLDS variable variable _RISK_BAND_THRESHOLDS L819–824
+ _enrich_one function function _enrich_one L930–1054
+ _prefetch_for_batch function async_function _prefetch_for_batch L874–927
+ _score_to_band function function _score_to_band L827–840
+ enrich_proposal_list_batch function async_function enrich_proposal_list_batch L1084–1112
+ enrich_proposal_list_entry function async_function enrich_proposal_list_entry L1057–1081
+ get_domain_heat function async_function get_domain_heat L1115–1171
+ get_merge_readiness function async_function get_merge_readiness L1174–1265
+ DomainHeatEntry import import DomainHeatEntry L32–32
+ DomainHeatResponse import import DomainHeatResponse L32–32
+ MergeReadinessResponse import import MergeReadinessResponse L32–32
+ ProposalListEntry import import ProposalListEntry L32–32
~ tests/test_merge_proposals.py .py 16 symbols removed, 3 symbols modified
TestUnitInferListRiskBand class class TestUnitInferListRiskBand L208–250
test_breaking_prefix_is_critical method method test_breaking_prefix_is_critical L239–240
test_bugfix_prefix_is_low method method test_bugfix_prefix_is_low L221–222
test_chore_prefix_is_low method method test_chore_prefix_is_low L230–231
test_docs_prefix_is_low method method test_docs_prefix_is_low L233–234
test_feat_prefix_is_medium method method test_feat_prefix_is_medium L209–210
test_feature_prefix_is_medium method method test_feature_prefix_is_medium L212–213
test_fix_prefix_is_low method method test_fix_prefix_is_low L218–219
test_hotfix_prefix_is_low method method test_hotfix_prefix_is_low L224–225
test_major_prefix_is_critical method method test_major_prefix_is_critical L242–243
test_no_slash_treats_whole_name_as_prefix method method test_no_slash_treats_whole_name_as_prefix L248–250
test_patch_prefix_is_low method method test_patch_prefix_is_low L227–228
test_refactor_prefix_is_medium method method test_refactor_prefix_is_medium L215–216
test_test_prefix_is_low method method test_test_prefix_is_low L236–237
test_unknown_prefix_is_medium method method test_unknown_prefix_is_medium L245–246
infer_list_risk_band import import infer_list_risk_band L59–59
~ _band
← Older Oldest on task/proposal-list-models
All commits
Newer → Latest on task/proposal-list-models

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:0614a3162653d62006dff44ec86e0fddc614adcbc0010fcc1af59f5af0347f68 --body "your comment"