Skip to content

refactor(policy): add Filter.RolePermissions, use it in membership listing#1623

Merged
AmanGIT07 merged 3 commits into
feat/membership-list-resources-by-principalfrom
refactor/policy-filter-role-permissions
May 19, 2026
Merged

refactor(policy): add Filter.RolePermissions, use it in membership listing#1623
AmanGIT07 merged 3 commits into
feat/membership-list-resources-by-principalfrom
refactor/policy-filter-role-permissions

Conversation

@AmanGIT07
Copy link
Copy Markdown
Contributor

Summary

Pushes the "policies whose role grants any of these permissions" filter into the policy layer. Membership stops doing the local list-then-filter dance for project listing.

Stacked on #1618 — review the diff against that branch. Will rebase onto main once #1618 merges.

Change

policy.Filter gains:

RolePermissions []string

The repo implements it by joining the roles table and using Postgres jsonb_exists_any to check overlap with role.permissions. Generated SQL (prepared mode):

SELECT p.* FROM policies p
INNER JOIN roles r ON r.id = p.role_id
WHERE p.principal_id = $1
  AND p.principal_type = $2
  AND p.resource_type = $3
  AND jsonb_exists_any(r.permissions, $4)

Membership call sites

Before:

policies, _ := s.policyService.List(ctx, policy.Filter{
    PrincipalID:   userID,
    PrincipalType: schema.UserPrincipal,
    ResourceType:  schema.ProjectNamespace,
})
return s.filterByRolePermissions(ctx, policies, schema.ProjectDirectVisibilityPerms)

After:

policies, _ := s.policyService.List(ctx, policy.Filter{
    PrincipalID:     userID,
    PrincipalType:   schema.UserPrincipal,
    ResourceType:    schema.ProjectNamespace,
    RolePermissions: schema.ProjectDirectVisibilityPerms,
})
return policyResourceIDs(policies), nil

Same for listGroupExpandedProjectIDs and listOrgInheritedProjectIDs. The filterByRolePermissions helper is removed (no remaining callers).

What doesn't change

  • Org listing (listOrgsForPrincipal) — doesn't set RolePermissions, behavior identical.
  • Group listing (listGroupsForPrincipal) — same.
  • The two schema constants in schema.go (ProjectDirectVisibilityPerms, OrganizationProjectInheritPerms) — unchanged; callers just pass them via policy.Filter now.
  • All 16 TestService_ListResourcesByPrincipal cases still pass; mock expectations updated for the new filter shape.

Files changed

  • core/policy/filter.goRolePermissions []string field
  • internal/store/postgres/policy_repository.go — JOIN + jsonb_exists_any branch in applyListFilter, built with goqu.Func
  • core/membership/service.go — three helpers use RolePermissions; filterByRolePermissions removed
  • core/membership/service_test.go — mock expectations updated; obsolete role fixtures removed

Test plan

  • make build
  • make lint (0 issues)
  • make test -race on touched packages green (core/membership, core/policy, internal/store/postgres)
  • SQL safety rules per .agents/backend/sql-safety.md followed: no ? in single-quoted strings, no goqu.L, identifiers via goqu.I/goqu.T, prepared-mode-clean (all values bound as $N)
  • Verified live against dev DB — query returns the same row as the previous algorithm trace (user 458a3e94... → Org Owner policy on PixxelSpace → 175 inherited projects)

🤖 Generated with Claude Code

…sting

Pushes the "policies whose role grants any of these permissions" filter
into the policy layer. The repo joins roles and uses jsonb_exists_any to
match against role.permissions.

Membership's three project-listing helpers drop the local
list-then-filter dance and pass RolePermissions through to policy.List.
The filterByRolePermissions helper is removed.

Org and group listing don't use the field — they don't gate by role
permissions, behavior unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment May 19, 2026 9:05am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6651b3b4-6721-43f9-b9d3-b7c30c56a1ae

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Removed a stale-state explainer that belongs in the PR description, not
the code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coveralls
Copy link
Copy Markdown

