Skip to content

feat(marketplace): typed allow-list gate with open-to-everyone toggle#8485

Open
LWS49 wants to merge 11 commits into
lws49/feat-marketplace-pr5b-miscfrom
lws49/feat-marketplace-pr6-allowlist
Open

feat(marketplace): typed allow-list gate with open-to-everyone toggle#8485
LWS49 wants to merge 11 commits into
lws49/feat-marketplace-pr5b-miscfrom
lws49/feat-marketplace-pr6-allowlist

Conversation

@LWS49

@LWS49 LWS49 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Marketplace visibility is currently all-or-nothing, which blocks a staged rollout. This PR gates the marketplace behind a global typed allow-list: course managers/owners only see it if a rule matches them (specific user, whole instance, or email domain), while system administrators always retain access. It adds a self-serve System::Admin page to manage the rules, and a deliberate, reversible "open to everyone" mode that opens the marketplace to all course managers everywhere in one action without discarding the scoped rules built up during the staged rollout.

Design decisions

  • Rules live in one global (non tenant-scoped) table with a typed rule_type enum (user / instance / email_domain / everyone) - a single pool keeps administration in one place, and instance rules still scope per-instance naturally because the match goes through user.instance_users, which is tenant-scoped at query time
  • "Open to everyone" is a fourth rule type rather than a separate settings flag - enable inserts one everyone row, disable deletes it, so scoped rules survive a temporary opening untouched and the existing create/destroy endpoints are reused with no new routes
  • The admin page surfaces everyone-mode as a page-level banner + toggle instead of a fourth table row - while open, the scoped table renders dimmed with add/delete disabled, making both the active mode and its reversibility obvious at a glance
  • At most one everyone row, enforced by a model uniqueness validation paired with a partial unique DB index

Regression prevention

  • Model specs cover: per-type presence validations, the single-everyone-rule constraint, and grants_access? matching for user rules, instance rules (including per-instance scoping under the current tenant), case-insensitive email-domain rules, the everyone rule, and nil-user denial
  • Controller specs cover: the visibility gate (manager denied without a matching rule, allowed with one or under everyone, sysadmin bypass with zero rules, students still denied even under everyone), admin CRUD, everyone-mode JSON reporting (everyoneRuleId, everyone row excluded from the scoped list), and rejection of a second everyone rule with 400
  • Frontend tests cover: the rules table and add/delete flows, the open-to-everyone and restrict confirm flows (request payloads and banner copy transitions), and disabled add/delete controls while open
  • Manually tested end to end: gate hidden for non-allow-listed managers (sidebar and direct URL), each rule type granting access, per-instance scoping, sysadmin bypass, students never gaining access, admin page CRUD and empty state, open-to-everyone and restrict round trip with scoped rules preserved, and double-open rejection
  • Behaviour change for existing users is intentional: managers lose marketplace access until allow-listed (or the marketplace is opened to everyone); system administrators are unaffected

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.

1 participant