Skip to content

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

Closed
LWS49 wants to merge 4 commits into
lws49/feat-marketplace-pr5b-miscfrom
lws49/feat-marketplace-pr6-allowlist
Closed

feat(marketplace): typed allow-list gate with open-to-everyone toggle#8485
LWS49 wants to merge 4 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

@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5b-misc branch from 2fce896 to a3e493d Compare July 17, 2026 07:27
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr6-allowlist branch from b27c3d2 to 0a3cc59 Compare July 17, 2026 07:30
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr5b-misc branch 2 times, most recently from 3bc206d to 909976b Compare July 17, 2026 08:14
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr6-allowlist branch 2 times, most recently from a8e79a4 to 74cd623 Compare July 19, 2026 07:48
LWS49 added 4 commits July 20, 2026 23:27
Specs commit (use_transactional_fixtures is false), so a bare per-second
timestamp collides whenever two rspec processes start within the same second,
tripping unique constraints. Append a random suffix per process.
Lift the instance picker out of InstanceDropdown into a reusable core field so
the marketplace allow-list rule form can share it.
Gate assessment-marketplace browsing per person rather than per current-course
role. A typed allow-list (user / instance / email-domain / everyone rules)
grants access to baseline-capable users (course manager/owner or instance
instructor/admin anywhere), with individual access blocks as overrides.

- AllowlistRule and AccessBlock models, migrations, and per-type uniqueness
- RuleMatchQuery / RulePreviewQuery / AccessListQuery for matching and audit
- ability component gates :access_marketplace on the allow-list minus blocks
- User baseline predicates and delete-user FK handling for both tables
- System::Admin CRUD, access-list, and block/unblock endpoints
System::Admin page to manage the marketplace allow-list and per-user access.
Add/remove typed rules with live preview of who a rule would let in, an access
audit list filterable by status and granting rule, and block/unblock controls.
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr6-allowlist branch from 74cd623 to 93cb96c Compare July 20, 2026 15:28
@LWS49

LWS49 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by the 6a-6d split: #8504 (spec factory fix) -> #8505 (backend) -> #8506 (allow-list UI) -> #8507 (audit list + block controls). Same net change, split so each PR is reviewable on its own. Closing this in favour of those.

@LWS49 LWS49 closed this Jul 22, 2026
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