coveralls commented May 19, 2026

Coverage Report for CI Build 26087403040

Coverage decreased (-0.04%) to 42.472%

Details

  • Coverage decreased (-0.04%) from the base build.
  • Patch coverage: 13 uncovered changes across 1 file (22 of 35 lines covered, 62.86%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
internal/store/postgres/policy_repository.go 14 1 7.14%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 37907
Covered Lines: 16100
Line Coverage: 42.47%
Coverage Strength: 11.91 hits per line

💛 - Coveralls

Drops a few doc lines that referenced what the new code replaces rather
than what it does:

- listProjectsForPrincipal: "matching today's listNonInheritedProjectIDs"
- listDirectProjectIDs: "analog of what SpiceDB does today"
- TestService_ListResourcesByPrincipal: outdated header that also
  claimed direct/group aren't role-gated (now incorrect)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AmanGIT07
Copy link
Copy Markdown
Contributor Author

E2E test — policy.Filter.RolePermissions + membership.ListResourcesByPrincipal

Result: 25 / 25 passed against dev Postgres.

# Scenario Expected Got Status
1 Org listing — Org Owner policy returns the org [OrgA] [OrgA] PASS
2 Org listing — Org Viewer returns the org (no role gate) [OrgA] [OrgA] PASS
3 Org listing — billing-only role returns the org (no role gate) [OrgA] [OrgA] PASS
4 Org listing — multi-org user returns both orgs [OrgA, OrgB] [OrgA, OrgB] PASS
5 Org listing — no policies → empty [] [] PASS
6 Org listing — service user with org policy returns the org [OrgA] [OrgA] PASS
7 Group listing — group member returns G1 [G1] [G1] PASS
8 Group listing — non-member → empty [] [] PASS
9 Project (direct) — ProjViewer role returns P1 [P1] [P1] PASS
10 Project (direct) — role without visibility perms gated out → empty [] [] PASS
11 Project (group) — G1 with ProjViewer on P3 → P3 [P3] [P3] PASS
12 Project (group) — G2 with weak role on P4 → empty [] [] PASS
13 Project (inherit) — Org Owner sees all 5 OrgA projects [P1, P2, P3, P4, P5] same PASS
14 Project (inherit) — Org Viewer fails gate → empty [] [] PASS
15 Project (inherit) — billing-only fails gate → empty [] [] PASS
16 Project (inherit) — empty-permissions role excluded by JOIN [] [] PASS
17 Project (inherit) — multi-org Owner: 5 OrgA + 1 OrgB [P1, P2, P3, P4, P5, P6] same PASS
18 Project — OrgID filter narrows multi-org Owner to OrgA [P1, P2, P3, P4, P5] same PASS
19 Project — NonInherited=true + Owner-only → empty [] [] PASS
20 Project — NonInherited=true keeps direct policy [P1] [P1] PASS
21 Project — dedup: Owner + direct P1 → [P1..P5] once [P1, P2, P3, P4, P5] same PASS
22 Project — no policies → empty [] [] PASS
23 PAT — specific-project PAT clamps user's wider visibility → [P2] [P2] [P2] PASS
24 PAT — all-projects PAT matches user's full visibility → [P1..P5] [P1, P2, P3, P4, P5] same PASS
25 PAT — zero-visibility PAT → empty [] [] PASS

Coverage

Dimension Tested
Resource types app/organization, app/project, app/group
Principal types app/user, app/serviceuser, app/pat
Project branches direct, group-expanded, org-inheritance, combined
Role gate states passes, fails (no overlap), fails (empty perms)
Filter modifiers OrgID, NonInherited, default
PAT semantics none, specific-project, all-projects, zero-scope

@AmanGIT07 AmanGIT07 merged commit e4fca7a into feat/membership-list-resources-by-principal May 19, 2026
8 checks passed
@AmanGIT07 AmanGIT07 deleted the refactor/policy-filter-role-permissions branch May 19, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants