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

feat(proposals): Phase 2 filter extensions, /heat and /readiness endpoints

Extend list_proposals() to accept ProposalListFilters: risk_band (score range predicates), domain (code domain via risk_score > 0), author_type (LEFT JOIN to musehub_identities), assigned_reviewer (EXISTS sub-select), and sort orders newest/oldest/risk_desc/risk_asc/merge_ready_first. Fix count query to reuse the data query's join so no Cartesian product is produced for author_type filters.

Add GET /api/repos/{id}/proposals/heat → DomainHeatResponse and GET /api/repos/{id}/proposals/readiness → MergeReadinessResponse endpoints to proposals.py. Extend the list endpoint to accept all ProposalListFilters params as query parameters.

Add 32 Tier-2 (integration) and Tier-6 (performance) tests covering state/risk_band/domain/author_type/assigned_reviewer filters, sort correctness, pagination, heat aggregation, and readiness bucketing.

sha256:9db5457c47dc08996ac0570a4d52892b8cff20b3b8e7dbe7a303b1ebfb5d3ad9 sha
+68 ~26 symbols
sha256:ee441cd4a8fbee36560d5ab2631b4153dbf1035021a43caa669eeec9663cc0bf snapshot
+68
symbols added
~26
symbols modified
0
dead code introduced
Semantic Changes 94 symbols
~ tests/test_proposal_list_phase2.py .py 60 symbols added
+ TestIntegrationDomainHeat class class TestIntegrationDomainHeat L432–485
+ test_avg_risk_in_code_domain method async_method test_avg_risk_in_code_domain L475–485
+ test_code_domain_count_matches_proposals_with_nonzero_risk method async_method test_code_domain_count_matches_proposals_with_nonzero_risk L436–451
+ test_empty_repo_returns_zero_counts method async_method test_empty_repo_returns_zero_counts L454–458
+ test_merged_proposals_excluded_from_open_heat method async_method test_merged_proposals_excluded_from_open_heat L461–472
+ TestIntegrationListProposalsAssignedReviewer class class TestIntegrationListProposalsAssignedReviewer L288–318
+ test_assigned_reviewer_returns_proposals_with_pending_review method async_method test_assigned_reviewer_returns_proposals_with_pending_review L292–306
+ test_dismissed_review_not_returned method async_method test_dismissed_review_not_returned L309–318
+ TestIntegrationListProposalsAuthorType class class TestIntegrationListProposalsAuthorType L251–285
+ test_agent_filter_returns_agent_proposals method async_method test_agent_filter_returns_agent_proposals L255–269
+ test_human_filter_returns_human_and_unknown_authors method async_method test_human_filter_returns_human_and_unknown_authors L272–285
+ TestIntegrationListProposalsDomain class class TestIntegrationListProposalsDomain L215–248
+ test_code_domain_returns_proposals_with_nonzero_risk method async_method test_code_domain_returns_proposals_with_nonzero_risk L219–233
+ test_no_domain_filter_returns_all method async_method test_no_domain_filter_returns_all L236–248
+ TestIntegrationListProposalsPagination class class TestIntegrationListProposalsPagination L390–425
+ test_cursor_advances_to_next_page method async_method test_cursor_advances_to_next_page L408–425
+ test_limit_caps_result_count method async_method test_limit_caps_result_count L394–405
+ TestIntegrationListProposalsRiskBand class class TestIntegrationListProposalsRiskBand L139–212
+ test_critical_band_returns_high_score_proposals method async_method test_critical_band_returns_high_score_proposals L143–158
+ test_high_band_excludes_critical method async_method test_high_band_excludes_critical L161–175
+ test_multiple_bands_or_semantics method async_method test_multiple_bands_or_semantics L178–195
+ test_none_band_returns_zero_score_proposals method async_method test_none_band_returns_zero_score_proposals L198–212
+ TestIntegrationListProposalsSort class class TestIntegrationListProposalsSort L321–387
+ test_merge_ready_first_surfaces_ready_proposals method async_method test_merge_ready_first_surfaces_ready_proposals L371–387
+ test_newest_sort_returns_most_recent_first method async_method test_newest_sort_returns_most_recent_first L325–338
+ test_oldest_sort_returns_earliest_first method async_method test_oldest_sort_returns_earliest_first L341–353
+ test_risk_desc_returns_highest_risk_first method async_method test_risk_desc_returns_highest_risk_first L356–368
+ TestIntegrationListProposalsState class class TestIntegrationListProposalsState L103–136
+ test_state_all_returns_all method async_method test_state_all_returns_all L129–136
+ test_state_merged_returns_only_merged method async_method test_state_merged_returns_only_merged L118–126
+ test_state_open_returns_only_open method async_method test_state_open_returns_only_open L107–115
+ TestIntegrationMergeReadiness class class TestIntegrationMergeReadiness L492–559
+ test_merged_proposals_excluded_from_readiness method async_method test_merged_proposals_excluded_from_readiness L536–545
+ test_proposal_with_breakage_goes_to_needs_review method async_method test_proposal_with_breakage_goes_to_needs_review L510–522
+ test_proposal_with_one_approval_goes_to_needs_review method async_method test_proposal_with_one_approval_goes_to_needs_review L548–559
+ test_ready_proposal_with_two_approvals_no_breakage method async_method test_ready_proposal_with_two_approvals_no_breakage L496–507
+ test_settling_proposal_goes_to_settling_bucket method async_method test_settling_proposal_goes_to_settling_bucket L525–533
+ TestPerformanceListProposals class class TestPerformanceListProposals L566–629
+ test_get_domain_heat_under_50ms method async_method test_get_domain_heat_under_50ms L589–600
+ test_get_merge_readiness_under_50ms method async_method test_get_merge_readiness_under_50ms L603–612
+ test_list_20_proposals_under_100ms method async_method test_list_20_proposals_under_100ms L575–586
+ test_risk_desc_sort_under_100ms method async_method test_risk_desc_sort_under_100ms L615–629
+ _make_identity function async_function _make_identity L60–74
+ _make_repo function async_function _make_repo L38–57
+ _make_review function async_function _make_review L77–96
+ _now function function _now L34–35
+ AsyncClient import import AsyncClient L20–20
+ AsyncSession import import AsyncSession L22–22
+ ProposalListFilters import import ProposalListFilters L25–25
+ annotations import import annotations L12–12
+ create_proposal import import create_proposal L27–27
+ datetime import import datetime L16–16
+ dbm import import dbm L24–24
+ musehub_proposals import import musehub_proposals L26–26
+ pytest import import pytest L18–18
+ pytest_asyncio import import pytest_asyncio L19–19
+ select import import select L21–21
+ time import import time L14–14
+ timezone import import timezone L16–16
+ uuid import import uuid L15–15
~ musehub/api/routes/musehub/proposals.py .py 5 symbols added, 15 symbols modified
+ get_proposal_domain_heat function async_function get_proposal_domain_heat L238–269
+ get_proposal_merge_readiness function async_function get_proposal_merge_readiness L278–306
+ DomainHeatResponse import import DomainHeatResponse L31–31
+ MergeReadinessResponse import import MergeReadinessResponse L31–31
+ ProposalListFilters import import ProposalListFilters L31–31
~ musehub/services/musehub_proposals.py .py 3 symbols added, 11 symbols modified
← Older Oldest on task/proposal-list-aggregates
All commits
Newer → Latest on task/proposal-list-aggregates

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:9db5457c47dc08996ac0570a4d52892b8cff20b3b8e7dbe7a303b1ebfb5d3ad9 --body "your comment"