SearchUserProjects diverges from ListProjectsByCurrentUser — missing inherited and group-expanded projects
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 344
- Forks
- 47
- Avg merge
- 4d 4h
- Merged PRs (30d)
- 26
Description
Symptom
AdminService/SearchUserProjects and FrontierService/ListProjectsByCurrentUser answer effectively the same question — "which projects can this user see?" — but return different results for the same user, same org, same state.
Concrete repro
User Alice is org owner of OrgX (5 projects). She has no direct user→project policies; her visibility comes entirely from the app_organization_owner role on OrgX.
| RPC | Result |
|---|---|
ListProjectsByCurrentUser(orgID=OrgX) |
5 projects (org-inheritance expansion) |
SearchUserProjects(userID=Alice, orgID=OrgX) |
0 projects |
The same gap appears for group-mediated access: a user in a group that holds a project policy is visible to ListProjectsByCurrentUser but invisible to SearchUserProjects.
Where it lives
internal/store/postgres/user_projects_repository.go::buildBaseQuery filters the inner subquery with:
WHERE pol.principal_id = :userID
AND pol.principal_type = 'app/user' -- excludes group-mediated policies
AND pol.resource_type = 'app/project' -- excludes org-level (inherited) policies
Both filters together restrict the result to direct user→project policy rows only.
project.Service.List(Filter{Principal}), used by ListProjectsByCurrentUser, delegates to membership.Service.ListProjectsByPrincipal, which unions three paths:
- Direct project policies (gated by
schema.ProjectDirectVisibilityPerms). - Group-expanded — principal's group memberships → policies on those groups.
- Org-inherited (skipped when
NonInherited=true) — org-level policies gated byschema.OrganizationProjectInheritPerms.
The aggregate query in user_projects_repository only implements path 1.
Background
The divergence is pre-existing. It became visible after the four-PR membership listing migration (parent #1478) made the ListProjects* semantics explicit and policy-table-backed end-to-end. PR #1648 originally landed a TODO(fix) comment at the aggregate; the comment was dropped in favour of this tracked issue.
Options
- A. Align the aggregate. Rewrite
buildBaseQuerytoUNION(a) direct user→project, (b) group-expanded — principal's groups joined into project policies, (c) org-inherited — Alice's org policies whose role grants any permission inOrganizationProjectInheritPerms, expanded to all projects in those orgs. Mirrors the membership method's logic in SQL. - B. Redefine the aggregate. Document
SearchUserProjectsas "projects on which the user has a direct policy" by product decision. Update the admin UI label if needed. - C. Document and leave both as-is. Keep the divergence; add a comment explaining the semantic difference.
A is the consistent fix; B is a smaller scope decision; C is the cheapest if the divergence is acceptable.
Acceptance
- Decision recorded.
- If A: aggregate returns the same set as
project.Service.List(Filter{Principal})for a given user/org. Regression test covering an org-owner-with-no-direct-policy case. - If B: admin UI / API docs reflect the narrowed semantic.
- If C: comment near
buildBaseQuerydocumenting the divergence and linking to this issue.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in internal/store/postgres/user_projects_repository.go at buildBaseQuery, then compare its direct-policy query with project.Service.List(Filter{Principal}) and membership.Service.ListProjectsByPrincipal. First resolve whether the project wants options A, B, or C; done means the recorded decision and its corresponding implementation, documentation, or comment, with the specified regression comparison if A is chosen.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- authorization, backend, databases
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100