proposals._guard_repo_owner rejects write collaborators, only allows literal repo owner
Report
aaronrene got a 403 whenever trying to assign gabriel as a reviewer on his own MP (https://staging.musehub.ai/gabriel/musehub/proposals/sha256:ba2a2fca6377ccc9782630d52d7ecdc56923d10624a26eff30251e686b2ec3f5), despite having confirmed write collaborator access on the repo.
Root cause
musehub/api/routes/musehub/proposals.py had its own _guard_repo_owner
helper — sync, 2-argument, checking only literal repo ownership:
def _guard_repo_owner(repo: RepoResponse, caller_handle: str) -> None:
if repo.owner != caller_handle:
raise HTTPException(status_code=403, ...)
musehub/api/routes/musehub/issues.py already has the correct,
collaborator-aware version — async, checks the MusehubCollaborator table
for an accepted write/admin row before rejecting. proposals.py's version
never got that treatment.
This guard gates three endpoints: request reviewers, remove reviewer, and merge proposal — all three incorrectly required literal ownership instead of write/admin collaborator access.
Fix
Ported issues.py's collaborator-aware _guard_repo_owner into
proposals.py verbatim (now async, takes db), updated all three call
sites to await it.
TDD: TestProposalsGuardRepoOwner in tests/test_auth_authorization.py,
4 tests, red-first (the old sync 2-arg signature couldn't even be awaited).
Also added a missing test_write_collaborator_allowed case to issues.py's
own guard test class — that suite only ever tested the no-collaborator path.
Full regression: 335 passed across the proposal test suites.
Fixed on branch fix/proposal-reviewer-guard-collaborator-check, commit
72d67593. Not yet merged.